mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-03-29 21:40:01 +00:00
Compare commits
195 Commits
room-serve
...
room-serve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9405e8bee3 | ||
|
|
91e9fcea4b | ||
|
|
750e955f19 | ||
|
|
8b68b5a689 | ||
|
|
a5cdc88fe2 | ||
|
|
ba6b8535c9 | ||
|
|
90e26129ee | ||
|
|
b59d1999e6 | ||
|
|
74f136ba7a | ||
|
|
ab0721d6df | ||
|
|
b31d3e7b5f | ||
|
|
1520f4d28e | ||
|
|
62d7ce110b | ||
|
|
28b90c18cf | ||
|
|
963290ea15 | ||
|
|
06825030e5 | ||
|
|
2e63499ae5 | ||
|
|
4a5404d997 | ||
|
|
ddac13ae80 | ||
|
|
256848208d | ||
|
|
09eab330a2 | ||
|
|
cf547da857 | ||
|
|
a9d245fe68 | ||
|
|
23783b27c8 | ||
|
|
7419ed71f7 | ||
|
|
82b4c1e6b0 | ||
|
|
3ef53e64a1 | ||
|
|
937865c8fd | ||
|
|
9ebeb477aa | ||
|
|
04c0c40b39 | ||
|
|
c3dbec41ba | ||
|
|
5c80334dbd | ||
|
|
d9ff3a4d02 | ||
|
|
ecd30f4d36 | ||
|
|
f797744f7c | ||
|
|
03fc949014 | ||
|
|
5b4544b9fe | ||
|
|
920ac51c8c | ||
|
|
0b9f055860 | ||
|
|
d0caa3be04 | ||
|
|
c13b4ae481 | ||
|
|
7755400a35 | ||
|
|
ef752926c9 | ||
|
|
228b073006 | ||
|
|
7ad45d113c | ||
|
|
7abe6c9693 | ||
|
|
52a3df4977 | ||
|
|
0b8159c6e5 | ||
|
|
5088444f85 | ||
|
|
96e786fa9e | ||
|
|
3d9378d91e | ||
|
|
c4e99a841a | ||
|
|
80f0405600 | ||
|
|
886878c70a | ||
|
|
8cbcd2271d | ||
|
|
cc002404fa | ||
|
|
ac37a37b18 | ||
|
|
4aef696620 | ||
|
|
377f9ff67d | ||
|
|
1c052d8ad2 | ||
|
|
1bbc2151f1 | ||
|
|
1d2a115b26 | ||
|
|
81ab944682 | ||
|
|
d4eb04d6e9 | ||
|
|
cb4468bd5d | ||
|
|
9aa11a87ab | ||
|
|
a2f5432818 | ||
|
|
0e259a63ed | ||
|
|
6d6db10ac5 | ||
|
|
2981fc70e1 | ||
|
|
61cd01db27 | ||
|
|
63c3342f7d | ||
|
|
dfb4497c7a | ||
|
|
273a54f104 | ||
|
|
f1824e68b9 | ||
|
|
6288a5d11a | ||
|
|
2e249e24dc | ||
|
|
8ca3ed28cf | ||
|
|
4cfbd3bad5 | ||
|
|
ac15131296 | ||
|
|
87677fda76 | ||
|
|
f27e8ba6b2 | ||
|
|
ec05d40b3c | ||
|
|
5d495d505a | ||
|
|
4687ab74e3 | ||
|
|
292305c5e1 | ||
|
|
31b8f7252a | ||
|
|
99e44f499e | ||
|
|
dab44a1bb0 | ||
|
|
53a2ae97ea | ||
|
|
798725d450 | ||
|
|
a222578041 | ||
|
|
ebf4599c92 | ||
|
|
79d0989702 | ||
|
|
b2dcb06197 | ||
|
|
a5070077ba | ||
|
|
a421215e84 | ||
|
|
37dc715a8e | ||
|
|
ce70792309 | ||
|
|
7d62a27836 | ||
|
|
f085a9d6c5 | ||
|
|
3210475f35 | ||
|
|
666447eafc | ||
|
|
006af52776 | ||
|
|
ece40716da | ||
|
|
24ed5b377f | ||
|
|
15ecf186fa | ||
|
|
02351abc2d | ||
|
|
3c48f01601 | ||
|
|
0e7486552d | ||
|
|
cd920693ec | ||
|
|
d3be6afccb | ||
|
|
fa8c31be88 | ||
|
|
34b9a1c9dc | ||
|
|
ca5dcf22dd | ||
|
|
86ecf97d33 | ||
|
|
c6b4a58449 | ||
|
|
633538d9c7 | ||
|
|
c6e5d5021e | ||
|
|
8426fddcb7 | ||
|
|
93c0180740 | ||
|
|
837e7dcbdb | ||
|
|
487b7c6576 | ||
|
|
69cddbca4e | ||
|
|
7cb2e0863a | ||
|
|
1979517381 | ||
|
|
c4a2b13930 | ||
|
|
bf1da43d7d | ||
|
|
4dc3dda2d8 | ||
|
|
f6064b41e9 | ||
|
|
76dcfbb23a | ||
|
|
ad2894a039 | ||
|
|
70ac820594 | ||
|
|
8a2e4721d1 | ||
|
|
da52d08168 | ||
|
|
b47ace5d10 | ||
|
|
b588e3f1e3 | ||
|
|
da7b8ad669 | ||
|
|
27e5f6e7df | ||
|
|
601479e572 | ||
|
|
da5dbcd274 | ||
|
|
9e3c2fc9d9 | ||
|
|
6ed8e9d514 | ||
|
|
c9fd1827ef | ||
|
|
5f31979e1e | ||
|
|
341b69e3c9 | ||
|
|
13a0202062 | ||
|
|
fb46e5cc8a | ||
|
|
7be65c148e | ||
|
|
e4f2d63b0a | ||
|
|
0502bc370d | ||
|
|
45ab0e8cf7 | ||
|
|
9b4d93d112 | ||
|
|
5ae574b426 | ||
|
|
c568edc8d0 | ||
|
|
3e3fa5b443 | ||
|
|
69e6d69798 | ||
|
|
54675ed1b2 | ||
|
|
e48f3a58ae | ||
|
|
8edcb46a28 | ||
|
|
262e9864e7 | ||
|
|
3912bbdf7d | ||
|
|
aa946bbe36 | ||
|
|
f5f5886327 | ||
|
|
8d8b9a6141 | ||
|
|
18bfc2d81a | ||
|
|
6ee0b85195 | ||
|
|
86225cd24a | ||
|
|
f594f2c7e6 | ||
|
|
219297172a | ||
|
|
6a1f8d65c9 | ||
|
|
b82f5ea7cd | ||
|
|
ec48e6acfc | ||
|
|
e381f03bc2 | ||
|
|
8ac6dcb644 | ||
|
|
fc0cf5f370 | ||
|
|
3dc04deabf | ||
|
|
c8a6bcf57f | ||
|
|
914001344f | ||
|
|
db7635102d | ||
|
|
4e886bfa90 | ||
|
|
816d4e2fa3 | ||
|
|
2297d24013 | ||
|
|
1d45c7ec66 | ||
|
|
76aa7cf488 | ||
|
|
9e460560bf | ||
|
|
9d009074da | ||
|
|
7b3a0bba97 | ||
|
|
2536fa6bcf | ||
|
|
7b08acf56d | ||
|
|
951d2dfdbb | ||
|
|
4b508136b4 | ||
|
|
65be15e6be | ||
|
|
1b0999fc7e | ||
|
|
2e2e677b0a |
15
README.md
15
README.md
@@ -97,10 +97,10 @@ Here are some general principals you should try to adhere to:
|
||||
|
||||
There are a number of fairly major features in the pipeline, with no particular time-frames attached yet. In very rough chronological order:
|
||||
- [X] Companion radio: UI redesign
|
||||
- [ ] Repeater + Room Server: add ACL's (like Sensor Node has)
|
||||
- [ ] Standardise Bridge mode for repeaters
|
||||
- [X] Repeater + Room Server: add ACL's (like Sensor Node has)
|
||||
- [X] Standardise Bridge mode for repeaters
|
||||
- [ ] Repeater/Bridge: Standardise the Transport Codes for zoning/filtering
|
||||
- [ ] Core + Repeater: enhanced zero-hop neighbour discovery
|
||||
- [X] Core + Repeater: enhanced zero-hop neighbour discovery
|
||||
- [ ] Core: round-trip manual path support
|
||||
- [ ] Companion + Apps: support for multiple sub-meshes (and 'off-grid' client repeat mode)
|
||||
- [ ] Core + Apps: support for LZW message compression
|
||||
@@ -113,12 +113,3 @@ There are a number of fairly major features in the pipeline, with no particular
|
||||
- Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page.
|
||||
- Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz).
|
||||
- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community.
|
||||
|
||||
## RAK Wireless Board Support in PlatformIO
|
||||
|
||||
Before building/flashing the RAK4631 targets in this project, there is, unfortunately, some patching you have to do to your platformIO packages to make it work. There is a guide here on the process:
|
||||
[RAK Wireless: How to Perform Installation of Board Support Package in PlatformIO](https://learn.rakwireless.com/hc/en-us/articles/26687276346775-How-To-Perform-Installation-of-Board-Support-Package-in-PlatformIO)
|
||||
|
||||
After building, you will need to convert the output firmware.hex file into a .uf2 file you can copy over to your RAK4631 device (after doing a full erase) by using the command `uf2conv.py -f 0xADA52840 -c firmware.hex` with the python script available from:
|
||||
[GitHub: Microsoft - uf2](https://github.com/Microsoft/uf2/blob/master/utils/uf2conv.py)
|
||||
|
||||
|
||||
39
boards/ESP32-S3-WROOM-1-N4.json
Normal file
39
boards/ESP32-S3-WROOM-1-N4.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-D ARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MSC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_DFU_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MODE=0",
|
||||
"-D ARDUINO_RUNNING_CORE=1",
|
||||
"-D ARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "ESP32-S3-WROOM-1-N4"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "ESP32-S3-WROOM-1-N4 (4 MB Flash, No PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 524288,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
40
boards/heltec_tracker_v2.json
Normal file
40
boards/heltec_tracker_v2.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default_8MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "heltec_tracker_v2"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "heltec_tracker v2",
|
||||
"upload": {
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 8388608,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://heltec.org/",
|
||||
"vendor": "heltec"
|
||||
}
|
||||
72
boards/rak4631.json
Normal file
72
boards/rak4631.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x239A",
|
||||
"0x8029"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x0029"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x002A"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x802A"
|
||||
]
|
||||
],
|
||||
"usb_product": "WisCore RAK4631 Board",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "WisCore_RAK4631_Board",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": [
|
||||
"bluetooth"
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "WisCore RAK4631 Board",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.rakwireless.com",
|
||||
"vendor": "RAKwireless"
|
||||
}
|
||||
47
build.sh
47
build.sh
@@ -1,12 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# usage
|
||||
# sh build.sh build-firmware RAK_4631_Repeater
|
||||
# sh build.sh build-firmwares
|
||||
# sh build.sh build-matching-firmwares RAK_4631
|
||||
# sh build.sh build-companion-firmwares
|
||||
# sh build.sh build-repeater-firmwares
|
||||
# sh build.sh build-room-server-firmwares
|
||||
global_usage() {
|
||||
cat - <<EOF
|
||||
Usage:
|
||||
sh build.sh <command> [target]
|
||||
|
||||
Commands:
|
||||
help|usage|-h|--help: Shows this message.
|
||||
build-firmware <target>: Build the firmware for the given build target.
|
||||
build-firmwares: Build all firmwares for all targets.
|
||||
build-matching-firmwares <build-match-spec>: Build all firmwares for build targets containing the string given for <build-match-spec>.
|
||||
build-companion-firmwares: Build all companion firmwares for all build targets.
|
||||
build-repeater-firmwares: Build all repeater firmwares for all build targets.
|
||||
build-room-server-firmwares: Build all chat room server firmwares for all build targets.
|
||||
|
||||
Examples:
|
||||
Build firmware for the "RAK_4631_repeater" device target
|
||||
$ sh build.sh build-firmware RAK_4631_repeater
|
||||
|
||||
Build all firmwares for device targets containing the string "RAK_4631"
|
||||
$ sh build.sh build-matching-firmwares <build-match-spec>
|
||||
|
||||
Build all companion firmwares
|
||||
$ sh build.sh build-companion-firmwares
|
||||
|
||||
Build all repeater firmwares
|
||||
$ sh build.sh build-repeater-firmwares
|
||||
|
||||
Build all chat room server firmwares
|
||||
$ sh build.sh build-room-server-firmwares
|
||||
EOF
|
||||
}
|
||||
|
||||
# Catch cries for help before doing anything else.
|
||||
case $1 in
|
||||
help|usage|-h|--help)
|
||||
global_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
# get a list of pio env names that start with "env:"
|
||||
get_pio_envs() {
|
||||
|
||||
31
create-uf2.py
Normal file
31
create-uf2.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Adds PlatformIO post-processing to convert hex files to uf2 files
|
||||
|
||||
import os
|
||||
|
||||
Import("env")
|
||||
|
||||
firmware_hex = "${BUILD_DIR}/${PROGNAME}.hex"
|
||||
uf2_file = os.environ.get("UF2_FILE_PATH", "${BUILD_DIR}/${PROGNAME}.uf2")
|
||||
|
||||
def create_uf2_action(source, target, env):
|
||||
uf2_cmd = " ".join(
|
||||
[
|
||||
'"$PYTHONEXE"',
|
||||
'"$PROJECT_DIR/bin/uf2conv/uf2conv.py"',
|
||||
'-f', '0xADA52840',
|
||||
'-c', firmware_hex,
|
||||
'-o', uf2_file,
|
||||
]
|
||||
)
|
||||
env.Execute(uf2_cmd)
|
||||
|
||||
env.AddCustomTarget(
|
||||
name="create_uf2",
|
||||
dependencies=firmware_hex,
|
||||
actions=create_uf2_action,
|
||||
title="Create UF2 file",
|
||||
description="Use uf2conv to convert hex binary into uf2",
|
||||
always_build=True,
|
||||
)
|
||||
227
docs/faq.md
227
docs/faq.md
@@ -1,10 +1,6 @@
|
||||
**MeshCore-FAQ**<!-- omit from toc -->
|
||||
A list of frequently-asked questions and answers for MeshCore
|
||||
|
||||
The current version of this MeshCore FAQ is at https://github.com/meshcore-dev/MeshCore/blob/main/docs/faq.md.
|
||||
This MeshCore FAQ is also mirrored at https://github.com/LitBomb/MeshCore-FAQ and might have newer updates if pull requests on Scott's MeshCore repo are not approved yet.
|
||||
|
||||
author: https://github.com/LitBomb<!-- omit from toc -->
|
||||
---
|
||||
|
||||
- [1. Introduction](#1-introduction)
|
||||
@@ -61,22 +57,23 @@ author: https://github.com/LitBomb<!-- omit from toc -->
|
||||
- [5.14.3. Python MeshCore](#5143-python-meshcore)
|
||||
- [5.14.4. meshcore-cli](#5144-meshcore-cli)
|
||||
- [5.14.5. meshcore.js](#5145-meshcorejs)
|
||||
- [5.14.6. pyMC\_core](#5146-pymc_core)
|
||||
- [6. Troubleshooting](#6-troubleshooting)
|
||||
- [6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago.](#61-q-my-client-says-another-client-or-a-repeater-or-a-room-server-was-last-seen-many-many-days-ago)
|
||||
- [6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed.](#62-q-a-repeater-or-a-client-or-a-room-server-i-expect-to-see-on-my-discover-list-on-t-deck-or-contact-list-on-a-smart-device-client-are-not-listed)
|
||||
- [6.3. Q: How to connect to a repeater via BLE (Bluetooth)?](#63-q-how-to-connect-to-a-repeater-via-ble-bluetooth)
|
||||
- [6.4. Q: My companion isn't showing up over Bluetooth?](#64-q-my-companion-isnt-showing-up-over-bluetooth)
|
||||
- [6.5. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code?](#64-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code)
|
||||
- [6.6. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection.](#65-q-my-heltec-v3-keeps-disconnecting-from-my-smartphone--it-cant-hold-a-solid-bluetooth-connection)
|
||||
- [6.7. Q: My RAK/T1000-E/xiao\_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh?](#66-q-my-rakt1000-exiao_nrf52-device-seems-to-be-corrupted-how-do-i-wipe-it-clean-to-start-fresh)
|
||||
- [6.8. Q: WebFlasher fails on Linux with failed to open](#67-q-webflasher-fails-on-linux-with-failed-to-open)
|
||||
- [6.5. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code?](#65-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code)
|
||||
- [6.6. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection.](#66-q-my-heltec-v3-keeps-disconnecting-from-my-smartphone--it-cant-hold-a-solid-bluetooth-connection)
|
||||
- [6.7. Q: My RAK/T1000-E/xiao\_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh?](#67-q-my-rakt1000-exiao_nrf52-device-seems-to-be-corrupted-how-do-i-wipe-it-clean-to-start-fresh)
|
||||
- [6.8. Q: WebFlasher fails on Linux with failed to open](#68-q-webflasher-fails-on-linux-with-failed-to-open)
|
||||
- [7. Other Questions:](#7-other-questions)
|
||||
- [7.1. Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app?](#71-q-how-to-update-nrf-rak-t114-seed-xiao-repeater-and-room-server-firmware-over-the-air-using-the-new-simpler-dfu-app)
|
||||
- [7.2. Q: How to update ESP32-based devices over the air?](#72-q-how-to-update-esp32-based-devices-over-the-air)
|
||||
- [7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?](#73-q-is-there-a-way-to-lower-the-chance-of-a-failed-ota-device-firmware-update-dfu)
|
||||
- [7.4. Q: are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available)
|
||||
- [7.5. Q: What is the format of a contact or channel QR code?](#75-q-what-is-the-format-of-a-contact-or-channel-qr-code)
|
||||
- [7.6. Q: How do I connect to the companion via WIFI, e.g. using a heltec v3?](#76-q-how-do-i-connect-to-the-comnpanion-via-wifi-eg-using-a-heltec-v3)
|
||||
- [7.6. Q: How do I connect to the companion via WIFI, e.g. using a heltec v3?](#76-q-how-do-i-connect-to-the-companion-via-wifi-eg-using-a-heltec-v3)
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
@@ -91,7 +88,7 @@ MeshCore is free and open source:
|
||||
* The T-Deck firmware is developed by Scott at Ripple Radios, the creator of MeshCore, is also free to flash on your devices and use
|
||||
|
||||
|
||||
Some more advanced, but optional features are available on T-Deck if you register your device for a key to unlock. On the MeshCore smartphone clients for Android and iOS/iPadOS, you can unlock the wait timer for repeater and room server remote management over RF feature.
|
||||
Some more advanced, but optional features are available on T-Deck if you register your device for a key to unlock. On the MeshCore smartphone clients for Android and iOS/iPadOS, you can unlock the wait timer for repeater and room server remote management over RF feature.
|
||||
|
||||
These features are completely optional and aren't needed for the core messaging experience. They're like super bonus features and to help the developers continue to work on these amazing features, they may charge a small fee for an unlock code to utilise the advanced features.
|
||||
|
||||
@@ -105,7 +102,7 @@ Anyone is able to build anything they like on top of MeshCore without paying any
|
||||
MeshCore Firmware GitHub: https://github.com/ripplebiz/MeshCore
|
||||
|
||||
NOTE: Andy Kirby has a very useful [intro video](https://www.youtube.com/watch?v=t1qne8uJBAc) for beginners.
|
||||
|
||||
|
||||
|
||||
You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server).
|
||||
|
||||
@@ -114,7 +111,7 @@ MeshCore is available on a variety of 433MHz, 868MHz and 915MHz LoRa devices. Fo
|
||||
|
||||
For an up-to-date list of supported devices, please go to https://flasher.meshcore.co.uk/
|
||||
|
||||
To use MeshCore without using a phone as the client interface, you can run MeshCore on a LiLygo's T-Deck, T-Deck Plus, T-Pager, T-Watch, or T-Display Pro. MeshCore Ultra firmware running on these devices are a complete off-grid secure communication solution.
|
||||
To use MeshCore without using a phone as the client interface, you can run MeshCore on a LiLygo's T-Deck, T-Deck Plus, T-Pager, T-Watch, or T-Display Pro. MeshCore Ultra firmware running on these devices are a complete off-grid secure communication solution.
|
||||
|
||||
#### 1.2.2. Firmware
|
||||
MeshCore has four firmware types that are not available on other LoRa systems. MeshCore has the following:
|
||||
@@ -122,30 +119,30 @@ MeshCore has four firmware types that are not available on other LoRa systems. M
|
||||
#### 1.2.3. Companion Radio Firmware
|
||||
Companion radios are for connecting to the Android app or web app as a messenger client. There are two different companion radio firmware versions:
|
||||
|
||||
1. **BLE Companion**
|
||||
BLE Companion firmware runs on a supported LoRa device and connects to a smart device running the Android or iOS MeshCore client over BLE
|
||||
1. **BLE Companion**
|
||||
BLE Companion firmware runs on a supported LoRa device and connects to a smart device running the Android or iOS MeshCore client over BLE
|
||||
<https://meshcore.co.uk/apps.html>
|
||||
|
||||
2. **USB Serial Companion**
|
||||
USB Serial Companion firmware runs on a supported LoRa device and connects to a smart device or a computer over USB Serial running the MeshCore web client
|
||||
<https://meshcore.liamcottle.net/#/>
|
||||
2. **USB Serial Companion**
|
||||
USB Serial Companion firmware runs on a supported LoRa device and connects to a smart device or a computer over USB Serial running the MeshCore web client
|
||||
<https://meshcore.liamcottle.net/#/>
|
||||
<https://client.meshcore.co.uk/tabs/devices>
|
||||
|
||||
#### 1.2.4. Repeater
|
||||
Repeaters are used to extend the range of a MeshCore network. Repeater firmware runs on the same devices that run client firmware. A repeater's job is to forward MeshCore packets to the destination device. It does **not** forward or retransmit every packet it receives, unlike other LoRa mesh systems.
|
||||
Repeaters are used to extend the range of a MeshCore network. Repeater firmware runs on the same devices that run client firmware. A repeater's job is to forward MeshCore packets to the destination device. It does **not** forward or retransmit every packet it receives, unlike other LoRa mesh systems.
|
||||
|
||||
A repeater can be remotely administered using a T-Deck running the MeshCore firmware with remote administration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app.
|
||||
|
||||
#### 1.2.5. Room Server
|
||||
A room server is a simple BBS server for sharing posts. T-Deck devices running MeshCore firmware or a BLE Companion client connected to a smartphone running the MeshCore app can connect to a room server.
|
||||
A room server is a simple BBS server for sharing posts. T-Deck devices running MeshCore firmware or a BLE Companion client connected to a smartphone running the MeshCore app can connect to a room server.
|
||||
|
||||
Room servers store message history on them and push the stored messages to users. Room servers allow roaming users to come back later and retrieve message history. With channels, messages are either received when it's sent, or not received and missed if the channel user is out of range. Room servers are different and more like email servers where you can come back later and get your emails from your mail server.
|
||||
|
||||
A room server can be remotely administered using a T-Deck running the MeshCore firmware with remote administration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app.
|
||||
A room server can be remotely administered using a T-Deck running the MeshCore firmware with remote administration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app.
|
||||
|
||||
When a client logs into a room server, the client will receive the previously 32 unseen messages.
|
||||
|
||||
Although room server can also repeat with the command line command `set repeat on`, it is not recommended nor encouraged. A room server with repeat set to `on` lacks the full set of repeater and remote administration features that are only available in the repeater firmware.
|
||||
Although room server can also repeat with the command line command `set repeat on`, it is not recommended nor encouraged. A room server with repeat set to `on` lacks the full set of repeater and remote administration features that are only available in the repeater firmware.
|
||||
|
||||
The recommendation is to run repeater and room server on separate devices for the best experience.
|
||||
|
||||
@@ -168,37 +165,32 @@ After you flashed the latest firmware onto your repeater device, keep the device
|
||||
|
||||
The repeater and room server CLI reference is here: https://github.com/meshcore-dev/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference
|
||||
|
||||
If you have more supported devices, you can use your additional devices with the room server firmware.
|
||||
If you have more supported devices, you can use your additional devices with the room server firmware.
|
||||
|
||||
### 2.2. Q: Does MeshCore cost any money?
|
||||
|
||||
**A:** All radio firmware versions (e.g. for Heltec V3, RAK, T-1000E, etc) are free and open source developed by Scott at Ripple Radios.
|
||||
**A:** All radio firmware versions (e.g. for Heltec V3, RAK, T-1000E, etc) are free and open source developed by Scott at Ripple Radios.
|
||||
|
||||
The native Android and iOS client uses the freemium model and is developed by Liam Cottle, developer of meshtastic map at [meshtastic.liamcottle.net](https://meshtastic.liamcottle.net) on [GitHub](https://github.com/liamcottle/meshtastic-map) and [reticulum-meshchat on github](https://github.com/liamcottle/reticulum-meshchat).
|
||||
The native Android and iOS client uses the freemium model and is developed by Liam Cottle, developer of meshtastic map at [meshtastic.liamcottle.net](https://meshtastic.liamcottle.net) on [GitHub](https://github.com/liamcottle/meshtastic-map) and [reticulum-meshchat on github](https://github.com/liamcottle/reticulum-meshchat).
|
||||
|
||||
The T-Deck firmware is free to download and most features are available without cost. To support the firmware developer, you can pay for a registration key to unlock your T-Deck for deeper map zoom and remote server administration over RF using the T-Deck. You do not need to pay for the registration to use your T-Deck for direct messaging and connecting to repeaters and room servers.
|
||||
The T-Deck firmware is free to download and most features are available without cost. To support the firmware developer, you can pay for a registration key to unlock your T-Deck for deeper map zoom and remote server administration over RF using the T-Deck. You do not need to pay for the registration to use your T-Deck for direct messaging and connecting to repeaters and room servers.
|
||||
|
||||
|
||||
### 2.3. Q: What frequencies are supported by MeshCore?
|
||||
**A:** It supports the 868MHz range in the UK/EU and the 915MHz range in New Zealand, Australia, and the USA. Countries and regions in these two frequency ranges are also supported. The firmware and client allow users to set their preferred frequency.
|
||||
- Australia and New Zealand are on **915.8MHz**
|
||||
- UK and EU are on **869.525MHz**
|
||||
- Canada and USA are on **910.525MHz**
|
||||
- For other regions and countries, please check your local LoRa frequency
|
||||
**A:** It supports the 868MHz range in the UK/EU and the 915MHz range in New Zealand, Australia, and the USA. Countries and regions in these two frequency ranges are also supported.
|
||||
|
||||
In UK and EU, 867.5MHz is not allowed to use 250kHz bandwidth and it only allows 2.5% duty cycle for clients. 869.525Mhz allows an airtime of 10%, 250KHz bandwidth, and a higher EIRP, therefore MeshCore nodes can send more often and with more power. That is why this frequency is chosen for UK and EU. This is also why Meshtastic also uses this frequency.
|
||||
Use the smartphone client or the repeater setup feature on there web flasher to set your radios' RF settings by choosing the preset for your regions.
|
||||
|
||||
[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1356540643853209641)
|
||||
Recently, as of October 2025, many regions have moved to the "narrow" setting, aka using BW62.5 and a lower SF number (instead of the original SF11). For example, USA/Canada (Recommended) preset is 910.525MHz, SF7, BW62.5, CR5.
|
||||
|
||||
After extensive testing, many regions have switched or about to switch over to BW62.5 and SF7, 8, or 9. Narrower bandwidth setting and lower SF setting allow MeshCore's radio signals to fit between interference in the ISM band, provide for a lower noise floor, better SNR, and faster transmissions.
|
||||
|
||||
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://discord.gg/cYtQNYCCRK) to let Liam Cottle know.
|
||||
|
||||
the rest of the radio settings are the same for all frequencies:
|
||||
- Spread Factor (SF): 11
|
||||
- Coding Rate (CR): 5
|
||||
- Bandwidth (BW): 250.00
|
||||
|
||||
(Originally MeshCore started with SF 10. recently (as of late April 2025) the community has advocated SF 11 also a viable option for longer range but a little slower transmission. Currently there are MeshCore meshes with SF 10 and SF 11. Liam Cottle's smartphone app's presets now recommend SF 10 for Australia and SF 11 for all other regions and countries. EU and UK has SF 10 and SF 11 presets. Work with your local meshers on deciding with SF number is best for your use cases. In the future, there may be bridge nodes that can bridge SF 10 and SF 11 (or even different frequencies) traffic.)
|
||||
|
||||
### 2.4. Q: What is an "advert" in MeshCore?
|
||||
**A:**
|
||||
**A:**
|
||||
Advert means to advertise yourself on the network. In Reticulum terms it would be to announce. In Meshtastic terms it would be the node sending its node info.
|
||||
|
||||
MeshCore allows you to manually broadcast your name, position and public encryption key, which is also signed to prevent spoofing. When you click the advert button, it broadcasts that data over LoRa. MeshCore calls that an Advert. There's two ways to advert, "zero hop" and "flood".
|
||||
@@ -214,7 +206,7 @@ As of Aug 20 2025, a pending PR on github will change the flood advert to 12 hou
|
||||
|
||||
### 2.5. Q: Is there a hop limit?
|
||||
|
||||
**A:** Internally the firmware has maximum limit of 64 hops. In real world settings it will be difficult to get close to the limit due to the environments and timing as packets travel further and further. We want to hear how far your MeshCore conversations go.
|
||||
**A:** Internally the firmware has maximum limit of 64 hops. In real world settings it will be difficult to get close to the limit due to the environments and timing as packets travel further and further. We want to hear how far your MeshCore conversations go.
|
||||
|
||||
|
||||
---
|
||||
@@ -224,14 +216,14 @@ As of Aug 20 2025, a pending PR on github will change the flood advert to 12 hou
|
||||
|
||||
### 3.1. Q: How do you configure a repeater or a room server?
|
||||
|
||||
**A:** - When MeshCore is flashed onto a LoRa device is for the first time, it is necessary to set the server device's frequency to make it utilize the frequency that is legal in your country or region.
|
||||
**A:** - When MeshCore is flashed onto a LoRa device is for the first time, it is necessary to set the server device's frequency to make it utilize the frequency that is legal in your country or region.
|
||||
|
||||
Repeater or room server can be administered with one of the options below:
|
||||
|
||||
|
||||
- After a repeater or room server firmware is flashed on to a LoRa device, go to <https://config.meshcore.dev> and use the web user interface to connect to the LoRa device via USB serial. From there you can set the name of the server, its frequency and other related settings, location, passwords etc.
|
||||
|
||||

|
||||
|
||||
|
||||
- Connect the server device using a USB cable to a computer running Chrome on https://flasher.meshcore.co.uk/, then use the `console` feature to connect to the device
|
||||
|
||||
- Use a MeshCore smartphone clients to remotely administer servers via LoRa.
|
||||
@@ -240,10 +232,10 @@ Repeater or room server can be administered with one of the options below:
|
||||
|
||||
<https://buymeacoffee.com/ripplebiz/e/249834>
|
||||
|
||||
|
||||
|
||||
|
||||
### 3.2. Q: Do I need to set the location for a repeater?
|
||||
**A:** With location set for a repeater, it can show up on a MeshCore map in the future. Set location with the following commands:
|
||||
**A:** While not required, with location set for a repeater it will show up on the MeshCore map in the future. Set location with the following command:
|
||||
|
||||
`set lat <GPS Lat> set long <GPS Lon>`
|
||||
|
||||
@@ -270,14 +262,14 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo
|
||||
**A:** Yes, it is available on https://buymeacoffee.com/ripplebiz/ultra-v7-7-guide-meshcore-users
|
||||
|
||||
### 4.2. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode?
|
||||
**A:**
|
||||
1. Device off
|
||||
2. Connect USB cable to device
|
||||
3. Hold down trackball (keep holding)
|
||||
4. Turn on device
|
||||
5. Hear USB connection sound
|
||||
6. Release trackball
|
||||
7. T-Deck in DFU mode now
|
||||
**A:**
|
||||
1. Device off
|
||||
2. Connect USB cable to device
|
||||
3. Hold down trackball (keep holding)
|
||||
4. Turn on device
|
||||
5. Hear USB connection sound
|
||||
6. Release trackball
|
||||
7. T-Deck in DFU mode now
|
||||
8. At this point you can begin flashing using <https://flasher.meshcore.co.uk/>
|
||||
|
||||
### 4.3. Q: Why is my T-Deck Plus not getting any satellite lock?
|
||||
@@ -294,8 +286,8 @@ GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-De
|
||||
**A:** Users have had no issues using 16GB or 32GB SD cards. Format the SD card to **FAT32**.
|
||||
|
||||
### 4.6. Q: what is the public key for the default public channel?
|
||||
**A:**
|
||||
T-Deck uses the same key the smartphone apps use but in base64
|
||||
**A:**
|
||||
T-Deck uses the same key the smartphone apps use but in base64
|
||||
`izOH6cXN6mrJ5e26oRXNcg==`
|
||||
The third character is the capital letter 'O', not zero `0`
|
||||
|
||||
@@ -305,24 +297,24 @@ The smartphone app key is in hex:
|
||||
[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354194409213792388)
|
||||
|
||||
### 4.7. Q: How do I get maps on T-Deck?
|
||||
**A:** You need map tiles. You can get pre-downloaded map tiles here (a good way to support development):
|
||||
- <https://buymeacoffee.com/ripplebiz/e/342543> (Europe)
|
||||
**A:** You need map tiles. You can get pre-downloaded map tiles here (a good way to support development):
|
||||
- <https://buymeacoffee.com/ripplebiz/e/342543> (Europe)
|
||||
- <https://buymeacoffee.com/ripplebiz/e/342542> (US)
|
||||
|
||||
Another way to download map tiles is to use this Python script to get the tiles in the areas you want:
|
||||
<https://github.com/fistulareffigy/MTD-Script>
|
||||
Another way to download map tiles is to use this Python script to get the tiles in the areas you want:
|
||||
<https://github.com/fistulareffigy/MTD-Script>
|
||||
|
||||
There is also a modified script that adds additional error handling and parallel downloads:
|
||||
<https://discord.com/channels/826570251612323860/1330643963501351004/1338775811548905572>
|
||||
There is also a modified script that adds additional error handling and parallel downloads:
|
||||
<https://discord.com/channels/826570251612323860/1330643963501351004/1338775811548905572>
|
||||
|
||||
UK map tiles are available separately from Andy Kirby on his discord server:
|
||||
UK map tiles are available separately from Andy Kirby on his discord server:
|
||||
<https://discord.com/channels/826570251612323860/1330643963501351004/1331346597367386224>
|
||||
|
||||
### 4.8. Q: Where do the map tiles go?
|
||||
Once you have the tiles downloaded, copy the `\tiles` folder to the root of your T-Deck's SD card.
|
||||
|
||||
### 4.9. Q: How to unlock deeper map zoom and server management features on T-Deck?
|
||||
**A:** You can download, install, and use the T-Deck firmware for free, but it has some features (map zoom, server administration) that are enabled if you purchase an unlock code for \$10 per T-Deck device.
|
||||
**A:** You can download, install, and use the T-Deck firmware for free, but it has some features (map zoom, server administration) that are enabled if you purchase an unlock code for \$10 per T-Deck device.
|
||||
Unlock page: <https://buymeacoffee.com/ripplebiz/e/249834>
|
||||
|
||||
### 4.10. Q: How to decipher the diagnostics screen on T-Deck?
|
||||
@@ -330,17 +322,17 @@ Unlock page: <https://buymeacoffee.com/ripplebiz/e/249834>
|
||||
**A: ** Space is tight on T-Deck's screen, so the information is a bit cryptic. The format is :
|
||||
`{hops} l:{packet-length}({payload-len}) t:{packet-type} snr:{n} rssi:{n}`
|
||||
|
||||
See here for packet-type:
|
||||
See here for packet-type:
|
||||
https://github.com/meshcore-dev/MeshCore/blob/main/src/Packet.h#L19
|
||||
|
||||
|
||||
#define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||
#define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||
#define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
|
||||
#define PAYLOAD_TYPE_ACK 0x03 // a simple ack #define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity
|
||||
#define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
|
||||
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
|
||||
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
|
||||
|
||||
#define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||
#define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||
#define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
|
||||
#define PAYLOAD_TYPE_ACK 0x03 // a simple ack #define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity
|
||||
#define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
|
||||
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
|
||||
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
|
||||
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
|
||||
|
||||
[Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350611321040932966)
|
||||
@@ -370,14 +362,30 @@ https://github.com/meshcore-dev/MeshCore/blob/main/src/Packet.h#L19
|
||||
|
||||
### 5.1. Q: What are BW, SF, and CR?
|
||||
|
||||
**A:**
|
||||
**A:**
|
||||
|
||||
**BW is bandwidth** - width of frequency spectrum that is used for transmission
|
||||
|
||||
**SF is spreading factor** - how much should the communication spread in time
|
||||
|
||||
**CR is coding rate** - https://www.thethingsnetwork.org/docs/lorawan/fec-and-code-rate/
|
||||
Making the bandwidth 2x wider (from BW125 to BW250) allows you to send 2x more bytes in the same time. Making the spreading factor 1 step lower (from SF10 to SF9) allows you to send 2x more bytes in the same time.
|
||||
**CR is coding rate** - from: https://www.thethingsnetwork.org/docs/lorawan/fec-and-code-rate/
|
||||
|
||||
TL;DR: default CR to 5 for good stable links. If it is not a solid link and is intermittent, change to CR to 7 or 8.
|
||||
|
||||
Forward Error Correction is a process of adding redundant bits to the data to be transmitted. During the transmission, data may get corrupted by interference (changes from 0 to 1 / 1 to 0). These error correction bits are used at the receivers for restoring corrupted bits.
|
||||
|
||||
The Code Rate of a forward error correction expresses the proportion of bits in a data stream that actually carry useful information.
|
||||
|
||||
There are 4 code rates used in LoRaWAN:
|
||||
|
||||
4/5
|
||||
4/6
|
||||
5/7
|
||||
4/8
|
||||
|
||||
For example, if the code rate is 5/7, for every 5 bits of useful information, the coder generates a total of 7 bits of data, of which 2 bits are redundant.
|
||||
|
||||
Making the bandwidth 2x wider (from BW125 to BW250) allows you to send 2x more bytes in the same time. Making the spreading factor 1 step lower (from SF10 to SF9) allows you to send 2x more bytes in the same time.
|
||||
|
||||
Lowering the spreading factor makes it more difficult for the gateway to receive a transmission, as it will be more sensitive to noise. You could compare this to two people taking in a noisy place (a bar for example). If you’re far from each other, you have to talk slow (SF10), but if you’re close, you can talk faster (SF7)
|
||||
|
||||
@@ -385,14 +393,14 @@ So, it's balancing act between speed of the transmission and resistance to noise
|
||||
things network is mainly focused on LoRaWAN, but the LoRa low-level stuff still checks out for any LoRa project
|
||||
|
||||
### 5.2. Q: Do MeshCore clients repeat?
|
||||
**A:** No, MeshCore clients do not repeat. This is the core of MeshCore's messaging-first design. This is to avoid devices flooding the air ware and create endless collisions, so messages sent aren't received.
|
||||
In MeshCore, only repeaters and room server with `set repeat on` repeat.
|
||||
**A:** No, MeshCore clients do not repeat. This is the core of MeshCore's messaging-first design. This is to avoid devices flooding the air ware and create endless collisions, so messages sent aren't received.
|
||||
In MeshCore, only repeaters and room server with `set repeat on` repeat.
|
||||
|
||||
### 5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone?
|
||||
|
||||
**A:** If you used to reach a node through a repeater and the repeater is no longer reachable, the client will send the message using the existing (but now broken) known path, the message will fail after 3 retries, and the app will reset the path and send the message as flood on the last retry by default. This can be turned off in settings. If the destination is reachable directly or through another repeater, the new path will be used going forward. Or you can set the path manually if you know a specific repeater to use to reach that destination.
|
||||
|
||||
In the case if users are moving around frequently, and the paths are breaking, they just see the phone client retries and revert to flood to attempt to re-establish a path.
|
||||
In the case if users are moving around frequently, and the paths are breaking, they just see the phone client retries and revert to flood to attempt to re-establish a path.
|
||||
|
||||
### 5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?
|
||||
|
||||
@@ -411,14 +419,14 @@ Routes are stored in sender's contact list. When you send a message the first t
|
||||
**A:** The smartphone app key is in hex:
|
||||
` 8b3387e9c5cdea6ac9e5edbaa115cd72`
|
||||
|
||||
T-Deck uses the same key but in base64
|
||||
T-Deck uses the same key but in base64
|
||||
`izOH6cXN6mrJ5e26oRXNcg==`
|
||||
The third character is the capital letter 'O', not zero `0`
|
||||
[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354194409213792388)
|
||||
|
||||
### 5.7. Q: Is MeshCore open source?
|
||||
**A:** Most of the firmware is freely available. Everything is open source except the T-Deck firmware and Liam's native mobile apps.
|
||||
- Firmware repo: https://github.com/meshcore-dev/MeshCore
|
||||
**A:** Most of the firmware is freely available. Everything is open source except the T-Deck firmware and Liam's native mobile apps.
|
||||
- Firmware repo: https://github.com/meshcore-dev/MeshCore
|
||||
|
||||
### 5.8. Q: How can I support MeshCore?
|
||||
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://discord.gg/BMwCtwHj5V). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
|
||||
@@ -428,7 +436,7 @@ Support Liam Cottle's smartphone client development by unlocking the server admi
|
||||
Support Rastislav Vysoky (recrof)'s flasher web site and the map web site development through [PayPal](https://www.paypal.com/donate/?business=DREHF5HM265ES&no_recurring=0&item_name=If+you+enjoy+my+work%2C+you+can+support+me+here%3A¤cy_code=EUR) or [Revolut](https://revolut.me/recrof)
|
||||
|
||||
### 5.9. Q: How do I build MeshCore firmware from source?
|
||||
**A:** See instructions here:
|
||||
**A:** See instructions here:
|
||||
https://discord.com/channels/826570251612323860/1330643963501351004/1341826372120608769
|
||||
|
||||
Build instructions for MeshCore:
|
||||
@@ -448,7 +456,7 @@ Then it should be the same for all platforms:
|
||||
python3 -m venv meshcore
|
||||
cd meshcore && source bin/activate
|
||||
pip install -U platformio
|
||||
git clone https://github.com/ripplebiz/MeshCore.git
|
||||
git clone https://github.com/ripplebiz/MeshCore.git
|
||||
cd MeshCore
|
||||
```
|
||||
open platformio.ini and in `[arduino_base]` edit the `LORA_FREQ=867.5`
|
||||
@@ -458,8 +466,8 @@ pio run -e RAK_4631_Repeater
|
||||
```
|
||||
then you'll find `firmware.zip` in `.pio/build/RAK_4631_Repeater`
|
||||
|
||||
Andy also has a video on how to build using VS Code:
|
||||
*How to build and flash Meshcore repeater firmware | Heltec V3*
|
||||
Andy also has a video on how to build using VS Code:
|
||||
*How to build and flash Meshcore repeater firmware | Heltec V3*
|
||||
<https://www.youtube.com/watch?v=WJvg6dt13hk> *(Link referenced in the Discord post)*
|
||||
|
||||
### 5.10. Q: Are there other MeshCore related open source projects?
|
||||
@@ -476,13 +484,13 @@ Meshcore would not be best suited to ATAK because MeshCore:
|
||||
clients do not repeat and therefore you would need a network of repeaters in place
|
||||
will not have a stable path where all clients are constantly moving between repeaters
|
||||
|
||||
MeshCore clients would need to reset path constantly and flood traffic across the network which could lead to lots of collisions with something as chatty as ATAK.
|
||||
MeshCore clients would need to reset path constantly and flood traffic across the network which could lead to lots of collisions with something as chatty as ATAK.
|
||||
|
||||
This could change in the future if MeshCore develops a client firmware that repeats.
|
||||
[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354780032140054659)
|
||||
|
||||
### 5.12. Q: How do I add a node to the [MeshCore Map]([url](https://meshcore.co.uk/map.html))
|
||||
**A:**
|
||||
**A:**
|
||||
|
||||
To add a BLE Companion radio, connect to the BLE Companion radio from the MeshCore smartphone app. In the app, tap the `3 dot` menu icon at the top right corner, then tap `Internet Map`. Tap the `3 dot` menu icon again and choose `Add me to the Map`
|
||||
|
||||
@@ -501,7 +509,7 @@ For ESP-based devices (e.g. Heltec V3) you need:
|
||||
- Download firmware file from flasher.meshcore.co.uk
|
||||
- Go to the web site on a browser, find the section that has the firmware up need
|
||||
- Click the Download button, right click on the file you need, for example,
|
||||
- `Heltec_V3_companion_radio_ble-v1.7.1-165fb33.bin`
|
||||
- `Heltec_V3_companion_radio_ble-v1.7.1-165fb33.bin`
|
||||
- Non-merged bin keeps the existing Bluetooth pairing database
|
||||
- `Heltec_v3_companion_radio_usb-v1.7.1-165fb33-merged.bin`
|
||||
- Merged bin overwrites everything including the bootloader, existing Bluetooth pairing database, but keeps configurations.
|
||||
@@ -520,7 +528,7 @@ For ESP-based devices (e.g. Heltec V3) you need:
|
||||
- `esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x10000 <non-merged_firmware>.bin`
|
||||
- For merged bin:
|
||||
- `esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x00000 <merged_firmware>.bin`
|
||||
|
||||
|
||||
|
||||
|
||||
**Instructions for nRF devices:**
|
||||
@@ -541,24 +549,25 @@ For nRF devices (e.g. RAK, Heltec T114) you need the following:
|
||||
- `pip install adafruit-nrfutil --break-system-packages`
|
||||
- Use this command to flash the nRF device:
|
||||
- `adafruit-nrfutil --verbose dfu serial --package RAK_4631_companion_radio_usb-v1.7.1-165fb33.zip -p /dev/ttyACM0 -b 115200 --singlebank --touch 1200`
|
||||
|
||||
|
||||
|
||||
|
||||
To manage a repeater or room server connected to a Pi over USB serial using shell commands, you need to install `picocom`. To install `picocom`, run the following command:
|
||||
- `sudo apt install picocom`
|
||||
|
||||
To start managing your USB serial-connected device using picocom, use the following command:
|
||||
- `picocom -b 115200 /dev/ttyUSB0 --imap lfcrlf`
|
||||
|
||||
From here, reference repeater and room server command line commands on MeshCore github wiki here:
|
||||
From here, reference repeater and room server command line commands on MeshCore github wiki here:
|
||||
- https://github.com/meshcore-dev/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference
|
||||
|
||||
|
||||
|
||||
### 5.14. Q: Are there are projects built around MeshCore?
|
||||
|
||||
**A:** Yes. See the following:
|
||||
|
||||
#### 5.14.1. meshcoremqtt
|
||||
A Python script to send meshore debug and packet capture data to MQTT for analysis
|
||||
A Python script to send meshcore debug and packet capture data to MQTT for analysis. Cisien's version is a fork of Andrew-a-g's and is being used to to collect data for https://map.w0z.is/messages and https://analyzer.letsme.sh/
|
||||
https://github.com/Cisien/meshcoretomqtt
|
||||
https://github.com/Andrew-a-g/meshcoretomqtt
|
||||
|
||||
#### 5.14.2. MeshCore for Home Assistant
|
||||
@@ -569,7 +578,7 @@ https://github.com/awolden/meshcore-ha
|
||||
Bindings to access your MeshCore companion radio nodes in python.
|
||||
https://github.com/fdlamotte/meshcore_py
|
||||
|
||||
#### 5.14.4. meshcore-cli
|
||||
#### 5.14.4. meshcore-cli
|
||||
CLI interface to MeshCore companion radio over BLE, TCP, or serial. Uses Python MeshCore above.
|
||||
https://github.com/fdlamotte/meshcore-cli
|
||||
|
||||
@@ -577,15 +586,19 @@ CLI interface to MeshCore companion radio over BLE, TCP, or serial. Uses Python
|
||||
A JavaScript library for interacting with a MeshCore device running the companion radio firmware
|
||||
https://github.com/liamcottle/meshcore.js
|
||||
|
||||
#### 5.14.6. pyMC_core
|
||||
pyMC_Core is a Python port of MeshCore, designed for Raspberry Pi and similar hardware, it talks to LoRa modules over SPI.
|
||||
https://github.com/rightup/pyMC_core
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
### 6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago.
|
||||
### 6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed.
|
||||
**A:**
|
||||
- If your client is a T-Deck, it may not have its time set (no GPS installed, no GPS lock, or wrong GPS baud rate).
|
||||
- If you are using the Android or iOS client, the other client, repeater, or room server may have the wrong time.
|
||||
**A:**
|
||||
- If your client is a T-Deck, it may not have its time set (no GPS installed, no GPS lock, or wrong GPS baud rate).
|
||||
- If you are using the Android or iOS client, the other client, repeater, or room server may have the wrong time.
|
||||
|
||||
You can get the epoch time on <https://www.epochconverter.com/> and use it to set your T-Deck clock. For a repeater and room server, the admin can use a T-Deck to remotely set their clock (clock sync), or use the `time` command in the USB serial console with the server device connected.
|
||||
|
||||
@@ -606,23 +619,23 @@ You can get the epoch time on <https://www.epochconverter.com/> and use it to se
|
||||
|
||||
### 6.7. Q: My RAK/T1000-E/xiao_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh?
|
||||
|
||||
**A:**
|
||||
**A:**
|
||||
1. Connect USB-C cable to your device, per your device's instruction, get it to flash mode:
|
||||
- For RAK, click the reset button **TWICE**
|
||||
- For T1000-e, quickly disconnect and reconnect the magnetic side of the cable from the device **TWICE**
|
||||
- For Heltec T114, click the reset button **TWICE** (the bottom button)
|
||||
- For Xiao nRF52, click the reset button once. If that doesn't work, quickly double click the reset button twice. If that doesn't work, disconnection the board from your PC and reconnect again ([seeed studio wiki](https://wiki.seeedstudio.com/XIAO_BLE/#access-the-swd-pins-for-debugging-and-reflashing-bootloader))
|
||||
5. A new folder will appear on your computer's desktop
|
||||
6. Download the `flash_erase*.uf2` file for your device on flasher.meshcore.co.uk
|
||||
6. Download the `flash_erase*.uf2` file for your device on flasher.meshcore.co.uk
|
||||
- RAK WisBlock and Heltec T114: `Flash_erase-nRF32_softdevice_v6.uf2`
|
||||
- Seeed Studio Xiao nRF52 WIO: `Flash_erase-nRF52_softdevice_v7.uf2`
|
||||
8. drag and drop the uf2 file for your device to the root of the new folder
|
||||
9. Wait for the copy to complete. You might get an error dialog, you can ignore it
|
||||
10. Go to https://flasher.meshcore.co.uk/, click `Console` and select the serial port for your connected device
|
||||
10. Go to https://flasher.meshcore.co.uk/, click `Console` and select the serial port for your connected device
|
||||
11. In the console, press enter. Your flash should now be erased
|
||||
12. You may now flash the latest MeshCore firmware onto your device
|
||||
|
||||
Separately, starting in firmware version 1.7.0, there is a CLI Rescue mode. If your device has a user button (e.g. some RAK, T114), you can activate the rescue mode by hold down the user button of the device within 8 seconds of boot. Then you can use the 'Console' on flasher.meshcore.co.uk
|
||||
Separately, starting in firmware version 1.7.0, there is a CLI Rescue mode. If your device has a user button (e.g. some RAK, T114), you can activate the rescue mode by hold down the user button of the device within 8 seconds of boot. Then you can use the 'Console' on flasher.meshcore.co.uk
|
||||
|
||||
### 6.8. Q: WebFlasher fails on Linux with failed to open
|
||||
|
||||
@@ -645,12 +658,12 @@ Allow the browser user on it:
|
||||
4. Go to the Command Line tab, type `start ota` and hit enter.
|
||||
5. you should see `OK` to confirm the repeater device is now in OTA mode
|
||||
6. Run the DFU app,tab `Settings` on the top right corner
|
||||
7. Enable `Packets receipt notifications`, and change `Number of Packets` to 10 for RAK, 8 for T114. 8 also works for RAK.
|
||||
7. Enable `Packets receipt notifications`, and change `Number of Packets` to 10 for RAK, 8 for T114. 8 also works for RAK.
|
||||
9. Select the firmware zip file you downloaded
|
||||
10. Select the device you want to update. If the device you want to update is not on the list, try enabling`OTA` on the device again
|
||||
11. If the device is not found, enable `Force Scanning` in the DFU app
|
||||
12. Tab the `Upload` to begin OTA update
|
||||
13. If it fails, try turning off and on Bluetooth on your phone. If that doesn't work, try rebooting your phone.
|
||||
13. If it fails, try turning off and on Bluetooth on your phone. If that doesn't work, try rebooting your phone.
|
||||
14. Wait for the update to complete. It can take a few minutes.
|
||||
|
||||
|
||||
@@ -662,13 +675,13 @@ Allow the browser user on it:
|
||||
4. Go to the Command Line tab, type `start ota` and hit enter.
|
||||
5. you should see `OK` to confirm the repeater device is now in OTA mode
|
||||
6. The command `start ota` on an ESP32-based device starts a wifi hotspot named `MeshCore OTA`
|
||||
7. From your phone or computer connect to the 'MeshCore OTA' hotspot
|
||||
7. From your phone or computer connect to the 'MeshCore OTA' hotspot
|
||||
8. From a browser, go to http://192.168.4.1/update and upload the non-merged bin from the flasher
|
||||
|
||||
|
||||
### 7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?
|
||||
|
||||
**A:** Yes, developer `che aporeps` has an enhanced OTA DFU bootloader for nRF52 based devices. With this bootloader, if it detects that the application firmware is invalid, it falls back to OTA DFU mode so you can attempt to flash again to recover. This bootloader has other changes to make the OTA DFU process more fault tolerant.
|
||||
**A:** Yes, developer `che aporeps` has an enhanced OTA DFU bootloader for nRF52 based devices. With this bootloader, if it detects that the application firmware is invalid, it falls back to OTA DFU mode so you can attempt to flash again to recover. This bootloader has other changes to make the OTA DFU process more fault tolerant.
|
||||
|
||||
Refer to https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX for the latest information.
|
||||
|
||||
@@ -680,7 +693,7 @@ Currently, the following boards are supported:
|
||||
|
||||
### 7.4. Q: are the MeshCore logo and font available?
|
||||
|
||||
**A:** Yes, it is on the MeshCore github repo here:
|
||||
**A:** Yes, it is on the MeshCore github repo here:
|
||||
https://github.com/meshcore-dev/MeshCore/tree/main/logo
|
||||
|
||||
### 7.5. Q: What is the format of a contact or channel QR code?
|
||||
@@ -699,7 +712,7 @@ where `&type` is:
|
||||
`sensor = 4`
|
||||
|
||||
### 7.6. Q: How do I connect to the companion via WIFI, e.g. using a heltec v3?
|
||||
**A:**
|
||||
**A:**
|
||||
WiFi firmware requires you to compile it yourself, as you need to set the wifi ssid and password.
|
||||
Edit WIFI_SSID and WIFI_PWD in `./variants/heltec_v3/platformio.ini` and then flash it to your device.
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ bit 0 means the lowest bit (1s place)
|
||||
| `0x08` | `PAYLOAD_TYPE_PATH` | Returned path. |
|
||||
| `0x09` | `PAYLOAD_TYPE_TRACE` | trace a path, collecting SNI for each hop. |
|
||||
| `0x0A` | `PAYLOAD_TYPE_MULTIPART` | packet is part of a sequence of packets. |
|
||||
| `0x0B` | `PAYLOAD_TYPE_CONTROL` | control packet data (unencrypted) |
|
||||
| `0x0C` | . | reserved |
|
||||
| `0x0D` | . | reserved |
|
||||
| `0x0E` | . | reserved |
|
||||
| `0x0F` | `PAYLOAD_TYPE_RAW_CUSTOM` | Custom packet (raw bytes, custom encryption). |
|
||||
|
||||
## Payload Version Values
|
||||
|
||||
@@ -11,6 +11,7 @@ Inside of each [meshcore packet](./packet_structure.md) is a payload, identified
|
||||
* Group text message (unverified).
|
||||
* Group datagram (unverified).
|
||||
* Multi-part packet
|
||||
* Control data packet
|
||||
* Custom packet (raw bytes, custom encryption).
|
||||
|
||||
This document defines the structure of each of these payload types.
|
||||
@@ -57,7 +58,7 @@ Appdata Flags
|
||||
|
||||
# Acknowledgement
|
||||
|
||||
An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement will be sent in the "extra" payload (see [Returned Path](#returned-path)) and not as a discrete ackowledgement. CLI commands do not require an acknowledgement, neither discrete nor extra.
|
||||
An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement can be sent in the "extra" payload (see [Returned Path](#returned-path)) instead of as a separate ackowledgement packet. CLI commands do not cause acknowledgement responses, neither discrete nor extra.
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------|--------------|------------------------------------------------------------|
|
||||
@@ -140,13 +141,13 @@ Request data about sensors on the node, including battery level.
|
||||
|
||||
## Plain text message
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|-----------------|-----------------|--------------------------------------------------------------|
|
||||
| timestamp | 4 | send time (unix timestamp) |
|
||||
| flags + attempt | 1 | upper six bits are flags (see below), lower two bits are attempt number (0..3) |
|
||||
| message | rest of payload | the message content, see next table |
|
||||
| Field | Size (bytes) | Description |
|
||||
|--------------------|-----------------|--------------------------------------------------------------|
|
||||
| timestamp | 4 | send time (unix timestamp) |
|
||||
| txt_type + attempt | 1 | upper six bits are txt_type (see below), lower two bits are attempt number (0..3) |
|
||||
| message | rest of payload | the message content, see next table |
|
||||
|
||||
Flags
|
||||
txt_type
|
||||
|
||||
| Value | Description | Message content |
|
||||
|--------|---------------------------|------------------------------------------------------------|
|
||||
@@ -163,13 +164,20 @@ Flags
|
||||
| cipher MAC | 2 | MAC for encrypted data in next field |
|
||||
| ciphertext | rest of payload | encrypted message, see below for details |
|
||||
|
||||
Plaintext message
|
||||
## Room server login
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------------|-----------------|-------------------------------------------------------------------------------|
|
||||
| timestamp | 4 | send time (unix timestamp) |
|
||||
| sync timestamp | 4 | NOTE: room server only! - sender's "sync messages SINCE x" timestamp |
|
||||
| password | rest of message | password for repeater/room |
|
||||
| timestamp | 4 | sender time (unix timestamp) |
|
||||
| sync timestamp | 4 | sender's "sync messages SINCE x" timestamp |
|
||||
| password | rest of message | password for room |
|
||||
|
||||
## Repeater/Sensor login
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------------|-----------------|-------------------------------------------------------------------------------|
|
||||
| timestamp | 4 | sender time (unix timestamp) |
|
||||
| password | rest of message | password for repeater/sensor |
|
||||
|
||||
# Group text message / datagram
|
||||
|
||||
@@ -182,8 +190,32 @@ Plaintext message
|
||||
The plaintext contained in the ciphertext matches the format described in [plain text message](#plain-text-message). Specifically, it consists of a four byte timestamp, a flags byte, and the message. The flags byte will generally be `0x00` because it is a "plain text message". The message will be of the form `<sender name>: <message body>` (eg., `user123: I'm on my way`).
|
||||
|
||||
|
||||
TODO: describe what datagram looks like
|
||||
# Control data
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|--------------|-----------------|--------------------------------------------|
|
||||
| flags | 1 | upper 4 bits is sub_type |
|
||||
| data | rest of payload | typically unencrypted data |
|
||||
|
||||
## DISCOVER_REQ (sub_type)
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|--------------|-----------------|----------------------------------------------|
|
||||
| flags | 1 | 0x8 (upper 4 bits), prefix_only (lowest bit) |
|
||||
| type_filter | 1 | bit for each ADV_TYPE_* |
|
||||
| tag | 4 | randomly generate by sender |
|
||||
| since | 4 | (optional) epoch timestamp (0 by default) |
|
||||
|
||||
## DISCOVER_RESP (sub_type)
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|--------------|-----------------|--------------------------------------------|
|
||||
| flags | 1 | 0x9 (upper 4 bits), node_type (lower 4) |
|
||||
| snr | 1 | signed, SNR*4 |
|
||||
| tag | 4 | reflected back from DISCOVER_REQ |
|
||||
| pubkey | 8 or 32 | node's ID (or prefix) |
|
||||
|
||||
|
||||
# Custom packet
|
||||
|
||||
Custom packets have no defined format.
|
||||
Custom packets have no defined format.
|
||||
|
||||
@@ -197,11 +197,7 @@ void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon)
|
||||
}
|
||||
|
||||
void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& node_lat, double& node_lon) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "r");
|
||||
#else
|
||||
File file = _fs->open(filename);
|
||||
#endif
|
||||
File file = openRead(_fs, filename);
|
||||
if (file) {
|
||||
uint8_t pad[8];
|
||||
|
||||
@@ -262,16 +258,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
}
|
||||
|
||||
void DataStore::loadContacts(DataStoreHost* host) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
if (_getContactsChannelsFS()->exists("/contacts3")) {
|
||||
File file = _getContactsChannelsFS()->open("/contacts3");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
if (_fs->exists("/contacts3")) {
|
||||
File file = _fs->open("/contacts3", "r");
|
||||
#else
|
||||
if (_fs->exists("/contacts3")) {
|
||||
File file = _fs->open("/contacts3", "r", false);
|
||||
#endif
|
||||
File file = openRead(_getContactsChannelsFS(), "/contacts3");
|
||||
if (file) {
|
||||
bool full = false;
|
||||
while (!full) {
|
||||
@@ -299,7 +286,6 @@ void DataStore::loadContacts(DataStoreHost* host) {
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::saveContacts(DataStoreHost* host) {
|
||||
@@ -332,16 +318,7 @@ void DataStore::saveContacts(DataStoreHost* host) {
|
||||
}
|
||||
|
||||
void DataStore::loadChannels(DataStoreHost* host) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
if (_getContactsChannelsFS()->exists("/channels2")) {
|
||||
File file = _getContactsChannelsFS()->open("/channels2");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
if (_fs->exists("/channels2")) {
|
||||
File file = _fs->open("/channels2", "r");
|
||||
#else
|
||||
if (_fs->exists("/channels2")) {
|
||||
File file = _fs->open("/channels2", "r", false);
|
||||
#endif
|
||||
File file = openRead(_getContactsChannelsFS(), "/channels2");
|
||||
if (file) {
|
||||
bool full = false;
|
||||
uint8_t channel_idx = 0;
|
||||
@@ -363,7 +340,6 @@ void DataStore::loadChannels(DataStoreHost* host) {
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::saveChannels(DataStoreHost* host) {
|
||||
@@ -520,7 +496,7 @@ void DataStore::migrateToSecondaryFS() {
|
||||
}
|
||||
|
||||
uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) {
|
||||
File file = _getContactsChannelsFS()->open("/adv_blobs");
|
||||
File file = openRead(_getContactsChannelsFS(), "/adv_blobs");
|
||||
uint8_t len = 0; // 0 = not found
|
||||
if (file) {
|
||||
BlobRec tmp;
|
||||
@@ -583,11 +559,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b
|
||||
sprintf(path, "/bl/%s", fname);
|
||||
|
||||
if (_fs->exists(path)) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File f = _fs->open(path, "r");
|
||||
#else
|
||||
File f = _fs->open(path);
|
||||
#endif
|
||||
File f = openRead(_fs, path);
|
||||
if (f) {
|
||||
int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!!
|
||||
f.close();
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
#define CMD_SEND_BINARY_REQ 50
|
||||
#define CMD_FACTORY_RESET 51
|
||||
#define CMD_SEND_PATH_DISCOVERY_REQ 52
|
||||
#define CMD_SET_FLOOD_SCOPE 54 // v8+
|
||||
#define CMD_SEND_CONTROL_DATA 55 // v8+
|
||||
|
||||
#define RESP_CODE_OK 0
|
||||
#define RESP_CODE_ERR 1
|
||||
@@ -99,6 +101,7 @@
|
||||
#define PUSH_CODE_TELEMETRY_RESPONSE 0x8B
|
||||
#define PUSH_CODE_BINARY_RESPONSE 0x8C
|
||||
#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D
|
||||
#define PUSH_CODE_CONTROL_DATA 0x8E // v8+
|
||||
|
||||
#define ERR_CODE_UNSUPPORTED_CMD 1
|
||||
#define ERR_CODE_NOT_FOUND 2
|
||||
@@ -378,6 +381,35 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
||||
// REVISIT: try to determine which Region (from transport_codes[1]) that Sender is indicating for replies/responses
|
||||
// if unknown, fallback to finding Region from transport_codes[0], the 'scope' used by Sender
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
// TODO: dynamic send_scope, depending on recipient and current 'home' Region
|
||||
if (send_scope.isNull()) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
} else {
|
||||
uint16_t codes[2];
|
||||
codes[0] = send_scope.calcTransportCode(pkt);
|
||||
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
|
||||
sendFlood(pkt, codes, delay_millis);
|
||||
}
|
||||
}
|
||||
void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
// TODO: have per-channel send_scope
|
||||
if (send_scope.isNull()) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
} else {
|
||||
uint16_t codes[2];
|
||||
codes[0] = send_scope.calcTransportCode(pkt);
|
||||
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
|
||||
sendFlood(pkt, codes, delay_millis);
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||
const char *text) {
|
||||
markConnectionActive(from); // in case this is from a server, and we have a connection
|
||||
@@ -596,6 +628,26 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i
|
||||
return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len);
|
||||
}
|
||||
|
||||
void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
||||
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
||||
MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_CONTROL_DATA;
|
||||
out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4);
|
||||
out_frame[i++] = (int8_t)(_radio->getLastRSSI());
|
||||
out_frame[i++] = packet->path_len;
|
||||
memcpy(&out_frame[i], packet->payload, packet->payload_len);
|
||||
i += packet->payload_len;
|
||||
|
||||
if (_serial->isConnected()) {
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onControlDataRecv(), data received while app offline");
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
||||
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
||||
MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||
@@ -663,6 +715,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
sign_data = NULL;
|
||||
dirty_contacts_expiry = 0;
|
||||
memset(advert_paths, 0, sizeof(advert_paths));
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key));
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
@@ -706,8 +759,8 @@ void MyMesh::begin(bool has_display) {
|
||||
_prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f);
|
||||
_prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f);
|
||||
_prefs.freq = constrain(_prefs.freq, 400.0f, 2500.0f);
|
||||
_prefs.bw = constrain(_prefs.bw, 62.5f, 500.0f);
|
||||
_prefs.sf = constrain(_prefs.sf, 7, 12);
|
||||
_prefs.bw = constrain(_prefs.bw, 7.8f, 500.0f);
|
||||
_prefs.sf = constrain(_prefs.sf, 5, 12);
|
||||
_prefs.cr = constrain(_prefs.cr, 5, 8);
|
||||
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER);
|
||||
|
||||
@@ -1485,6 +1538,21 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE && len >= 2 && cmd_frame[1] == 0) {
|
||||
if (len >= 2 + 16) {
|
||||
memcpy(send_scope.key, &cmd_frame[2], sizeof(send_scope.key)); // set curr scope TransportKey
|
||||
} else {
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key)); // set scope to null
|
||||
}
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_SEND_CONTROL_DATA && len >= 2 && (cmd_frame[1] & 0x80) != 0) {
|
||||
auto resp = createControlData(&cmd_frame[1], len - 1);
|
||||
if (resp) {
|
||||
sendZeroHop(resp);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
#include "AbstractUITask.h"
|
||||
|
||||
/*------------ Frame Protocol --------------*/
|
||||
#define FIRMWARE_VER_CODE 7
|
||||
#define FIRMWARE_VER_CODE 8
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "28 Sep 2025"
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.9.0"
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -68,6 +68,7 @@
|
||||
#endif
|
||||
|
||||
#include <helpers/BaseChatMesh.h>
|
||||
#include <helpers/TransportKeyStore.h>
|
||||
|
||||
/* -------------------------------------------------------------------------------------- */
|
||||
|
||||
@@ -106,6 +107,10 @@ protected:
|
||||
int getInterferenceThreshold() const override;
|
||||
int calcRxDelay(float score, uint32_t air_time) const override;
|
||||
uint8_t getExtraAckTransmitCount() const override;
|
||||
bool filterRecvFloodPacket(mesh::Packet* packet) override;
|
||||
|
||||
void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override;
|
||||
void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override;
|
||||
|
||||
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
|
||||
bool isAutoAddEnabled() const override;
|
||||
@@ -128,6 +133,7 @@ protected:
|
||||
uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
|
||||
uint8_t len, uint8_t *reply) override;
|
||||
void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override;
|
||||
void onControlDataRecv(mesh::Packet *packet) override;
|
||||
void onRawDataRecv(mesh::Packet *packet) override;
|
||||
void onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
|
||||
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) override;
|
||||
@@ -191,6 +197,8 @@ private:
|
||||
uint32_t sign_data_len;
|
||||
unsigned long dirty_contacts_expiry;
|
||||
|
||||
TransportKey send_scope;
|
||||
|
||||
uint8_t cmd_frame[MAX_FRAME_SIZE + 1];
|
||||
uint8_t out_frame[MAX_FRAME_SIZE + 1];
|
||||
CayenneLPP telemetry;
|
||||
|
||||
@@ -227,4 +227,5 @@ void loop() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@
|
||||
#define UI_RECENT_LIST_SIZE 4
|
||||
#endif
|
||||
|
||||
#define PRESS_LABEL "long press"
|
||||
#if UI_HAS_JOYSTICK
|
||||
#define PRESS_LABEL "press Enter"
|
||||
#else
|
||||
#define PRESS_LABEL "long press"
|
||||
#endif
|
||||
|
||||
#include "icons.h"
|
||||
|
||||
@@ -75,6 +79,9 @@ class HomeScreen : public UIScreen {
|
||||
RADIO,
|
||||
BLUETOOTH,
|
||||
ADVERT,
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
GPS,
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
SENSORS,
|
||||
#endif
|
||||
@@ -170,7 +177,7 @@ public:
|
||||
|
||||
// curr page indicator
|
||||
int y = 14;
|
||||
int x = display.width() / 2 - 25;
|
||||
int x = display.width() / 2 - 5 * (HomePage::Count-1);
|
||||
for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) {
|
||||
if (i == _page) {
|
||||
display.fillRect(x-1, y-1, 3, 3);
|
||||
@@ -250,6 +257,34 @@ public:
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32);
|
||||
display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL);
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
} else if (_page == HomePage::GPS) {
|
||||
LocationProvider* nmea = sensors.getLocationProvider();
|
||||
int y = 18;
|
||||
display.drawTextLeftAlign(0, y, _task->getGPSState() ? "gps on" : "gps off");
|
||||
if (nmea == NULL) {
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "Can't access GPS");
|
||||
} else {
|
||||
char buf[50];
|
||||
strcpy(buf, nmea->isValid()?"fix":"no fix");
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "sat");
|
||||
sprintf(buf, "%d", nmea->satellitesCount());
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "pos");
|
||||
sprintf(buf, "%.4f %.4f",
|
||||
nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.);
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "alt");
|
||||
sprintf(buf, "%.2f", nmea->getAltitude()/1000.);
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
}
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
} else if (_page == HomePage::SENSORS) {
|
||||
int y = 18;
|
||||
@@ -329,7 +364,7 @@ public:
|
||||
display.drawTextCentered(display.width() / 2, 34, "hibernating...");
|
||||
} else {
|
||||
display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
|
||||
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate: " PRESS_LABEL);
|
||||
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL);
|
||||
}
|
||||
}
|
||||
return 5000; // next render after 5000 ms
|
||||
@@ -364,6 +399,12 @@ public:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
if (c == KEY_ENTER && _page == HomePage::GPS) {
|
||||
_task->toggleGPS();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
if (c == KEY_ENTER && _page == HomePage::SENSORS) {
|
||||
_task->toggleGPS();
|
||||
@@ -623,19 +664,13 @@ bool UITask::isButtonPressed() const {
|
||||
|
||||
void UITask::loop() {
|
||||
char c = 0;
|
||||
#if defined(PIN_USER_BTN)
|
||||
#if UI_HAS_JOYSTICK
|
||||
int ev = user_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
c = checkDisplayOn(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
c = handleDoubleClick(KEY_PREV);
|
||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code
|
||||
}
|
||||
#endif
|
||||
#if defined(WIO_TRACKER_L1)
|
||||
ev = joystick_left.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_LEFT);
|
||||
@@ -648,9 +683,12 @@ void UITask::loop() {
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_RIGHT);
|
||||
}
|
||||
#endif
|
||||
#if defined(PIN_USER_BTN_ANA)
|
||||
ev = analog_btn.check();
|
||||
ev = back_btn.check();
|
||||
if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
#elif defined(PIN_USER_BTN)
|
||||
int ev = user_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
@@ -661,6 +699,21 @@ void UITask::loop() {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
#endif
|
||||
#if defined(PIN_USER_BTN_ANA)
|
||||
if (abs(millis() - _analogue_pin_read_millis) > 10) {
|
||||
ev = analog_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
c = handleDoubleClick(KEY_PREV);
|
||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
_analogue_pin_read_millis = millis();
|
||||
}
|
||||
#endif
|
||||
#if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN)
|
||||
if (millis() > next_backlight_btn_check) {
|
||||
bool touch_state = digitalRead(PIN_BUTTON2);
|
||||
@@ -773,6 +826,18 @@ char UITask::handleTripleClick(char c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
bool UITask::getGPSState() {
|
||||
if (_sensors != NULL) {
|
||||
int num = _sensors->getNumSettings();
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||
return !strcmp(_sensors->getSettingValue(i), "1");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UITask::toggleGPS() {
|
||||
if (_sensors != NULL) {
|
||||
// toggle GPS on/off
|
||||
|
||||
@@ -40,6 +40,10 @@ class UITask : public AbstractUITask {
|
||||
int last_led_increment = 0;
|
||||
#endif
|
||||
|
||||
#ifdef PIN_USER_BTN_ANA
|
||||
unsigned long _analogue_pin_read_millis = millis();
|
||||
#endif
|
||||
|
||||
UIScreen* splash;
|
||||
UIScreen* home;
|
||||
UIScreen* msg_preview;
|
||||
@@ -71,6 +75,7 @@ public:
|
||||
bool isButtonPressed() const;
|
||||
|
||||
void toggleBuzzer();
|
||||
bool getGPSState();
|
||||
void toggleGPS();
|
||||
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
|
||||
MESH_DEBUG_PRINTLN("Login success!");
|
||||
client->last_timestamp = sender_timestamp;
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
client->permissions &= ~0x03;
|
||||
client->permissions |= perms;
|
||||
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
||||
|
||||
@@ -148,7 +149,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
stats.n_packets_recv = radio_driver.getPacketsRecv();
|
||||
stats.n_packets_sent = radio_driver.getPacketsSent();
|
||||
stats.total_air_time_secs = getTotalAirTime() / 1000;
|
||||
stats.total_up_time_secs = _ms->getMillis() / 1000;
|
||||
stats.total_up_time_secs = uptime_millis / 1000;
|
||||
stats.n_sent_flood = getNumSentFlood();
|
||||
stats.n_sent_direct = getNumSentDirect();
|
||||
stats.n_recv_flood = getNumRecvFlood();
|
||||
@@ -169,7 +170,10 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry);
|
||||
if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) {
|
||||
perm_mask = 0x00; // just base telemetry allowed
|
||||
}
|
||||
sensors.querySensors(perm_mask, telemetry);
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
@@ -287,11 +291,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
|
||||
mesh::Packet *MyMesh::createSelfAdvert() {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
{
|
||||
AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
app_data_len = builder.encodeTo(app_data);
|
||||
}
|
||||
uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_REPEATER, app_data);
|
||||
|
||||
return createAdvert(self_id, app_data, app_data_len);
|
||||
}
|
||||
@@ -309,6 +309,10 @@ File MyMesh::openAppend(const char *fname) {
|
||||
bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
if (_prefs.disable_fwd) return false;
|
||||
if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false;
|
||||
if (packet->isRouteFlood() && recv_pkt_region == NULL) {
|
||||
MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -331,6 +335,12 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
||||
}
|
||||
|
||||
void MyMesh::logRx(mesh::Packet *pkt, int len, float score) {
|
||||
#ifdef WITH_BRIDGE
|
||||
if (_prefs.bridge_pkt_src == 1) {
|
||||
bridge.sendPacket(pkt);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_logging) {
|
||||
File f = openAppend(PACKET_LOG_FILE);
|
||||
if (f) {
|
||||
@@ -352,8 +362,11 @@ void MyMesh::logRx(mesh::Packet *pkt, int len, float score) {
|
||||
|
||||
void MyMesh::logTx(mesh::Packet *pkt, int len) {
|
||||
#ifdef WITH_BRIDGE
|
||||
bridge.onPacketTransmitted(pkt);
|
||||
if (_prefs.bridge_pkt_src == 0) {
|
||||
bridge.sendPacket(pkt);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_logging) {
|
||||
File f = openAppend(PACKET_LOG_FILE);
|
||||
if (f) {
|
||||
@@ -391,11 +404,28 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
|
||||
uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6) * t;
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6) * t;
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
// just try to determine region for packet (apply later in allowPacketForward())
|
||||
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
|
||||
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
|
||||
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
|
||||
if (region_map.getWildcard().flags & REGION_DENY_FLOOD) {
|
||||
recv_pkt_region = NULL;
|
||||
} else {
|
||||
recv_pkt_region = ®ion_map.getWildcard();
|
||||
}
|
||||
} else {
|
||||
recv_pkt_region = NULL;
|
||||
}
|
||||
// do normal processing
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
|
||||
@@ -405,7 +435,15 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
uint32_t timestamp;
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
data[len] = 0; // ensure null terminator
|
||||
uint8_t reply_len;
|
||||
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
//} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
||||
// TODO
|
||||
} else {
|
||||
reply_len = 0; // unknown request type
|
||||
}
|
||||
|
||||
if (reply_len == 0) return; // invalid request
|
||||
|
||||
@@ -441,12 +479,19 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool isShare(const mesh::Packet *packet) {
|
||||
if (packet->hasTransportCodes()) {
|
||||
return packet->transport_codes[0] == 0 && packet->transport_codes[1] == 0; // codes { 0, 0 } means 'send to nowhere'
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32_t timestamp,
|
||||
const uint8_t *app_data, size_t app_data_len) {
|
||||
mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl
|
||||
|
||||
// if this a zero hop advert, add it to neighbours
|
||||
if (packet->path_len == 0) {
|
||||
// if this a zero hop advert (and not via 'Share'), add it to neighbours
|
||||
if (packet->path_len == 0 && !isShare(packet)) {
|
||||
AdvertDataParser parser(app_data, app_data_len);
|
||||
if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters
|
||||
putNeighbour(id, timestamp, packet->getSNR());
|
||||
@@ -576,20 +621,57 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||
|
||||
void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits
|
||||
if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
int i = 1;
|
||||
uint8_t filter = packet->payload[i++];
|
||||
uint32_t tag;
|
||||
memcpy(&tag, &packet->payload[i], 4); i += 4;
|
||||
uint32_t since;
|
||||
if (packet->payload_len >= i+4) { // optional since field
|
||||
memcpy(&since, &packet->payload[i], 4); i += 4;
|
||||
} else {
|
||||
since = 0;
|
||||
}
|
||||
|
||||
if ((filter & (1 << ADV_TYPE_REPEATER)) != 0 && _prefs.discovery_mod_timestamp >= since) {
|
||||
bool prefix_only = packet->payload[0] & 1;
|
||||
uint8_t data[6 + PUB_KEY_SIZE];
|
||||
data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_REPEATER; // low 4-bits for node type
|
||||
data[1] = packet->_snr; // let sender know the inbound SNR ( x 4)
|
||||
memcpy(&data[2], &tag, 4); // include tag from request, for client to match to
|
||||
memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE);
|
||||
auto resp = createControlData(data, prefix_only ? 6 + 8 : 6 + PUB_KEY_SIZE);
|
||||
if (resp) {
|
||||
sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
||||
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store),
|
||||
discover_limiter(4, 120) // max 4 every 2 minutes
|
||||
#if defined(WITH_RS232_BRIDGE)
|
||||
, bridge(WITH_RS232_BRIDGE, _mgr, &rtc)
|
||||
#elif defined(WITH_ESPNOW_BRIDGE)
|
||||
, bridge(_mgr, &rtc)
|
||||
, bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc)
|
||||
#endif
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
, bridge(&_prefs, _mgr, &rtc)
|
||||
#endif
|
||||
{
|
||||
last_millis = 0;
|
||||
uptime_millis = 0;
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
dirty_contacts_expiry = 0;
|
||||
set_radio_at = revert_radio_at = 0;
|
||||
_logging = false;
|
||||
region_load_active = false;
|
||||
|
||||
#if MAX_NEIGHBOURS
|
||||
memset(neighbours, 0, sizeof(neighbours));
|
||||
@@ -600,6 +682,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -613,6 +696,20 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_advert_interval = 12; // 12 hours
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
|
||||
// bridge defaults
|
||||
_prefs.bridge_enabled = 1; // enabled
|
||||
_prefs.bridge_delay = 500; // milliseconds
|
||||
_prefs.bridge_pkt_src = 0; // logTx
|
||||
_prefs.bridge_baud = 115200; // baud rate
|
||||
_prefs.bridge_channel = 1; // channel 1
|
||||
|
||||
StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret));
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
}
|
||||
|
||||
void MyMesh::begin(FILESYSTEM *fs) {
|
||||
@@ -620,11 +717,14 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
_fs = fs;
|
||||
// load persisted prefs
|
||||
_cli.loadPrefs(_fs);
|
||||
|
||||
acl.load(_fs);
|
||||
// TODO: key_store.begin();
|
||||
region_map.load(_fs);
|
||||
|
||||
#ifdef WITH_BRIDGE
|
||||
bridge.begin();
|
||||
#if defined(WITH_BRIDGE)
|
||||
if (_prefs.bridge_enabled) {
|
||||
bridge.begin();
|
||||
}
|
||||
#endif
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
@@ -632,6 +732,10 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
|
||||
@@ -757,6 +861,19 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::formatStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr);
|
||||
}
|
||||
|
||||
void MyMesh::formatRadioStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime());
|
||||
}
|
||||
|
||||
void MyMesh::formatPacketStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(),
|
||||
getNumRecvFlood(), getNumRecvDirect());
|
||||
}
|
||||
|
||||
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
||||
self_id = new_id;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -778,8 +895,41 @@ void MyMesh::clearStats() {
|
||||
}
|
||||
|
||||
void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
|
||||
while (*command == ' ')
|
||||
command++; // skip leading spaces
|
||||
if (region_load_active) {
|
||||
if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation
|
||||
region_map = temp_map; // copy over the temp instance as new current map
|
||||
region_load_active = false;
|
||||
|
||||
sprintf(reply, "OK - loaded %d regions", region_map.getCount());
|
||||
} else {
|
||||
char *np = command;
|
||||
while (*np == ' ') np++; // skip indent
|
||||
int indent = np - command;
|
||||
|
||||
char *ep = np;
|
||||
while (RegionMap::is_name_char(*ep)) ep++;
|
||||
if (*ep) { *ep++ = 0; } // set null terminator for end of name
|
||||
|
||||
while (*ep && *ep != 'F') ep++; // look for (optional) flags
|
||||
|
||||
if (indent > 0 && indent < 8 && strlen(np) > 0) {
|
||||
auto parent = load_stack[indent - 1];
|
||||
if (parent) {
|
||||
auto old = region_map.findByName(np);
|
||||
auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists)
|
||||
if (nw) {
|
||||
nw->flags = old ? old->flags : (*ep == 'F' ? 0 : REGION_DENY_FLOOD); // carry-over flags from curr
|
||||
|
||||
load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's
|
||||
}
|
||||
}
|
||||
}
|
||||
reply[0] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
while (*command == ' ') command++; // skip leading spaces
|
||||
|
||||
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
||||
memcpy(reply, command, 3); // reflect the prefix back
|
||||
@@ -821,6 +971,88 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
Serial.printf("\n");
|
||||
}
|
||||
reply[0] = 0;
|
||||
} else if (memcmp(command, "region", 6) == 0) {
|
||||
reply[0] = 0;
|
||||
|
||||
const char* parts[4];
|
||||
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
|
||||
if (n == 1 && sender_timestamp == 0) {
|
||||
region_map.exportTo(Serial);
|
||||
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
|
||||
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
|
||||
memset(load_stack, 0, sizeof(load_stack));
|
||||
load_stack[0] = &temp_map.getWildcard();
|
||||
region_load_active = true;
|
||||
} else if (n >= 2 && strcmp(parts[1], "save") == 0) {
|
||||
_prefs.discovery_mod_timestamp = rtc_clock.getCurrentTime(); // this node is now 'modified' (for discovery info)
|
||||
savePrefs();
|
||||
bool success = region_map.save(_fs);
|
||||
strcpy(reply, success ? "OK" : "Err - save failed");
|
||||
} else if (n >= 3 && strcmp(parts[1], "allowf") == 0) {
|
||||
auto region = region_map.findByNamePrefix(parts[2]);
|
||||
if (region) {
|
||||
region->flags &= ~REGION_DENY_FLOOD;
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - unknown region");
|
||||
}
|
||||
} else if (n >= 3 && strcmp(parts[1], "denyf") == 0) {
|
||||
auto region = region_map.findByNamePrefix(parts[2]);
|
||||
if (region) {
|
||||
region->flags |= REGION_DENY_FLOOD;
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - unknown region");
|
||||
}
|
||||
} else if (n >= 3 && strcmp(parts[1], "get") == 0) {
|
||||
auto region = region_map.findByNamePrefix(parts[2]);
|
||||
if (region) {
|
||||
auto parent = region_map.findById(region->parent);
|
||||
if (parent && parent->id != 0) {
|
||||
sprintf(reply, " %s (%s) %s", region->name, parent->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
|
||||
} else {
|
||||
sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Err - unknown region");
|
||||
}
|
||||
} else if (n >= 3 && strcmp(parts[1], "home") == 0) {
|
||||
auto home = region_map.findByNamePrefix(parts[2]);
|
||||
if (home) {
|
||||
region_map.setHomeRegion(home);
|
||||
sprintf(reply, " home is now %s", home->name);
|
||||
} else {
|
||||
strcpy(reply, "Err - unknown region");
|
||||
}
|
||||
} else if (n == 2 && strcmp(parts[1], "home") == 0) {
|
||||
auto home = region_map.getHomeRegion();
|
||||
sprintf(reply, " home is %s", home ? home->name : "*");
|
||||
} else if (n >= 3 && strcmp(parts[1], "put") == 0) {
|
||||
auto parent = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard();
|
||||
if (parent == NULL) {
|
||||
strcpy(reply, "Err - unknown parent");
|
||||
} else {
|
||||
auto region = region_map.putRegion(parts[2], parent->id);
|
||||
if (region == NULL) {
|
||||
strcpy(reply, "Err - unable to put");
|
||||
} else {
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
}
|
||||
} else if (n >= 3 && strcmp(parts[1], "remove") == 0) {
|
||||
auto region = region_map.findByName(parts[2]);
|
||||
if (region) {
|
||||
if (region_map.removeRegion(*region)) {
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - not empty");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Err - not found");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Err - ??");
|
||||
}
|
||||
} else{
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
}
|
||||
@@ -863,4 +1095,9 @@ void MyMesh::loop() {
|
||||
acl.save(_fs);
|
||||
dirty_contacts_expiry = 0;
|
||||
}
|
||||
|
||||
// update uptime
|
||||
uint32_t now = millis();
|
||||
uptime_millis += now - last_millis;
|
||||
last_millis = now;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Mesh.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
@@ -12,16 +13,6 @@
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/StaticPoolPacketManager.h>
|
||||
#include <helpers/SimpleMeshTables.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
#include "helpers/bridges/RS232Bridge.h"
|
||||
#define WITH_BRIDGE
|
||||
@@ -32,6 +23,18 @@
|
||||
#define WITH_BRIDGE
|
||||
#endif
|
||||
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/SimpleMeshTables.h>
|
||||
#include <helpers/StaticPoolPacketManager.h>
|
||||
#include <helpers/StatsFormatHelper.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/RegionMap.h>
|
||||
#include "RateLimiter.h"
|
||||
|
||||
#ifdef WITH_BRIDGE
|
||||
extern AbstractBridge* bridge;
|
||||
#endif
|
||||
@@ -65,11 +68,11 @@ struct NeighbourInfo {
|
||||
};
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "28 Sep 2025"
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.9.0"
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "repeater"
|
||||
@@ -78,12 +81,20 @@ struct NeighbourInfo {
|
||||
|
||||
class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
FILESYSTEM* _fs;
|
||||
uint32_t last_millis;
|
||||
uint64_t uptime_millis;
|
||||
unsigned long next_local_advert, next_flood_advert;
|
||||
bool _logging;
|
||||
NodePrefs _prefs;
|
||||
CommonCLI _cli;
|
||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||
ClientACL acl;
|
||||
TransportKeyStore key_store;
|
||||
RegionMap region_map, temp_map;
|
||||
RegionEntry* load_stack[8];
|
||||
RegionEntry* recv_pkt_region;
|
||||
RateLimiter discover_limiter;
|
||||
bool region_load_active;
|
||||
unsigned long dirty_contacts_expiry;
|
||||
#if MAX_NEIGHBOURS
|
||||
NeighbourInfo neighbours[MAX_NEIGHBOURS];
|
||||
@@ -135,12 +146,21 @@ protected:
|
||||
return _prefs.multi_acks;
|
||||
}
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
void applyGpsPrefs() {
|
||||
sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0");
|
||||
}
|
||||
#endif
|
||||
|
||||
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
|
||||
|
||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
||||
int searchPeersByHash(const uint8_t* hash) override;
|
||||
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
|
||||
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len);
|
||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onControlDataRecv(mesh::Packet* packet) override;
|
||||
|
||||
public:
|
||||
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
||||
@@ -175,6 +195,9 @@ public:
|
||||
void setTxPower(uint8_t power_dbm) override;
|
||||
void formatNeighborsReply(char *reply) override;
|
||||
void removeNeighbor(const uint8_t* pubkey, int key_len) override;
|
||||
void formatStatsReply(char *reply) override;
|
||||
void formatRadioStatsReply(char *reply) override;
|
||||
void formatPacketStatsReply(char *reply) override;
|
||||
|
||||
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
||||
|
||||
@@ -182,4 +205,24 @@ public:
|
||||
void clearStats() override;
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||
void loop();
|
||||
|
||||
#if defined(WITH_BRIDGE)
|
||||
void setBridgeState(bool enable) override {
|
||||
if (enable == bridge.isRunning()) return;
|
||||
if (enable)
|
||||
{
|
||||
bridge.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
bridge.end();
|
||||
}
|
||||
}
|
||||
|
||||
void restartBridge() override {
|
||||
if (!bridge.isRunning()) return;
|
||||
bridge.end();
|
||||
bridge.begin();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
23
examples/simple_repeater/RateLimiter.h
Normal file
23
examples/simple_repeater/RateLimiter.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class RateLimiter {
|
||||
uint32_t _start_timestamp;
|
||||
uint32_t _secs;
|
||||
uint16_t _maximum, _count;
|
||||
|
||||
public:
|
||||
RateLimiter(uint16_t maximum, uint32_t secs): _maximum(maximum), _secs(secs), _start_timestamp(0), _count(0) { }
|
||||
|
||||
bool allow(uint32_t now) {
|
||||
if (now < _start_timestamp + _secs) {
|
||||
_count++;
|
||||
if (_count > _maximum) return false; // deny
|
||||
} else { // time window now expired
|
||||
_start_timestamp = now;
|
||||
_count = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -91,14 +91,16 @@ void loop() {
|
||||
if (c != '\n') {
|
||||
command[len++] = c;
|
||||
command[len] = 0;
|
||||
Serial.print(c);
|
||||
}
|
||||
Serial.print(c);
|
||||
if (c == '\r') break;
|
||||
}
|
||||
if (len == sizeof(command)-1) { // command buffer full
|
||||
command[sizeof(command)-1] = '\r';
|
||||
}
|
||||
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
Serial.print('\n');
|
||||
command[len - 1] = 0; // replace newline with C string null terminator
|
||||
char reply[160];
|
||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
@@ -114,4 +116,5 @@ void loop() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -114,11 +114,7 @@ bool MyMesh::processAck(const uint8_t *data) {
|
||||
|
||||
mesh::Packet *MyMesh::createSelfAdvert() {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
{
|
||||
AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
app_data_len = builder.encodeTo(app_data);
|
||||
}
|
||||
uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_ROOM, app_data);
|
||||
|
||||
return createAdvert(self_id, app_data, app_data_len);
|
||||
}
|
||||
@@ -148,7 +144,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
stats.n_packets_recv = radio_driver.getPacketsRecv();
|
||||
stats.n_packets_sent = radio_driver.getPacketsSent();
|
||||
stats.total_air_time_secs = getTotalAirTime() / 1000;
|
||||
stats.total_up_time_secs = _ms->getMillis() / 1000;
|
||||
stats.total_up_time_secs = uptime_millis / 1000;
|
||||
stats.n_sent_flood = getNumSentFlood();
|
||||
stats.n_sent_direct = getNumSentDirect();
|
||||
stats.n_recv_flood = getNumRecvFlood();
|
||||
@@ -169,7 +165,10 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry);
|
||||
if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) {
|
||||
perm_mask = 0x00; // just base telemetry allowed
|
||||
}
|
||||
sensors.querySensors(perm_mask, telemetry);
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
@@ -266,11 +265,11 @@ const char *MyMesh::getLogDateTime() {
|
||||
|
||||
uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6) * t;
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6) * t;
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
@@ -290,7 +289,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
data[len] = 0; // ensure null terminator
|
||||
|
||||
ClientInfo* client = NULL;
|
||||
if (data[8] == 0 && !_prefs.allow_read_only) { // blank password, just check if sender is in ACL
|
||||
if (data[8] == 0) { // blank password, just check if sender is in ACL
|
||||
client = acl.getClient(sender.pub_key, PUB_KEY_SIZE);
|
||||
if (client == NULL) {
|
||||
#if MESH_DEBUG
|
||||
@@ -326,6 +325,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
client->extra.room.push_failures = 0;
|
||||
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
client->permissions &= ~0x03;
|
||||
client->permissions |= perm;
|
||||
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
||||
|
||||
@@ -583,7 +583,9 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) {
|
||||
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
||||
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) {
|
||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) {
|
||||
last_millis = 0;
|
||||
uptime_millis = 0;
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
dirty_contacts_expiry = 0;
|
||||
_logging = false;
|
||||
@@ -594,6 +596,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.rx_delay_base = 0.0f; // off by default, was 10.0
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f;
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -612,6 +615,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
|
||||
#endif
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
|
||||
next_post_idx = 0;
|
||||
next_client_idx = 0;
|
||||
next_push = 0;
|
||||
@@ -632,6 +640,10 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
|
||||
@@ -721,6 +733,19 @@ void MyMesh::clearStats() {
|
||||
((SimpleMeshTables *)getTables())->resetStats();
|
||||
}
|
||||
|
||||
void MyMesh::formatStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr);
|
||||
}
|
||||
|
||||
void MyMesh::formatRadioStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime());
|
||||
}
|
||||
|
||||
void MyMesh::formatPacketStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(),
|
||||
getNumRecvFlood(), getNumRecvDirect());
|
||||
}
|
||||
|
||||
void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
|
||||
while (*command == ' ')
|
||||
command++; // skip leading spaces
|
||||
@@ -784,7 +809,7 @@ void MyMesh::loop() {
|
||||
if (c->extra.room.pending_ack && millisHasNowPassed(c->extra.room.ack_timeout)) {
|
||||
c->extra.room.push_failures++;
|
||||
c->extra.room.pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry)
|
||||
MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures);
|
||||
MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->extra.room.push_failures);
|
||||
}
|
||||
}
|
||||
// check next Round-Robin client, and sync next new post
|
||||
@@ -852,4 +877,9 @@ void MyMesh::loop() {
|
||||
}
|
||||
|
||||
// TODO: periodically check for OLD/inactive entries in known_clients[], and evict
|
||||
|
||||
// update uptime
|
||||
uint32_t now = millis();
|
||||
uptime_millis += now - last_millis;
|
||||
last_millis = now;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/StatsFormatHelper.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
@@ -25,11 +26,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "28 Sep 2025"
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.9.0"
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
@@ -88,6 +89,8 @@ struct PostInfo {
|
||||
|
||||
class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
FILESYSTEM* _fs;
|
||||
uint32_t last_millis;
|
||||
uint64_t uptime_millis;
|
||||
unsigned long next_local_advert, next_flood_advert;
|
||||
bool _logging;
|
||||
NodePrefs _prefs;
|
||||
@@ -149,6 +152,12 @@ protected:
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
void applyGpsPrefs() {
|
||||
sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0");
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
||||
|
||||
@@ -184,6 +193,9 @@ public:
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
strcpy(reply, "not supported");
|
||||
}
|
||||
void formatStatsReply(char *reply) override;
|
||||
void formatRadioStatsReply(char *reply) override;
|
||||
void formatPacketStatsReply(char *reply) override;
|
||||
|
||||
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
||||
|
||||
|
||||
@@ -110,4 +110,5 @@ void loop() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ public:
|
||||
|
||||
StdRNG fast_rng;
|
||||
SimpleMeshTables tables;
|
||||
MyMesh the_mesh(radio_driver, fast_rng, *new VolatileRTCClock(), tables); // TODO: test with 'rtc_clock' in target.cpp
|
||||
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables);
|
||||
|
||||
void halt() {
|
||||
while (1) ;
|
||||
@@ -587,4 +587,5 @@ void setup() {
|
||||
|
||||
void loop() {
|
||||
the_mesh.loop();
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -239,11 +239,7 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
|
||||
|
||||
mesh::Packet* SensorMesh::createSelfAdvert() {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
{
|
||||
AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
app_data_len = builder.encodeTo(app_data);
|
||||
}
|
||||
uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_SENSOR, app_data);
|
||||
|
||||
return createAdvert(self_id, app_data, app_data_len);
|
||||
}
|
||||
@@ -453,7 +449,14 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
data[len] = 0; // ensure null terminator
|
||||
uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
uint8_t reply_len;
|
||||
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
//} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
||||
// TODO
|
||||
} else {
|
||||
reply_len = 0; // unknown request type
|
||||
}
|
||||
|
||||
if (reply_len == 0) return; // invalid request
|
||||
|
||||
@@ -614,6 +617,39 @@ bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||
|
||||
void SensorMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits
|
||||
if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6) {
|
||||
// TODO: apply rate limiting to these!
|
||||
int i = 1;
|
||||
uint8_t filter = packet->payload[i++];
|
||||
uint32_t tag;
|
||||
memcpy(&tag, &packet->payload[i], 4); i += 4;
|
||||
uint32_t since;
|
||||
if (packet->payload_len >= i+4) { // optional since field
|
||||
memcpy(&since, &packet->payload[i], 4); i += 4;
|
||||
} else {
|
||||
since = 0;
|
||||
}
|
||||
|
||||
if ((filter & (1 << ADV_TYPE_SENSOR)) != 0 && _prefs.discovery_mod_timestamp >= since) {
|
||||
bool prefix_only = packet->payload[0] & 1;
|
||||
uint8_t data[6 + PUB_KEY_SIZE];
|
||||
data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_SENSOR; // low 4-bits for node type
|
||||
data[1] = packet->_snr; // let sender know the inbound SNR ( x 4)
|
||||
memcpy(&data[2], &tag, 4); // include tag from request, for client to match to
|
||||
memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE);
|
||||
auto resp = createControlData(data, prefix_only ? 6 + 8 : 6 + PUB_KEY_SIZE);
|
||||
if (resp) {
|
||||
sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
|
||||
int i = matching_peer_indexes[sender_idx];
|
||||
if (i < 0 || i >= acl.getNumClients()) {
|
||||
@@ -655,7 +691,7 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
|
||||
SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
{
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
dirty_contacts_expiry = 0;
|
||||
@@ -668,6 +704,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -682,6 +719,11 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
_prefs.disable_fwd = true;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
}
|
||||
|
||||
void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
@@ -697,6 +739,10 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SensorMesh::formatFileSystem() {
|
||||
@@ -764,6 +810,19 @@ void SensorMesh::setTxPower(uint8_t power_dbm) {
|
||||
radio_set_tx_power(power_dbm);
|
||||
}
|
||||
|
||||
void SensorMesh::formatStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr);
|
||||
}
|
||||
|
||||
void SensorMesh::formatRadioStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime());
|
||||
}
|
||||
|
||||
void SensorMesh::formatPacketStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(),
|
||||
getNumRecvFlood(), getNumRecvDirect());
|
||||
}
|
||||
|
||||
float SensorMesh::getTelemValue(uint8_t channel, uint8_t type) {
|
||||
auto buf = telemetry.getBuffer();
|
||||
uint8_t size = telemetry.getSize();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/StatsFormatHelper.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
@@ -32,11 +33,11 @@
|
||||
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "28 Sep 2025"
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.9.0"
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "sensor"
|
||||
@@ -69,6 +70,9 @@ public:
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
strcpy(reply, "not supported");
|
||||
}
|
||||
void formatStatsReply(char *reply) override;
|
||||
void formatRadioStatsReply(char *reply) override;
|
||||
void formatPacketStatsReply(char *reply) override;
|
||||
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
||||
void saveIdentity(const mesh::LocalIdentity& new_id) override;
|
||||
void clearStats() override { }
|
||||
@@ -121,6 +125,7 @@ protected:
|
||||
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
|
||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onControlDataRecv(mesh::Packet* packet) override;
|
||||
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
||||
virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len);
|
||||
void sendAckTo(const ClientInfo& dest, uint32_t ack_hash);
|
||||
@@ -149,4 +154,9 @@ private:
|
||||
|
||||
void sendAlert(const ClientInfo* c, Trigger* t);
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
void applyGpsPrefs() {
|
||||
sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0");
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -144,4 +144,5 @@ void loop() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "MeshCore",
|
||||
"version" : "1.8.0",
|
||||
"version" : "1.10.0",
|
||||
"dependencies": {
|
||||
"SPI": "*",
|
||||
"Wire": "*",
|
||||
"jgromes/RadioLib": "^7.1.2",
|
||||
"jgromes/RadioLib": "^7.3.0",
|
||||
"rweather/Crypto": "^0.4.0",
|
||||
"adafruit/RTClib": "^2.1.3",
|
||||
"melopero/Melopero RV3028": "^1.1.0",
|
||||
"electroniccats/CayenneLPP": "1.4.0"
|
||||
"electroniccats/CayenneLPP": "1.6.1"
|
||||
},
|
||||
"build": {
|
||||
"extraScript": "build_as_lib.py"
|
||||
|
||||
@@ -18,11 +18,11 @@ monitor_speed = 115200
|
||||
lib_deps =
|
||||
SPI
|
||||
Wire
|
||||
jgromes/RadioLib @ ^7.1.2
|
||||
jgromes/RadioLib @ ^7.3.0
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
melopero/Melopero RV3028 @ ^1.1.0
|
||||
electroniccats/CayenneLPP @ 1.4.0
|
||||
electroniccats/CayenneLPP @ 1.6.1
|
||||
build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-D LORA_FREQ=869.525
|
||||
-D LORA_BW=250
|
||||
@@ -67,6 +67,7 @@ lib_deps =
|
||||
file://arch/esp32/AsyncElegantOTA
|
||||
|
||||
; esp32c6 uses arduino framework 3.x
|
||||
; WARNING: experimental. pioarduino on esp32c6 needs work - it's not considered stable and has issues.
|
||||
[esp32c6_base]
|
||||
extends = esp32_base
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip
|
||||
@@ -76,6 +77,9 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
|
||||
[nrf52_base]
|
||||
extends = arduino_base
|
||||
platform = nordicnrf52
|
||||
platform_packages =
|
||||
framework-arduinoadafruitnrf52 @ 1.10700.0
|
||||
extra_scripts = create-uf2.py
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
-D NRF52_PLATFORM
|
||||
-D LFS_NO_ASSERT=1
|
||||
@@ -124,6 +128,8 @@ build_flags =
|
||||
-D ENV_INCLUDE_INA260=1
|
||||
-D ENV_INCLUDE_MLX90614=1
|
||||
-D ENV_INCLUDE_VL53L0X=1
|
||||
-D ENV_INCLUDE_BME680=1
|
||||
-D ENV_INCLUDE_BMP085=1
|
||||
lib_deps =
|
||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
||||
adafruit/Adafruit INA219 @ ^1.2.3
|
||||
@@ -138,3 +144,5 @@ lib_deps =
|
||||
adafruit/Adafruit MLX90614 Library @ ^2.1.5
|
||||
adafruit/Adafruit_VL53L0X @ ^1.2.4
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
adafruit/Adafruit BME680 Library @ ^2.0.4
|
||||
adafruit/Adafruit BMP085 Library @ ^1.2.4
|
||||
|
||||
70
src/Mesh.cpp
70
src/Mesh.cpp
@@ -68,6 +68,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
return ACTION_RELEASE;
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_CONTROL && (pkt->payload[0] & 0x80) != 0) {
|
||||
if (pkt->path_len == 0) {
|
||||
onControlDataRecv(pkt);
|
||||
}
|
||||
// just zero-hop control packets allowed (for this subset of payloads)
|
||||
return ACTION_RELEASE;
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
|
||||
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
|
||||
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
|
||||
@@ -90,6 +98,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard.
|
||||
}
|
||||
|
||||
if (pkt->isRouteFlood() && filterRecvFloodPacket(pkt)) return ACTION_RELEASE;
|
||||
|
||||
DispatcherAction action = ACTION_RELEASE;
|
||||
|
||||
switch (pkt->getPayloadType()) {
|
||||
@@ -201,9 +211,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
if (i + 2 >= pkt->payload_len) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
|
||||
} else if (!_tables->hasSeen(pkt)) {
|
||||
// scan channels DB, for all matching hashes of 'channel_hash' (max 2 matches supported ATM)
|
||||
GroupChannel channels[2];
|
||||
int num = searchChannelsByHash(&channel_hash, channels, 2);
|
||||
// scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM)
|
||||
GroupChannel channels[4];
|
||||
int num = searchChannelsByHash(&channel_hash, channels, 4);
|
||||
// for each matching channel, try to decrypt data
|
||||
for (int j = 0; j < num; j++) {
|
||||
// decrypt, checking MAC is valid
|
||||
@@ -587,6 +597,22 @@ Packet* Mesh::createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
Packet* Mesh::createControlData(const uint8_t* data, size_t len) {
|
||||
if (len > sizeof(Packet::payload)) return NULL; // invalid arg
|
||||
|
||||
Packet* packet = obtainNewPacket();
|
||||
if (packet == NULL) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::createControlData(): error, packet pool empty", getLogDateTime());
|
||||
return NULL;
|
||||
}
|
||||
packet->header = (PAYLOAD_TYPE_CONTROL << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
|
||||
|
||||
memcpy(packet->payload, data, len);
|
||||
packet->payload_len = len;
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
||||
@@ -610,6 +636,31 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
|
||||
sendPacket(packet, pri, delay_millis);
|
||||
}
|
||||
|
||||
void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
||||
return;
|
||||
}
|
||||
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_TRANSPORT_FLOOD;
|
||||
packet->transport_codes[0] = transport_codes[0];
|
||||
packet->transport_codes[1] = transport_codes[1];
|
||||
packet->path_len = 0;
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
uint8_t pri;
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||
pri = 2;
|
||||
} else if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT) {
|
||||
pri = 3; // de-prioritie these
|
||||
} else {
|
||||
pri = 1;
|
||||
}
|
||||
sendPacket(packet, pri, delay_millis);
|
||||
}
|
||||
|
||||
void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis) {
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_DIRECT;
|
||||
@@ -645,4 +696,17 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) {
|
||||
sendPacket(packet, 0, delay_millis);
|
||||
}
|
||||
|
||||
void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) {
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_TRANSPORT_DIRECT;
|
||||
packet->transport_codes[0] = transport_codes[0];
|
||||
packet->transport_codes[1] = transport_codes[1];
|
||||
|
||||
packet->path_len = 0; // path_len of zero means Zero Hop
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
sendPacket(packet, 0, delay_millis);
|
||||
}
|
||||
|
||||
}
|
||||
24
src/Mesh.h
24
src/Mesh.h
@@ -43,6 +43,12 @@ protected:
|
||||
*/
|
||||
DispatcherAction routeRecvPacket(Packet* packet);
|
||||
|
||||
/**
|
||||
* \brief Called _before_ the packet is dispatched to the on..Recv() methods.
|
||||
* \returns true, if given packet should be NOT be processed.
|
||||
*/
|
||||
virtual bool filterRecvFloodPacket(Packet* packet) { return false; }
|
||||
|
||||
/**
|
||||
* \brief Check whether this packet should be forwarded (re-transmitted) or not.
|
||||
* Is sub-classes responsibility to make sure given packet is only transmitted ONCE (by this node)
|
||||
@@ -128,6 +134,11 @@ protected:
|
||||
*/
|
||||
virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { }
|
||||
|
||||
/**
|
||||
* \brief A control packet has been received.
|
||||
*/
|
||||
virtual void onControlDataRecv(Packet* packet) { }
|
||||
|
||||
/**
|
||||
* \brief A packet with PAYLOAD_TYPE_RAW_CUSTOM has been received.
|
||||
*/
|
||||
@@ -180,12 +191,19 @@ public:
|
||||
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
|
||||
Packet* createRawData(const uint8_t* data, size_t len);
|
||||
Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0);
|
||||
Packet* createControlData(const uint8_t* data, size_t len);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet with flood routing
|
||||
*/
|
||||
void sendFlood(Packet* packet, uint32_t delay_millis=0);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet with flood routing
|
||||
* \param transport_codes array of 2 codes to attach to packet
|
||||
*/
|
||||
void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet with Direct routing
|
||||
*/
|
||||
@@ -196,6 +214,12 @@ public:
|
||||
*/
|
||||
void sendZeroHop(Packet* packet, uint32_t delay_millis=0);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet to just neigbor nodes (zero hops), with specific transort codes
|
||||
* \param transport_codes array of 2 codes to attach to packet
|
||||
*/
|
||||
void sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
#define MESH_DEBUG_PRINTLN(...) {}
|
||||
#endif
|
||||
|
||||
#if BRIDGE_DEBUG && ARDUINO
|
||||
#define BRIDGE_DEBUG_PRINTLN(F, ...) Serial.printf("%s BRIDGE: " F, getLogDateTime(), ##__VA_ARGS__)
|
||||
#else
|
||||
#define BRIDGE_DEBUG_PRINTLN(...) {}
|
||||
#endif
|
||||
|
||||
namespace mesh {
|
||||
|
||||
#define BD_STARTUP_NORMAL 0 // getStartupReason() codes
|
||||
@@ -66,6 +72,11 @@ public:
|
||||
*/
|
||||
virtual void setCurrentTime(uint32_t time) = 0;
|
||||
|
||||
/**
|
||||
* override in classes that need to periodically update internal state
|
||||
*/
|
||||
virtual void tick() { /* no op */}
|
||||
|
||||
uint32_t getCurrentTimeUnique() {
|
||||
uint32_t t = getCurrentTime();
|
||||
if (t <= last_unique) {
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace mesh {
|
||||
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
|
||||
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop
|
||||
#define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets
|
||||
#define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet
|
||||
//...
|
||||
#define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc
|
||||
|
||||
|
||||
@@ -11,6 +11,18 @@ public:
|
||||
*/
|
||||
virtual void begin() = 0;
|
||||
|
||||
/**
|
||||
* @brief Stops the bridge.
|
||||
*/
|
||||
virtual void end() = 0;
|
||||
|
||||
/**
|
||||
* @brief Gets the current state of the bridge.
|
||||
*
|
||||
* @return true if the bridge is initialized and running, false otherwise.
|
||||
*/
|
||||
virtual bool isRunning() const = 0;
|
||||
|
||||
/**
|
||||
* @brief A method to be called on every main loop iteration.
|
||||
* Used for tasks like checking for incoming data.
|
||||
@@ -20,14 +32,14 @@ public:
|
||||
/**
|
||||
* @brief A callback that is triggered when the mesh transmits a packet.
|
||||
* The bridge can use this to forward the packet.
|
||||
*
|
||||
*
|
||||
* @param packet The packet that was transmitted.
|
||||
*/
|
||||
virtual void onPacketTransmitted(mesh::Packet* packet) = 0;
|
||||
virtual void sendPacket(mesh::Packet* packet) = 0;
|
||||
|
||||
/**
|
||||
* @brief Processes a received packet from the bridge's medium.
|
||||
*
|
||||
*
|
||||
* @param packet The packet that was received.
|
||||
*/
|
||||
virtual void onPacketReceived(mesh::Packet* packet) = 0;
|
||||
|
||||
@@ -4,11 +4,19 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
class VolatileRTCClock : public mesh::RTCClock {
|
||||
long millis_offset;
|
||||
uint32_t base_time;
|
||||
uint64_t accumulator;
|
||||
unsigned long prev_millis;
|
||||
public:
|
||||
VolatileRTCClock() { millis_offset = 1715770351; } // 15 May 2024, 8:50pm
|
||||
uint32_t getCurrentTime() override { return (millis()/1000 + millis_offset); }
|
||||
void setCurrentTime(uint32_t time) override { millis_offset = time - millis()/1000; }
|
||||
VolatileRTCClock() { base_time = 1715770351; accumulator = 0; prev_millis = millis(); } // 15 May 2024, 8:50pm
|
||||
uint32_t getCurrentTime() override { return base_time + accumulator/1000; }
|
||||
void setCurrentTime(uint32_t time) override { base_time = time; accumulator = 0; prev_millis = millis(); }
|
||||
|
||||
void tick() override {
|
||||
unsigned long now = millis();
|
||||
accumulator += (now - prev_millis);
|
||||
prev_millis = now;
|
||||
}
|
||||
};
|
||||
|
||||
class ArduinoMillis : public mesh::MillisecondClock {
|
||||
|
||||
@@ -14,4 +14,8 @@ public:
|
||||
void begin(TwoWire& wire);
|
||||
uint32_t getCurrentTime() override;
|
||||
void setCurrentTime(uint32_t time) override;
|
||||
|
||||
void tick() override {
|
||||
_fallback->tick(); // is typically VolatileRTCClock, which now needs tick()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
#define TXT_ACK_DELAY 200
|
||||
#endif
|
||||
|
||||
void BaseChatMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
}
|
||||
void BaseChatMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
}
|
||||
|
||||
mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
@@ -34,7 +41,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl
|
||||
void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
||||
if (dest.out_path_len < 0) {
|
||||
mesh::Packet* ack = createAck(ack_hash);
|
||||
if (ack) sendFlood(ack, TXT_ACK_DELAY);
|
||||
if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY);
|
||||
} else {
|
||||
uint32_t d = TXT_ACK_DELAY;
|
||||
if (getExtraAckTransmitCount() > 0) {
|
||||
@@ -68,9 +75,16 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
|
||||
}
|
||||
|
||||
// save a copy of raw advert packet (to support "Share..." function)
|
||||
int plen = packet->writeTo(temp_buf);
|
||||
int plen;
|
||||
{
|
||||
uint8_t save = packet->header;
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_FLOOD; // make sure transport codes are NOT saved
|
||||
plen = packet->writeTo(temp_buf);
|
||||
packet->header = save;
|
||||
}
|
||||
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
|
||||
|
||||
|
||||
bool is_new = false;
|
||||
if (from == NULL) {
|
||||
if (!isAutoAddEnabled()) {
|
||||
@@ -168,7 +182,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||
if (path) sendFlood(path, TXT_ACK_DELAY);
|
||||
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
|
||||
} else {
|
||||
sendAckTo(from, ack_hash);
|
||||
}
|
||||
@@ -179,7 +193,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
if (packet->isRouteFlood()) {
|
||||
// let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra)
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0);
|
||||
if (path) sendFlood(path);
|
||||
if (path) sendFloodScoped(from, path);
|
||||
}
|
||||
} else if (flags == TXT_TYPE_SIGNED_PLAIN) {
|
||||
if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date
|
||||
@@ -195,7 +209,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||
if (path) sendFlood(path, TXT_ACK_DELAY);
|
||||
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
|
||||
} else {
|
||||
sendAckTo(from, ack_hash);
|
||||
}
|
||||
@@ -211,14 +225,14 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len);
|
||||
if (reply) {
|
||||
if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,7 +353,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp,
|
||||
|
||||
int rc;
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
||||
rc = MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
@@ -365,7 +379,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
int rc;
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
||||
rc = MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
@@ -391,7 +405,7 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan
|
||||
|
||||
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len);
|
||||
if (pkt) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(channel, pkt);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -405,7 +419,9 @@ bool BaseChatMesh::shareContactZeroHop(const ContactInfo& contact) {
|
||||
if (packet == NULL) return false; // no Packets available
|
||||
|
||||
packet->readFrom(temp_buf, plen); // restore Packet from 'blob'
|
||||
sendZeroHop(packet);
|
||||
uint16_t codes[2];
|
||||
codes[0] = codes[1] = 0; // { 0, 0 } means 'send this nowhere'
|
||||
sendZeroHop(packet, codes);
|
||||
return true; // success
|
||||
}
|
||||
|
||||
@@ -451,7 +467,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
@@ -478,7 +494,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
@@ -505,7 +521,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
|
||||
@@ -107,6 +107,9 @@ protected:
|
||||
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
|
||||
virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len);
|
||||
|
||||
virtual void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0);
|
||||
virtual void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0);
|
||||
|
||||
// storage concepts, for sub-classes to override/implement
|
||||
virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented
|
||||
virtual bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { return false; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "CommonCLI.h"
|
||||
#include "TxtDataHelpers.h"
|
||||
#include "AdvertDataHelpers.h"
|
||||
#include <RTClib.h>
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
@@ -32,32 +33,44 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
if (file) {
|
||||
uint8_t pad[8];
|
||||
|
||||
file.read((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.read((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.read(pad, 4); // 36
|
||||
file.read((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.read((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.read((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.read((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.read((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.read((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.read((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.read((uint8_t *) pad, 1); // 79 was 'unused'
|
||||
file.read((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.read((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.read((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.read((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.read(pad, 4); // 108
|
||||
file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.read((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.read((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.read((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.read(pad, 3); // 121
|
||||
file.read((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.read((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.read((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.read((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.read((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.read(pad, 4); // 36
|
||||
file.read((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.read((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.read((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.read((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.read((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.read((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.read((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.read((uint8_t *)pad, 1); // 79 was 'unused'
|
||||
file.read((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.read((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.read((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.read((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.read(pad, 4); // 108
|
||||
file.read((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.read((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.read((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.read(pad, 3); // 121
|
||||
file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.read((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
|
||||
file.read((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
|
||||
file.read((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
|
||||
file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
|
||||
file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
|
||||
file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
|
||||
file.read(pad, 4); // 152
|
||||
file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||
file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||
file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
|
||||
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
// 166
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -65,12 +78,22 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0, 2.0f);
|
||||
_prefs->airtime_factor = constrain(_prefs->airtime_factor, 0, 9.0f);
|
||||
_prefs->freq = constrain(_prefs->freq, 400.0f, 2500.0f);
|
||||
_prefs->bw = constrain(_prefs->bw, 62.5f, 500.0f);
|
||||
_prefs->sf = constrain(_prefs->sf, 7, 12);
|
||||
_prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f);
|
||||
_prefs->sf = constrain(_prefs->sf, 5, 12);
|
||||
_prefs->cr = constrain(_prefs->cr, 5, 8);
|
||||
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30);
|
||||
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
|
||||
|
||||
// sanitise bad bridge pref values
|
||||
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
|
||||
_prefs->bridge_delay = constrain(_prefs->bridge_delay, 0, 10000);
|
||||
_prefs->bridge_pkt_src = constrain(_prefs->bridge_pkt_src, 0, 1);
|
||||
_prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200);
|
||||
_prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
|
||||
|
||||
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
|
||||
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
@@ -88,32 +111,44 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
uint8_t pad[8];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
|
||||
file.write((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.write((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.write(pad, 4); // 36
|
||||
file.write((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.write((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.write((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.write((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.write((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.write((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.write((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.write((uint8_t *) pad, 1); // 79 was 'unused'
|
||||
file.write((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.write((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.write((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.write((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.write(pad, 4); // 108
|
||||
file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.write((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.write((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.write((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.write(pad, 3); // 121
|
||||
file.write((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.write((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.write((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.write((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.write((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.write(pad, 4); // 36
|
||||
file.write((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.write((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.write((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.write((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.write((uint8_t *)pad, 1); // 79 was 'unused'
|
||||
file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.write(pad, 4); // 108
|
||||
file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.write(pad, 3); // 121
|
||||
file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
|
||||
file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
|
||||
file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
|
||||
file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
|
||||
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
|
||||
file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
|
||||
file.write(pad, 4); // 152
|
||||
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
|
||||
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
// 166
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -128,6 +163,19 @@ void CommonCLI::savePrefs() {
|
||||
_callbacks->savePrefs();
|
||||
}
|
||||
|
||||
uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) {
|
||||
if (_prefs->advert_loc_policy == ADVERT_LOC_NONE) {
|
||||
AdvertDataBuilder builder(node_type, _prefs->node_name);
|
||||
return builder.encodeTo(app_data);
|
||||
} else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) {
|
||||
AdvertDataBuilder builder(node_type, _prefs->node_name, _sensors->node_lat, _sensors->node_lon);
|
||||
return builder.encodeTo(app_data);
|
||||
} else {
|
||||
AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon);
|
||||
return builder.encodeTo(app_data);
|
||||
}
|
||||
}
|
||||
|
||||
void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
|
||||
if (memcmp(command, "reboot", 6) == 0) {
|
||||
_board->reboot(); // doesn't return
|
||||
@@ -199,6 +247,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (memcmp(command, "clear stats", 11) == 0) {
|
||||
_callbacks->clearStats();
|
||||
strcpy(reply, "(OK - stats reset)");
|
||||
/*
|
||||
* GET commands
|
||||
*/
|
||||
} else if (memcmp(command, "get ", 4) == 0) {
|
||||
const char* config = &command[4];
|
||||
if (memcmp(config, "af", 2) == 0) {
|
||||
@@ -252,9 +303,40 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE);
|
||||
} else if (memcmp(config, "role", 4) == 0) {
|
||||
sprintf(reply, "> %s", _callbacks->getRole());
|
||||
} else if (memcmp(config, "bridge.type", 11) == 0) {
|
||||
sprintf(reply, "> %s",
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
"rs232"
|
||||
#elif WITH_ESPNOW_BRIDGE
|
||||
"espnow"
|
||||
#else
|
||||
"none"
|
||||
#endif
|
||||
);
|
||||
#ifdef WITH_BRIDGE
|
||||
} else if (memcmp(config, "bridge.enabled", 14) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "bridge.delay", 12) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay);
|
||||
} else if (memcmp(config, "bridge.source", 13) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx");
|
||||
#endif
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
} else if (memcmp(config, "bridge.baud", 11) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_baud);
|
||||
#endif
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
} else if (memcmp(config, "bridge.channel", 14) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
|
||||
} else if (memcmp(config, "bridge.secret", 13) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_secret);
|
||||
#endif
|
||||
} else {
|
||||
sprintf(reply, "??: %s", config);
|
||||
}
|
||||
/*
|
||||
* SET commands
|
||||
*/
|
||||
} else if (memcmp(command, "set ", 4) == 0) {
|
||||
const char* config = &command[4];
|
||||
if (memcmp(config, "af ", 3) == 0) {
|
||||
@@ -301,7 +383,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (sender_timestamp == 0 && memcmp(config, "prv.key ", 8) == 0) { // from serial command line only
|
||||
} else if (sender_timestamp == 0 &&
|
||||
memcmp(config, "prv.key ", 8) == 0) { // from serial command line only
|
||||
uint8_t prv_key[PRV_KEY_SIZE];
|
||||
bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
|
||||
if (success) {
|
||||
@@ -391,6 +474,55 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
_prefs->freq = atof(&config[5]);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - reboot to apply");
|
||||
#ifdef WITH_BRIDGE
|
||||
} else if (memcmp(config, "bridge.enabled ", 15) == 0) {
|
||||
_prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0;
|
||||
_callbacks->setBridgeState(_prefs->bridge_enabled);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "bridge.delay ", 13) == 0) {
|
||||
int delay = _atoi(&config[13]);
|
||||
if (delay >= 0 && delay <= 10000) {
|
||||
_prefs->bridge_delay = (uint16_t)delay;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: delay must be between 0-10000 ms");
|
||||
}
|
||||
} else if (memcmp(config, "bridge.source ", 14) == 0) {
|
||||
_prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
#endif
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
} else if (memcmp(config, "bridge.baud ", 12) == 0) {
|
||||
uint32_t baud = atoi(&config[12]);
|
||||
if (baud >= 9600 && baud <= 115200) {
|
||||
_prefs->bridge_baud = (uint32_t)baud;
|
||||
_callbacks->restartBridge();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: baud rate must be between 9600-115200");
|
||||
}
|
||||
#endif
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
} else if (memcmp(config, "bridge.channel ", 15) == 0) {
|
||||
int ch = atoi(&config[15]);
|
||||
if (ch > 0 && ch < 15) {
|
||||
_prefs->bridge_channel = (uint8_t)ch;
|
||||
_callbacks->restartBridge();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: channel must be between 1-14");
|
||||
}
|
||||
} else if (memcmp(config, "bridge.secret ", 14) == 0) {
|
||||
StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret));
|
||||
_callbacks->restartBridge();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
#endif
|
||||
} else {
|
||||
sprintf(reply, "unknown config: %s", config);
|
||||
}
|
||||
@@ -399,6 +531,128 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
sprintf(reply, "File system erase: %s", s ? "OK" : "Err");
|
||||
} else if (memcmp(command, "ver", 3) == 0) {
|
||||
sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
|
||||
} else if (memcmp(command, "board", 5) == 0) {
|
||||
sprintf(reply, "%s", _board->getManufacturerName());
|
||||
} else if (memcmp(command, "sensor get ", 11) == 0) {
|
||||
const char* key = command + 11;
|
||||
const char* val = _sensors->getSettingByKey(key);
|
||||
if (val != NULL) {
|
||||
sprintf(reply, "> %s", val);
|
||||
} else {
|
||||
strcpy(reply, "null");
|
||||
}
|
||||
} else if (memcmp(command, "sensor set ", 11) == 0) {
|
||||
strcpy(tmp, &command[11]);
|
||||
const char *parts[2];
|
||||
int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' ');
|
||||
const char *key = (num > 0) ? parts[0] : "";
|
||||
const char *value = (num > 1) ? parts[1] : "null";
|
||||
if (_sensors->setSettingValue(key, value)) {
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "can't find custom var");
|
||||
}
|
||||
} else if (memcmp(command, "sensor list", 11) == 0) {
|
||||
char* dp = reply;
|
||||
int start = 0;
|
||||
int end = _sensors->getNumSettings();
|
||||
if (strlen(command) > 11) {
|
||||
start = _atoi(command+12);
|
||||
}
|
||||
if (start >= end) {
|
||||
strcpy(reply, "no custom var");
|
||||
} else {
|
||||
sprintf(dp, "%d vars\n", end);
|
||||
dp = strchr(dp, 0);
|
||||
int i;
|
||||
for (i = start; i < end && (dp-reply < 134); i++) {
|
||||
sprintf(dp, "%s=%s\n",
|
||||
_sensors->getSettingName(i),
|
||||
_sensors->getSettingValue(i));
|
||||
dp = strchr(dp, 0);
|
||||
}
|
||||
if (i < end) {
|
||||
sprintf(dp, "... next:%d", i);
|
||||
} else {
|
||||
*(dp-1) = 0; // remove last CR
|
||||
}
|
||||
}
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
} else if (memcmp(command, "gps on", 6) == 0) {
|
||||
if (_sensors->setSettingValue("gps", "1")) {
|
||||
_prefs->gps_enabled = 1;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "gps toggle not found");
|
||||
}
|
||||
} else if (memcmp(command, "gps off", 7) == 0) {
|
||||
if (_sensors->setSettingValue("gps", "0")) {
|
||||
_prefs->gps_enabled = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "gps toggle not found");
|
||||
}
|
||||
} else if (memcmp(command, "gps sync", 8) == 0) {
|
||||
LocationProvider * l = _sensors->getLocationProvider();
|
||||
if (l != NULL) {
|
||||
l->syncTime();
|
||||
}
|
||||
} else if (memcmp(command, "gps setloc", 10) == 0) {
|
||||
_prefs->node_lat = _sensors->node_lat;
|
||||
_prefs->node_lon = _sensors->node_lon;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command, "gps advert", 10) == 0) {
|
||||
if (strlen(command) == 10) {
|
||||
switch (_prefs->advert_loc_policy) {
|
||||
case ADVERT_LOC_NONE:
|
||||
strcpy(reply, "> none");
|
||||
break;
|
||||
case ADVERT_LOC_PREFS:
|
||||
strcpy(reply, "> prefs");
|
||||
break;
|
||||
case ADVERT_LOC_SHARE:
|
||||
strcpy(reply, "> share");
|
||||
break;
|
||||
default:
|
||||
strcpy(reply, "error");
|
||||
}
|
||||
} else if (memcmp(command+11, "none", 4) == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_NONE;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command+11, "share", 5) == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_SHARE;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command+11, "prefs", 4) == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "error");
|
||||
}
|
||||
} else if (memcmp(command, "gps", 3) == 0) {
|
||||
LocationProvider * l = _sensors->getLocationProvider();
|
||||
if (l != NULL) {
|
||||
bool enabled = l->isEnabled(); // is EN pin on ?
|
||||
bool fix = l->isValid(); // has fix ?
|
||||
int sats = l->satellitesCount();
|
||||
bool active = !strcmp(_sensors->getSettingByKey("gps"), "1");
|
||||
if (enabled) {
|
||||
sprintf(reply, "on, %s, %s, %d sats",
|
||||
active?"active":"deactivated",
|
||||
fix?"fix":"no fix",
|
||||
sats);
|
||||
} else {
|
||||
strcpy(reply, "off");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Can't find GPS");
|
||||
}
|
||||
#endif
|
||||
} else if (memcmp(command, "log start", 9) == 0) {
|
||||
_callbacks->setLoggingOn(true);
|
||||
strcpy(reply, " logging on");
|
||||
@@ -411,6 +665,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) {
|
||||
_callbacks->dumpLogFile();
|
||||
strcpy(reply, " EOF");
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) {
|
||||
_callbacks->formatPacketStatsReply(reply);
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "stats-radio", 11) == 0 && (command[11] == 0 || command[11] == ' ')) {
|
||||
_callbacks->formatRadioStatsReply(reply);
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) {
|
||||
_callbacks->formatStatsReply(reply);
|
||||
} else {
|
||||
strcpy(reply, "Unknown command");
|
||||
}
|
||||
|
||||
@@ -2,30 +2,51 @@
|
||||
|
||||
#include "Mesh.h"
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
double node_lat, node_lon;
|
||||
char password[16];
|
||||
float freq;
|
||||
uint8_t tx_power_dbm;
|
||||
uint8_t disable_fwd;
|
||||
uint8_t advert_interval; // minutes / 2
|
||||
uint8_t flood_advert_interval; // hours
|
||||
float rx_delay_base;
|
||||
float tx_delay_factor;
|
||||
char guest_password[16];
|
||||
float direct_tx_delay_factor;
|
||||
uint32_t guard;
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
uint8_t allow_read_only;
|
||||
uint8_t multi_acks;
|
||||
float bw;
|
||||
uint8_t flood_max;
|
||||
uint8_t interference_threshold;
|
||||
uint8_t agc_reset_interval; // secs / 4
|
||||
#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
|
||||
#define WITH_BRIDGE
|
||||
#endif
|
||||
|
||||
#define ADVERT_LOC_NONE 0
|
||||
#define ADVERT_LOC_SHARE 1
|
||||
#define ADVERT_LOC_PREFS 2
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
double node_lat, node_lon;
|
||||
char password[16];
|
||||
float freq;
|
||||
uint8_t tx_power_dbm;
|
||||
uint8_t disable_fwd;
|
||||
uint8_t advert_interval; // minutes / 2
|
||||
uint8_t flood_advert_interval; // hours
|
||||
float rx_delay_base;
|
||||
float tx_delay_factor;
|
||||
char guest_password[16];
|
||||
float direct_tx_delay_factor;
|
||||
uint32_t guard;
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
uint8_t allow_read_only;
|
||||
uint8_t multi_acks;
|
||||
float bw;
|
||||
uint8_t flood_max;
|
||||
uint8_t interference_threshold;
|
||||
uint8_t agc_reset_interval; // secs / 4
|
||||
// Bridge settings
|
||||
uint8_t bridge_enabled; // boolean
|
||||
uint16_t bridge_delay; // milliseconds (default 500 ms)
|
||||
uint8_t bridge_pkt_src; // 0 = logTx, 1 = logRx (default logTx)
|
||||
uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200)
|
||||
uint8_t bridge_channel; // 1-14 (ESP-NOW only)
|
||||
char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only)
|
||||
// Gps settings
|
||||
uint8_t gps_enabled;
|
||||
uint32_t gps_interval; // in seconds
|
||||
uint8_t advert_loc_policy;
|
||||
uint32_t discovery_mod_timestamp;
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
@@ -46,10 +67,21 @@ public:
|
||||
virtual void removeNeighbor(const uint8_t* pubkey, int key_len) {
|
||||
// no op by default
|
||||
};
|
||||
virtual void formatStatsReply(char *reply) = 0;
|
||||
virtual void formatRadioStatsReply(char *reply) = 0;
|
||||
virtual void formatPacketStatsReply(char *reply) = 0;
|
||||
virtual mesh::LocalIdentity& getSelfId() = 0;
|
||||
virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0;
|
||||
virtual void clearStats() = 0;
|
||||
virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
|
||||
|
||||
virtual void setBridgeState(bool enable) {
|
||||
// no op by default
|
||||
};
|
||||
|
||||
virtual void restartBridge() {
|
||||
// no op by default
|
||||
};
|
||||
};
|
||||
|
||||
class CommonCLI {
|
||||
@@ -57,6 +89,7 @@ class CommonCLI {
|
||||
NodePrefs* _prefs;
|
||||
CommonCLICallbacks* _callbacks;
|
||||
mesh::MainBoard* _board;
|
||||
SensorManager* _sensors;
|
||||
char tmp[PRV_KEY_SIZE*2 + 4];
|
||||
|
||||
mesh::RTCClock* getRTCClock() { return _rtc; }
|
||||
@@ -64,10 +97,11 @@ class CommonCLI {
|
||||
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
|
||||
|
||||
public:
|
||||
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||
: _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { }
|
||||
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||
: _board(&board), _rtc(&rtc), _sensors(&sensors), _prefs(prefs), _callbacks(callbacks) { }
|
||||
|
||||
void loadPrefs(FILESYSTEM* _fs);
|
||||
void savePrefs(FILESYSTEM* _fs);
|
||||
void handleCommand(uint32_t sender_timestamp, const char* command, char* reply);
|
||||
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
|
||||
};
|
||||
|
||||
237
src/helpers/RegionMap.cpp
Normal file
237
src/helpers/RegionMap.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
#include "RegionMap.h"
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <SHA256.h>
|
||||
|
||||
RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
|
||||
next_id = 1; num_regions = 0; home_id = 0;
|
||||
wildcard.id = wildcard.parent = 0;
|
||||
wildcard.flags = 0; // default behaviour, allow flood and direct
|
||||
strcpy(wildcard.name, "*");
|
||||
}
|
||||
|
||||
bool RegionMap::is_name_char(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#';
|
||||
}
|
||||
|
||||
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
_fs->remove(filename);
|
||||
return _fs->open(filename, FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
return _fs->open(filename, "w");
|
||||
#else
|
||||
return _fs->open(filename, "w", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RegionMap::load(FILESYSTEM* _fs) {
|
||||
if (_fs->exists("/regions2")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/regions2", "r");
|
||||
#else
|
||||
File file = _fs->open("/regions2");
|
||||
#endif
|
||||
|
||||
if (file) {
|
||||
uint8_t pad[128];
|
||||
|
||||
num_regions = 0; next_id = 1; home_id = 0;
|
||||
|
||||
bool success = file.read(pad, 5) == 5; // reserved header
|
||||
success = success && file.read((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
|
||||
success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
|
||||
success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
|
||||
|
||||
if (success) {
|
||||
while (num_regions < MAX_REGION_ENTRIES) {
|
||||
auto r = ®ions[num_regions];
|
||||
|
||||
success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
|
||||
success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
|
||||
success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
|
||||
success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
|
||||
success = success && file.read(pad, sizeof(pad)) == sizeof(pad);
|
||||
|
||||
if (!success) break; // EOF
|
||||
|
||||
if (r->id >= next_id) { // make sure next_id is valid
|
||||
next_id = r->id + 1;
|
||||
}
|
||||
num_regions++;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
bool RegionMap::save(FILESYSTEM* _fs) {
|
||||
File file = openWrite(_fs, "/regions2");
|
||||
if (file) {
|
||||
uint8_t pad[128];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
|
||||
bool success = file.write(pad, 5) == 5; // reserved header
|
||||
success = success && file.write((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
|
||||
success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
|
||||
success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
|
||||
|
||||
if (success) {
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto r = ®ions[i];
|
||||
|
||||
success = file.write((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
|
||||
success = success && file.write((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
|
||||
success = success && file.write((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
|
||||
success = success && file.write((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
|
||||
success = success && file.write(pad, sizeof(pad)) == sizeof(pad);
|
||||
if (!success) break; // write failed
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) {
|
||||
const char* sp = name; // check for illegal name chars
|
||||
while (*sp) {
|
||||
if (!is_name_char(*sp)) return NULL; // error
|
||||
sp++;
|
||||
}
|
||||
|
||||
auto region = findByName(name);
|
||||
if (region) {
|
||||
if (region->id == parent_id) return NULL; // ERROR: invalid parent!
|
||||
|
||||
region->parent = parent_id; // re-parent / move this region in the hierarchy
|
||||
} else {
|
||||
if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL; // full!
|
||||
|
||||
region = ®ions[num_regions++]; // alloc new RegionEntry
|
||||
region->flags = REGION_DENY_FLOOD; // DENY by default
|
||||
region->id = id == 0 ? next_id++ : id;
|
||||
StrHelper::strncpy(region->name, name, sizeof(region->name));
|
||||
region->parent = parent_id;
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) {
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param)
|
||||
TransportKey keys[4];
|
||||
int num;
|
||||
if (region->name[0] == '#') { // auto hashtag region
|
||||
_store->getAutoKeyFor(region->id, region->name, keys[0]);
|
||||
num = 1;
|
||||
} else {
|
||||
num = _store->loadKeysFor(region->id, keys, 4);
|
||||
}
|
||||
for (int j = 0; j < num; j++) {
|
||||
uint16_t code = keys[j].calcTransportCode(packet);
|
||||
if (packet->transport_codes[0] == code) { // a match!!
|
||||
return region;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL; // no matches
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findByName(const char* name) {
|
||||
if (strcmp(name, "*") == 0) return &wildcard;
|
||||
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if (strcmp(name, region->name) == 0) return region;
|
||||
}
|
||||
return NULL; // not found
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findByNamePrefix(const char* prefix) {
|
||||
if (strcmp(prefix, "*") == 0) return &wildcard;
|
||||
|
||||
RegionEntry* partial = NULL;
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if (strcmp(prefix, region->name) == 0) return region; // is a complete match, preference this one
|
||||
if (memcmp(prefix, region->name, strlen(prefix)) == 0) {
|
||||
partial = region;
|
||||
}
|
||||
}
|
||||
return partial;
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findById(uint16_t id) {
|
||||
if (id == 0) return &wildcard; // special root Region
|
||||
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if (region->id == id) return region;
|
||||
}
|
||||
return NULL; // not found
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::getHomeRegion() {
|
||||
return findById(home_id);
|
||||
}
|
||||
|
||||
void RegionMap::setHomeRegion(const RegionEntry* home) {
|
||||
home_id = home ? home->id : 0;
|
||||
}
|
||||
|
||||
bool RegionMap::removeRegion(const RegionEntry& region) {
|
||||
if (region.id == 0) return false; // failed (cannot remove the wildcard Region)
|
||||
|
||||
int i; // first check region has no child regions
|
||||
for (i = 0; i < num_regions; i++) {
|
||||
if (regions[i].parent == region.id) return false; // failed (must remove child Regions first)
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while (i < num_regions) {
|
||||
if (region.id == regions[i].id) break;
|
||||
i++;
|
||||
}
|
||||
if (i >= num_regions) return false; // failed (not found)
|
||||
|
||||
num_regions--; // remove from regions array
|
||||
while (i < num_regions) {
|
||||
regions[i] = regions[i + 1];
|
||||
i++;
|
||||
}
|
||||
return true; // success
|
||||
}
|
||||
|
||||
bool RegionMap::clear() {
|
||||
num_regions = 0;
|
||||
return true; // success
|
||||
}
|
||||
|
||||
void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out) const {
|
||||
for (int i = 0; i < indent; i++) {
|
||||
out.print(' ');
|
||||
}
|
||||
|
||||
if (parent->flags & REGION_DENY_FLOOD) {
|
||||
out.printf("%s%s\n", parent->name, parent->id == home_id ? "^" : "");
|
||||
} else {
|
||||
out.printf("%s%s F\n", parent->name, parent->id == home_id ? "^" : "");
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto r = ®ions[i];
|
||||
if (r->parent == parent->id) {
|
||||
printChildRegions(indent + 1, r, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegionMap::exportTo(Stream& out) const {
|
||||
printChildRegions(0, &wildcard, out); // recursive
|
||||
}
|
||||
52
src/helpers/RegionMap.h
Normal file
52
src/helpers/RegionMap.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Packet.h>
|
||||
#include "TransportKeyStore.h"
|
||||
|
||||
#ifndef MAX_REGION_ENTRIES
|
||||
#define MAX_REGION_ENTRIES 32
|
||||
#endif
|
||||
|
||||
#define REGION_DENY_FLOOD 0x01
|
||||
#define REGION_DENY_DIRECT 0x02 // reserved for future
|
||||
|
||||
struct RegionEntry {
|
||||
uint16_t id;
|
||||
uint16_t parent;
|
||||
uint8_t flags;
|
||||
char name[31];
|
||||
};
|
||||
|
||||
class RegionMap {
|
||||
TransportKeyStore* _store;
|
||||
uint16_t next_id, home_id;
|
||||
uint16_t num_regions;
|
||||
RegionEntry regions[MAX_REGION_ENTRIES];
|
||||
RegionEntry wildcard;
|
||||
|
||||
void printChildRegions(int indent, const RegionEntry* parent, Stream& out) const;
|
||||
|
||||
public:
|
||||
RegionMap(TransportKeyStore& store);
|
||||
|
||||
static bool is_name_char(char c);
|
||||
|
||||
bool load(FILESYSTEM* _fs);
|
||||
bool save(FILESYSTEM* _fs);
|
||||
|
||||
RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0);
|
||||
RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask);
|
||||
RegionEntry& getWildcard() { return wildcard; }
|
||||
RegionEntry* findByName(const char* name);
|
||||
RegionEntry* findByNamePrefix(const char* prefix);
|
||||
RegionEntry* findById(uint16_t id);
|
||||
RegionEntry* getHomeRegion(); // NOTE: can be NULL
|
||||
void setHomeRegion(const RegionEntry* home);
|
||||
bool removeRegion(const RegionEntry& region);
|
||||
bool clear();
|
||||
void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; }
|
||||
int getCount() const { return num_regions; }
|
||||
|
||||
void exportTo(Stream& out) const;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <CayenneLPP.h>
|
||||
#include "sensors/LocationProvider.h"
|
||||
|
||||
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
|
||||
#define TELEM_PERM_LOCATION 0x02
|
||||
@@ -21,4 +22,16 @@ public:
|
||||
virtual const char* getSettingName(int i) const { return NULL; }
|
||||
virtual const char* getSettingValue(int i) const { return NULL; }
|
||||
virtual bool setSettingValue(const char* name, const char* value) { return false; }
|
||||
virtual LocationProvider* getLocationProvider() { return NULL; }
|
||||
|
||||
// Helper functions to manage setting by keys (useful in many places ...)
|
||||
const char* getSettingByKey(const char* key) {
|
||||
int num = getNumSettings();
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (strcmp(getSettingName(i), key) == 0) {
|
||||
return getSettingValue(i);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
54
src/helpers/StatsFormatHelper.h
Normal file
54
src/helpers/StatsFormatHelper.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "Mesh.h"
|
||||
|
||||
class StatsFormatHelper {
|
||||
public:
|
||||
static void formatCoreStats(char* reply,
|
||||
mesh::MainBoard& board,
|
||||
mesh::MillisecondClock& ms,
|
||||
uint16_t err_flags,
|
||||
mesh::PacketManager* mgr) {
|
||||
sprintf(reply,
|
||||
"{\"battery_mv\":%u,\"uptime_secs\":%u,\"errors\":%u,\"queue_len\":%u}",
|
||||
board.getBattMilliVolts(),
|
||||
ms.getMillis() / 1000,
|
||||
err_flags,
|
||||
mgr->getOutboundCount(0xFFFFFFFF)
|
||||
);
|
||||
}
|
||||
|
||||
template<typename RadioDriverType>
|
||||
static void formatRadioStats(char* reply,
|
||||
mesh::Radio* radio,
|
||||
RadioDriverType& driver,
|
||||
uint32_t total_air_time_ms,
|
||||
uint32_t total_rx_air_time_ms) {
|
||||
sprintf(reply,
|
||||
"{\"noise_floor\":%d,\"last_rssi\":%d,\"last_snr\":%.2f,\"tx_air_secs\":%u,\"rx_air_secs\":%u}",
|
||||
(int16_t)radio->getNoiseFloor(),
|
||||
(int16_t)driver.getLastRSSI(),
|
||||
driver.getLastSNR(),
|
||||
total_air_time_ms / 1000,
|
||||
total_rx_air_time_ms / 1000
|
||||
);
|
||||
}
|
||||
|
||||
template<typename RadioDriverType>
|
||||
static void formatPacketStats(char* reply,
|
||||
RadioDriverType& driver,
|
||||
uint32_t n_sent_flood,
|
||||
uint32_t n_sent_direct,
|
||||
uint32_t n_recv_flood,
|
||||
uint32_t n_recv_direct) {
|
||||
sprintf(reply,
|
||||
"{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u}",
|
||||
driver.getPacketsRecv(),
|
||||
driver.getPacketsSent(),
|
||||
n_sent_flood,
|
||||
n_sent_direct,
|
||||
n_recv_flood,
|
||||
n_recv_direct
|
||||
);
|
||||
}
|
||||
};
|
||||
92
src/helpers/TransportKeyStore.cpp
Normal file
92
src/helpers/TransportKeyStore.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "TransportKeyStore.h"
|
||||
#include <SHA256.h>
|
||||
|
||||
uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const {
|
||||
uint16_t code;
|
||||
SHA256 sha;
|
||||
sha.resetHMAC(key, sizeof(key));
|
||||
uint8_t type = packet->getPayloadType();
|
||||
sha.update(&type, 1);
|
||||
sha.update(packet->payload, packet->payload_len);
|
||||
sha.finalizeHMAC(key, sizeof(key), &code, 2);
|
||||
if (code == 0) { // reserve codes 0000 and FFFF
|
||||
code++;
|
||||
} else if (code == 0xFFFF) {
|
||||
code--;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
bool TransportKey::isNull() const {
|
||||
for (int i = 0; i < sizeof(key); i++) {
|
||||
if (key[i]) return false;
|
||||
}
|
||||
return true; // key is all zeroes
|
||||
}
|
||||
|
||||
void TransportKeyStore::putCache(uint16_t id, const TransportKey& key) {
|
||||
if (num_cache < MAX_TKS_ENTRIES) {
|
||||
cache_ids[num_cache] = id;
|
||||
cache_keys[num_cache] = key;
|
||||
num_cache++;
|
||||
} else {
|
||||
// TODO: evict oldest cache entry
|
||||
}
|
||||
}
|
||||
|
||||
void TransportKeyStore::getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest) {
|
||||
for (int i = 0; i < num_cache; i++) { // first, check cache
|
||||
if (cache_ids[i] == id) { // cache hit!
|
||||
dest = cache_keys[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
// calc key for publicly-known hashtag region name
|
||||
SHA256 sha;
|
||||
sha.update(name, strlen(name));
|
||||
sha.finalize(&dest.key, sizeof(dest.key));
|
||||
|
||||
putCache(id, dest);
|
||||
}
|
||||
|
||||
int TransportKeyStore::loadKeysFor(uint16_t id, TransportKey keys[], int max_num) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < num_cache && n < max_num; i++) { // first, check cache
|
||||
if (cache_ids[i] == id) {
|
||||
keys[n++] = cache_keys[i];
|
||||
}
|
||||
}
|
||||
if (n > 0) return n; // cache hit!
|
||||
|
||||
// TODO: retrieve from difficult-to-copy keystore
|
||||
|
||||
// store in cache (if room)
|
||||
for (int i = 0; i < n; i++) {
|
||||
putCache(id, keys[i]);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
bool TransportKeyStore::saveKeysFor(uint16_t id, const TransportKey keys[], int num) {
|
||||
invalidateCache();
|
||||
|
||||
// TODO: update hardware keystore
|
||||
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
bool TransportKeyStore::removeKeys(uint16_t id) {
|
||||
invalidateCache();
|
||||
|
||||
// TODO: remove from hardware keystore
|
||||
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
bool TransportKeyStore::clear() {
|
||||
invalidateCache();
|
||||
|
||||
// TODO: clear hardware keystore
|
||||
|
||||
return false; // failed
|
||||
}
|
||||
31
src/helpers/TransportKeyStore.h
Normal file
31
src/helpers/TransportKeyStore.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Packet.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
|
||||
struct TransportKey {
|
||||
uint8_t key[16];
|
||||
|
||||
uint16_t calcTransportCode(const mesh::Packet* packet) const;
|
||||
bool isNull() const;
|
||||
};
|
||||
|
||||
#define MAX_TKS_ENTRIES 16
|
||||
|
||||
class TransportKeyStore {
|
||||
uint16_t cache_ids[MAX_TKS_ENTRIES];
|
||||
TransportKey cache_keys[MAX_TKS_ENTRIES];
|
||||
int num_cache;
|
||||
|
||||
void putCache(uint16_t id, const TransportKey& key);
|
||||
void invalidateCache() { num_cache = 0; }
|
||||
|
||||
public:
|
||||
TransportKeyStore() { num_cache = 0; }
|
||||
void getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest);
|
||||
int loadKeysFor(uint16_t id, TransportKey keys[], int max_num);
|
||||
bool saveKeysFor(uint16_t id, const TransportKey keys[], int num);
|
||||
bool removeKeys(uint16_t id);
|
||||
bool clear();
|
||||
};
|
||||
@@ -19,6 +19,13 @@ void StrHelper::strzcpy(char* dest, const char* src, size_t buf_sz) {
|
||||
}
|
||||
}
|
||||
|
||||
bool StrHelper::isBlank(const char* str) {
|
||||
while (*str) {
|
||||
if (*str++ != ' ') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
union int32_Float_t
|
||||
@@ -132,3 +139,23 @@ const char* StrHelper::ftoa(float f) {
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
uint32_t StrHelper::fromHex(const char* src) {
|
||||
uint32_t n = 0;
|
||||
while (*src) {
|
||||
if (*src >= '0' && *src <= '9') {
|
||||
n <<= 4;
|
||||
n |= (*src - '0');
|
||||
} else if (*src >= 'A' && *src <= 'F') {
|
||||
n <<= 4;
|
||||
n |= (*src - 'A' + 10);
|
||||
} else if (*src >= 'a' && *src <= 'f') {
|
||||
n <<= 4;
|
||||
n |= (*src - 'a' + 10);
|
||||
} else {
|
||||
break; // non-hex char encountered, stop parsing
|
||||
}
|
||||
src++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -12,4 +12,6 @@ public:
|
||||
static void strncpy(char* dest, const char* src, size_t buf_sz);
|
||||
static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls
|
||||
static const char* ftoa(float f);
|
||||
static bool isBlank(const char* str);
|
||||
static uint32_t fromHex(const char* src);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
bool BridgeBase::isRunning() const {
|
||||
return _initialized;
|
||||
}
|
||||
|
||||
const char *BridgeBase::getLogDateTime() {
|
||||
static char tmp[32];
|
||||
uint32_t now = _rtc->getCurrentTime();
|
||||
@@ -28,8 +32,16 @@ bool BridgeBase::validateChecksum(const uint8_t *data, size_t len, uint16_t rece
|
||||
}
|
||||
|
||||
void BridgeBase::handleReceivedPacket(mesh::Packet *packet) {
|
||||
// Guard against uninitialized state
|
||||
if (_initialized == false) {
|
||||
BRIDGE_DEBUG_PRINTLN("RX packet received before initialization\n");
|
||||
_mgr->free(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_seen_packets.hasSeen(packet)) {
|
||||
_mgr->queueInbound(packet, millis() + BRIDGE_DELAY);
|
||||
// bridge_delay provides a buffer to prevent immediate processing conflicts in the mesh network.
|
||||
_mgr->queueInbound(packet, millis() + _prefs->bridge_delay);
|
||||
} else {
|
||||
_mgr->free(packet);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "helpers/AbstractBridge.h"
|
||||
#include "helpers/CommonCLI.h"
|
||||
#include "helpers/SimpleMeshTables.h"
|
||||
|
||||
#include <RTClib.h>
|
||||
@@ -21,6 +22,13 @@ class BridgeBase : public AbstractBridge {
|
||||
public:
|
||||
virtual ~BridgeBase() = default;
|
||||
|
||||
/**
|
||||
* @brief Gets the current state of the bridge.
|
||||
*
|
||||
* @return true if the bridge is initialized and running, false otherwise.
|
||||
*/
|
||||
bool isRunning() const override;
|
||||
|
||||
/**
|
||||
* @brief Common magic number used by all bridge implementations for packet identification
|
||||
*
|
||||
@@ -41,31 +49,31 @@ public:
|
||||
static constexpr uint16_t BRIDGE_LENGTH_SIZE = sizeof(uint16_t);
|
||||
static constexpr uint16_t BRIDGE_CHECKSUM_SIZE = sizeof(uint16_t);
|
||||
|
||||
/**
|
||||
* @brief Default delay in milliseconds for scheduling inbound packet processing
|
||||
*
|
||||
* It provides a buffer to prevent immediate processing conflicts in the mesh network.
|
||||
* Used in handleReceivedPacket() as: millis() + BRIDGE_DELAY
|
||||
*/
|
||||
static constexpr uint16_t BRIDGE_DELAY = 500; // TODO: maybe too high ?
|
||||
|
||||
protected:
|
||||
/** Tracks bridge state */
|
||||
bool _initialized = false;
|
||||
|
||||
/** Packet manager for allocating and queuing mesh packets */
|
||||
mesh::PacketManager *_mgr;
|
||||
|
||||
/** RTC clock for timestamping debug messages */
|
||||
mesh::RTCClock *_rtc;
|
||||
|
||||
/** Node preferences for configuration settings */
|
||||
NodePrefs *_prefs;
|
||||
|
||||
/** Tracks seen packets to prevent loops in broadcast communications */
|
||||
SimpleMeshTables _seen_packets;
|
||||
|
||||
/**
|
||||
* @brief Constructs a BridgeBase instance
|
||||
*
|
||||
* @param prefs Node preferences for configuration settings
|
||||
* @param mgr PacketManager for allocating and queuing packets
|
||||
* @param rtc RTCClock for timestamping debug messages
|
||||
*/
|
||||
BridgeBase(mesh::PacketManager *mgr, mesh::RTCClock *rtc) : _mgr(mgr), _rtc(rtc) {}
|
||||
BridgeBase(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: _prefs(prefs), _mgr(mgr), _rtc(rtc) {}
|
||||
|
||||
/**
|
||||
* @brief Gets formatted date/time string for logging
|
||||
|
||||
@@ -21,18 +21,26 @@ void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) {
|
||||
}
|
||||
}
|
||||
|
||||
ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: BridgeBase(mgr, rtc), _rx_buffer_pos(0) {
|
||||
ESPNowBridge::ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: BridgeBase(prefs, mgr, rtc), _rx_buffer_pos(0) {
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
void ESPNowBridge::begin() {
|
||||
BRIDGE_DEBUG_PRINTLN("Initializing...\n");
|
||||
|
||||
// Initialize WiFi in station mode
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
// Set wifi channel
|
||||
if (esp_wifi_set_channel(_prefs->bridge_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
|
||||
BRIDGE_DEBUG_PRINTLN("Error setting WIFI channel to %d\n", _prefs->bridge_channel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize ESP-NOW
|
||||
if (esp_now_init() != ESP_OK) {
|
||||
Serial.printf("%s: ESPNOW BRIDGE: Error initializing ESP-NOW\n", getLogDateTime());
|
||||
BRIDGE_DEBUG_PRINTLN("Error initializing ESP-NOW\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,13 +52,41 @@ void ESPNowBridge::begin() {
|
||||
esp_now_peer_info_t peerInfo = {};
|
||||
memset(&peerInfo, 0, sizeof(peerInfo));
|
||||
memset(peerInfo.peer_addr, 0xFF, ESP_NOW_ETH_ALEN); // Broadcast address
|
||||
peerInfo.channel = 0;
|
||||
peerInfo.channel = _prefs->bridge_channel;
|
||||
peerInfo.encrypt = false;
|
||||
|
||||
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
|
||||
Serial.printf("%s: ESPNOW BRIDGE: Failed to add broadcast peer\n", getLogDateTime());
|
||||
BRIDGE_DEBUG_PRINTLN("Failed to add broadcast peer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Update bridge state
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void ESPNowBridge::end() {
|
||||
BRIDGE_DEBUG_PRINTLN("Stopping...\n");
|
||||
|
||||
// Remove broadcast peer
|
||||
uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
if (esp_now_del_peer(broadcastAddress) != ESP_OK) {
|
||||
BRIDGE_DEBUG_PRINTLN("Error removing broadcast peer\n");
|
||||
}
|
||||
|
||||
// Unregister callbacks
|
||||
esp_now_register_recv_cb(nullptr);
|
||||
esp_now_register_send_cb(nullptr);
|
||||
|
||||
// Deinitialize ESP-NOW
|
||||
if (esp_now_deinit() != ESP_OK) {
|
||||
BRIDGE_DEBUG_PRINTLN("Error deinitializing ESP-NOW\n");
|
||||
}
|
||||
|
||||
// Turn off WiFi
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
// Update bridge state
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
void ESPNowBridge::loop() {
|
||||
@@ -58,35 +94,29 @@ void ESPNowBridge::loop() {
|
||||
}
|
||||
|
||||
void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) {
|
||||
size_t keyLen = strlen(_secret);
|
||||
size_t keyLen = strlen(_prefs->bridge_secret);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
data[i] ^= _secret[i % keyLen];
|
||||
data[i] ^= _prefs->bridge_secret[i % keyLen];
|
||||
}
|
||||
}
|
||||
|
||||
void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t len) {
|
||||
// Ignore packets that are too small to contain header + checksum
|
||||
if (len < (BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE)) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX packet too small, len=%d\n", getLogDateTime(), len);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX packet too small, len=%d\n", len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate total packet size
|
||||
if (len > MAX_ESPNOW_PACKET_SIZE) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX packet too large, len=%d\n", getLogDateTime(), len);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX packet too large, len=%d\n", len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check packet header magic
|
||||
uint16_t received_magic = (data[0] << 8) | data[1];
|
||||
if (received_magic != BRIDGE_PACKET_MAGIC) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX invalid magic 0x%04X\n", getLogDateTime(), received_magic);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX invalid magic 0x%04X\n", received_magic);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,16 +134,11 @@ void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t l
|
||||
|
||||
if (!validateChecksum(decrypted + BRIDGE_CHECKSUM_SIZE, payloadLen, received_checksum)) {
|
||||
// Failed to decrypt - likely from a different network
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX checksum mismatch, rcv=0x%04X\n", getLogDateTime(),
|
||||
received_checksum);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX checksum mismatch, rcv=0x%04X\n", received_checksum);
|
||||
return;
|
||||
}
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX, payload_len=%d\n", getLogDateTime(), payloadLen);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX, payload_len=%d\n", payloadLen);
|
||||
|
||||
// Create mesh packet
|
||||
mesh::Packet *pkt = _instance->_mgr->allocNew();
|
||||
@@ -130,31 +155,27 @@ void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t sta
|
||||
// Could add transmission error handling here if needed
|
||||
}
|
||||
|
||||
void ESPNowBridge::onPacketReceived(mesh::Packet *packet) {
|
||||
handleReceivedPacket(packet);
|
||||
}
|
||||
void ESPNowBridge::sendPacket(mesh::Packet *packet) {
|
||||
// Guard against uninitialized state
|
||||
if (_initialized == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
|
||||
// First validate the packet pointer
|
||||
if (!packet) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: TX invalid packet pointer\n", getLogDateTime());
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("TX invalid packet pointer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_seen_packets.hasSeen(packet)) {
|
||||
|
||||
// Create a temporary buffer just for size calculation and reuse for actual writing
|
||||
uint8_t sizingBuffer[MAX_PAYLOAD_SIZE];
|
||||
uint16_t meshPacketLen = packet->writeTo(sizingBuffer);
|
||||
|
||||
// Check if packet fits within our maximum payload size
|
||||
if (meshPacketLen > MAX_PAYLOAD_SIZE) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(),
|
||||
meshPacketLen, MAX_PAYLOAD_SIZE);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", meshPacketLen,
|
||||
MAX_PAYLOAD_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,14 +204,16 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
|
||||
uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
esp_err_t result = esp_now_send(broadcastAddress, buffer, totalPacketSize);
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
if (result == ESP_OK) {
|
||||
Serial.printf("%s: ESPNOW BRIDGE: TX, len=%d\n", getLogDateTime(), meshPacketLen);
|
||||
BRIDGE_DEBUG_PRINTLN("TX, len=%d\n", meshPacketLen);
|
||||
} else {
|
||||
Serial.printf("%s: ESPNOW BRIDGE: TX FAILED!\n", getLogDateTime());
|
||||
BRIDGE_DEBUG_PRINTLN("TX FAILED!\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ESPNowBridge::onPacketReceived(mesh::Packet *packet) {
|
||||
handleReceivedPacket(packet);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
|
||||
#ifndef WITH_ESPNOW_BRIDGE_SECRET
|
||||
#error WITH_ESPNOW_BRIDGE_SECRET must be defined to use ESPNowBridge
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Bridge implementation using ESP-NOW protocol for packet transport
|
||||
*
|
||||
@@ -36,11 +32,11 @@
|
||||
*
|
||||
* Configuration:
|
||||
* - Define WITH_ESPNOW_BRIDGE to enable this bridge
|
||||
* - Define WITH_ESPNOW_BRIDGE_SECRET with a string to set the network encryption key
|
||||
* - Define _prefs->bridge_secret with a string to set the network encryption key
|
||||
*
|
||||
* Network Isolation:
|
||||
* Multiple independent mesh networks can coexist by using different
|
||||
* WITH_ESPNOW_BRIDGE_SECRET values. Packets encrypted with a different key will
|
||||
* _prefs->bridge_secret values. Packets encrypted with a different key will
|
||||
* fail the checksum validation and be discarded.
|
||||
*/
|
||||
class ESPNowBridge : public BridgeBase {
|
||||
@@ -73,17 +69,11 @@ private:
|
||||
/** Current position in receive buffer */
|
||||
size_t _rx_buffer_pos;
|
||||
|
||||
/**
|
||||
* Network encryption key from build define
|
||||
* Must be defined with WITH_ESPNOW_BRIDGE_SECRET
|
||||
* Used for XOR encryption to isolate different mesh networks
|
||||
*/
|
||||
const char *_secret = WITH_ESPNOW_BRIDGE_SECRET;
|
||||
|
||||
/**
|
||||
* Performs XOR encryption/decryption of data
|
||||
* Used to isolate different mesh networks
|
||||
*
|
||||
* Uses WITH_ESPNOW_BRIDGE_SECRET as the key in a simple XOR operation.
|
||||
* Uses _prefs->bridge_secret as the key in a simple XOR operation.
|
||||
* The same operation is used for both encryption and decryption.
|
||||
* While not cryptographically secure, it provides basic network isolation.
|
||||
*
|
||||
@@ -115,10 +105,11 @@ public:
|
||||
/**
|
||||
* Constructs an ESPNowBridge instance
|
||||
*
|
||||
* @param prefs Node preferences for configuration settings
|
||||
* @param mgr PacketManager for allocating and queuing packets
|
||||
* @param rtc RTCClock for timestamping debug messages
|
||||
*/
|
||||
ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
|
||||
/**
|
||||
* Initializes the ESP-NOW bridge
|
||||
@@ -130,6 +121,16 @@ public:
|
||||
*/
|
||||
void begin() override;
|
||||
|
||||
/**
|
||||
* Stops the ESP-NOW bridge
|
||||
*
|
||||
* - Removes broadcast peer
|
||||
* - Unregisters callbacks
|
||||
* - Deinitializes ESP-NOW protocol
|
||||
* - Turns off WiFi to release radio resources
|
||||
*/
|
||||
void end() override;
|
||||
|
||||
/**
|
||||
* Main loop handler
|
||||
* ESP-NOW is callback-based, so this is currently empty
|
||||
@@ -150,7 +151,7 @@ public:
|
||||
*
|
||||
* @param packet The mesh packet to transmit
|
||||
*/
|
||||
void onPacketTransmitted(mesh::Packet *packet) override;
|
||||
void sendPacket(mesh::Packet *packet) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
|
||||
RS232Bridge::RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: BridgeBase(mgr, rtc), _serial(&serial) {}
|
||||
RS232Bridge::RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: BridgeBase(prefs, mgr, rtc), _serial(&serial) {}
|
||||
|
||||
void RS232Bridge::begin() {
|
||||
BRIDGE_DEBUG_PRINTLN("Initializing at %d baud...\n", _prefs->bridge_baud);
|
||||
#if !defined(WITH_RS232_BRIDGE_RX) || !defined(WITH_RS232_BRIDGE_TX)
|
||||
#error "WITH_RS232_BRIDGE_RX and WITH_RS232_BRIDGE_TX must be defined"
|
||||
#endif
|
||||
@@ -25,53 +26,26 @@ void RS232Bridge::begin() {
|
||||
#else
|
||||
#error RS232Bridge was not tested on the current platform
|
||||
#endif
|
||||
((HardwareSerial *)_serial)->begin(115200);
|
||||
((HardwareSerial *)_serial)->begin(_prefs->bridge_baud);
|
||||
|
||||
// Update bridge state
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) {
|
||||
// First validate the packet pointer
|
||||
if (!packet) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: TX invalid packet pointer\n", getLogDateTime());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
void RS232Bridge::end() {
|
||||
BRIDGE_DEBUG_PRINTLN("Stopping...\n");
|
||||
((HardwareSerial *)_serial)->end();
|
||||
|
||||
if (!_seen_packets.hasSeen(packet)) {
|
||||
|
||||
uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
|
||||
uint16_t len = packet->writeTo(buffer + 4);
|
||||
|
||||
// Check if packet fits within our maximum payload size
|
||||
if (len > (MAX_TRANS_UNIT + 1)) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), len,
|
||||
MAX_TRANS_UNIT + 1);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Build packet header
|
||||
buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte
|
||||
buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Magic low byte
|
||||
buffer[2] = (len >> 8) & 0xFF; // Length high byte
|
||||
buffer[3] = len & 0xFF; // Length low byte
|
||||
|
||||
// Calculate checksum over the payload
|
||||
uint16_t checksum = fletcher16(buffer + 4, len);
|
||||
buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte
|
||||
buffer[5 + len] = checksum & 0xFF; // Checksum low byte
|
||||
|
||||
// Send complete packet
|
||||
_serial->write(buffer, len + SERIAL_OVERHEAD);
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum);
|
||||
#endif
|
||||
}
|
||||
// Update bridge state
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
void RS232Bridge::loop() {
|
||||
// Guard against uninitialized state
|
||||
if (_initialized == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (_serial->available()) {
|
||||
uint8_t b = _serial->read();
|
||||
|
||||
@@ -97,9 +71,7 @@ void RS232Bridge::loop() {
|
||||
|
||||
// Validate length field
|
||||
if (len > (MAX_TRANS_UNIT + 1)) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX invalid length %d, resetting\n", getLogDateTime(), len);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX invalid length %d, resetting\n", len);
|
||||
_rx_buffer_pos = 0; // Invalid length, reset
|
||||
continue;
|
||||
}
|
||||
@@ -108,30 +80,20 @@ void RS232Bridge::loop() {
|
||||
uint16_t received_checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len];
|
||||
|
||||
if (validateChecksum(_rx_buffer + 4, len, received_checksum)) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len,
|
||||
received_checksum);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX, len=%d crc=0x%04x\n", len, received_checksum);
|
||||
mesh::Packet *pkt = _mgr->allocNew();
|
||||
if (pkt) {
|
||||
if (pkt->readFrom(_rx_buffer + 4, len)) {
|
||||
onPacketReceived(pkt);
|
||||
} else {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX failed to parse packet\n", getLogDateTime());
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX failed to parse packet\n");
|
||||
_mgr->free(pkt);
|
||||
}
|
||||
} else {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX failed to allocate packet\n", getLogDateTime());
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX failed to allocate packet\n");
|
||||
}
|
||||
} else {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX checksum mismatch, rcv=0x%04x\n", getLogDateTime(),
|
||||
received_checksum);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX checksum mismatch, rcv=0x%04x\n", received_checksum);
|
||||
}
|
||||
_rx_buffer_pos = 0; // Reset for next packet
|
||||
}
|
||||
@@ -140,6 +102,48 @@ void RS232Bridge::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void RS232Bridge::sendPacket(mesh::Packet *packet) {
|
||||
// Guard against uninitialized state
|
||||
if (_initialized == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First validate the packet pointer
|
||||
if (!packet) {
|
||||
BRIDGE_DEBUG_PRINTLN("TX invalid packet pointer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_seen_packets.hasSeen(packet)) {
|
||||
|
||||
uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
|
||||
uint16_t len = packet->writeTo(buffer + 4);
|
||||
|
||||
// Check if packet fits within our maximum payload size
|
||||
if (len > (MAX_TRANS_UNIT + 1)) {
|
||||
BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len,
|
||||
MAX_TRANS_UNIT + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build packet header
|
||||
buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte
|
||||
buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Magic low byte
|
||||
buffer[2] = (len >> 8) & 0xFF; // Length high byte
|
||||
buffer[3] = len & 0xFF; // Length low byte
|
||||
|
||||
// Calculate checksum over the payload
|
||||
uint16_t checksum = fletcher16(buffer + 4, len);
|
||||
buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte
|
||||
buffer[5 + len] = checksum & 0xFF; // Checksum low byte
|
||||
|
||||
// Send complete packet
|
||||
_serial->write(buffer, len + SERIAL_OVERHEAD);
|
||||
|
||||
BRIDGE_DEBUG_PRINTLN("TX, len=%d crc=0x%04x\n", len, checksum);
|
||||
}
|
||||
}
|
||||
|
||||
void RS232Bridge::onPacketReceived(mesh::Packet *packet) {
|
||||
handleReceivedPacket(packet);
|
||||
}
|
||||
|
||||
@@ -49,11 +49,12 @@ public:
|
||||
/**
|
||||
* @brief Constructs an RS232Bridge instance
|
||||
*
|
||||
* @param prefs Node preferences for configuration settings
|
||||
* @param serial The hardware serial port to use
|
||||
* @param mgr PacketManager for allocating and queuing packets
|
||||
* @param rtc RTCClock for timestamping debug messages
|
||||
*/
|
||||
RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
|
||||
/**
|
||||
* Initializes the RS232 bridge
|
||||
@@ -65,6 +66,12 @@ public:
|
||||
*/
|
||||
void begin() override;
|
||||
|
||||
/**
|
||||
* Stops the RS232 bridge
|
||||
*
|
||||
*/
|
||||
void end() override;
|
||||
|
||||
/**
|
||||
* @brief Main loop handler for processing incoming serial data
|
||||
*
|
||||
@@ -90,7 +97,7 @@ public:
|
||||
*
|
||||
* @param packet The mesh packet to transmit
|
||||
*/
|
||||
void onPacketTransmitted(mesh::Packet *packet) override;
|
||||
void sendPacket(mesh::Packet *packet) override;
|
||||
|
||||
/**
|
||||
* @brief Called when a complete valid packet has been received from serial
|
||||
|
||||
@@ -1,70 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
#define LR1110_IRQ_HAS_PREAMBLE 0b0000000100 // 4 4 valid LoRa header received
|
||||
#define LR1110_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||
#include "MeshCore.h"
|
||||
|
||||
class CustomLR1110 : public LR1110 {
|
||||
public:
|
||||
CustomLR1110(Module *mod) : LR1110(mod) { }
|
||||
|
||||
RadioLibTime_t getTimeOnAir(size_t len) override {
|
||||
// calculate number of symbols
|
||||
float N_symbol = 0;
|
||||
if(this->codingRate <= RADIOLIB_LR11X0_LORA_CR_4_8_SHORT) {
|
||||
// legacy coding rate - nice and simple
|
||||
// get SF coefficients
|
||||
float coeff1 = 0;
|
||||
int16_t coeff2 = 0;
|
||||
int16_t coeff3 = 0;
|
||||
if(this->spreadingFactor < 7) {
|
||||
// SF5, SF6
|
||||
coeff1 = 6.25;
|
||||
coeff2 = 4*this->spreadingFactor;
|
||||
coeff3 = 4*this->spreadingFactor;
|
||||
} else if(this->spreadingFactor < 11) {
|
||||
// SF7. SF8, SF9, SF10
|
||||
coeff1 = 4.25;
|
||||
coeff2 = 4*this->spreadingFactor + 8;
|
||||
coeff3 = 4*this->spreadingFactor;
|
||||
} else {
|
||||
// SF11, SF12
|
||||
coeff1 = 4.25;
|
||||
coeff2 = 4*this->spreadingFactor + 8;
|
||||
coeff3 = 4*(this->spreadingFactor - 2);
|
||||
size_t getPacketLength(bool update) override {
|
||||
size_t len = LR1110::getPacketLength(update);
|
||||
if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) {
|
||||
// we've just recieved a corrupted packet
|
||||
// this may have triggered a bug causing subsequent packets to be shifted
|
||||
// call standby() to return radio to known-good state
|
||||
// recvRaw will call startReceive() to restart rx
|
||||
MESH_DEBUG_PRINTLN("LR1110: got header err, calling standby()");
|
||||
standby();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// get CRC length
|
||||
int16_t N_bitCRC = 16;
|
||||
if(this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_DISABLED) {
|
||||
N_bitCRC = 0;
|
||||
}
|
||||
|
||||
// get header length
|
||||
int16_t N_symbolHeader = 20;
|
||||
if(this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) {
|
||||
N_symbolHeader = 0;
|
||||
}
|
||||
|
||||
// calculate number of LoRa preamble symbols - NO! Lora preamble is already in symbols
|
||||
// uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4));
|
||||
|
||||
// calculate the number of symbols - nope
|
||||
// N_symbol = (float)N_symbolPreamble + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4);
|
||||
// calculate the number of symbols - using only preamblelora because it's already in symbols
|
||||
N_symbol = (float)preambleLengthLoRa + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4);
|
||||
} else {
|
||||
// long interleaving - not needed for this modem
|
||||
}
|
||||
|
||||
// get time-on-air in us
|
||||
return(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0f);
|
||||
}
|
||||
|
||||
|
||||
bool isReceiving() {
|
||||
uint16_t irq = getIrqStatus();
|
||||
bool detected = ((irq & LR1110_IRQ_HEADER_VALID) || (irq & LR1110_IRQ_HAS_PREAMBLE));
|
||||
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
|
||||
return detected;
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,21 @@
|
||||
#define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors
|
||||
#endif
|
||||
|
||||
#ifdef ENV_INCLUDE_BME680
|
||||
#ifndef TELEM_BME680_ADDRESS
|
||||
#define TELEM_BME680_ADDRESS 0x76
|
||||
#endif
|
||||
#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
|
||||
#include <Adafruit_BME680.h>
|
||||
static Adafruit_BME680 BME680;
|
||||
#endif
|
||||
|
||||
#ifdef ENV_INCLUDE_BMP085
|
||||
#define TELEM_BMP085_SEALEVELPRESSURE_HPA (1013.25)
|
||||
#include <Adafruit_BMP085.h>
|
||||
static Adafruit_BMP085 BMP085;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address
|
||||
#include <Adafruit_AHTX0.h>
|
||||
@@ -96,6 +111,41 @@ static bool serialGPSFlag = false;
|
||||
#define TELEM_RAK12500_ADDRESS 0x42 //RAK12500 Ublox GPS via i2c
|
||||
#include <SparkFun_u-blox_GNSS_Arduino_Library.h>
|
||||
static SFE_UBLOX_GNSS ublox_GNSS;
|
||||
|
||||
class RAK12500LocationProvider : public LocationProvider {
|
||||
long _lat = 0;
|
||||
long _lng = 0;
|
||||
long _alt = 0;
|
||||
int _sats = 0;
|
||||
long _epoch = 0;
|
||||
bool _fix = false;
|
||||
public:
|
||||
long getLatitude() override { return _lat; }
|
||||
long getLongitude() override { return _lng; }
|
||||
long getAltitude() override { return _alt; }
|
||||
long satellitesCount() override { return _sats; }
|
||||
bool isValid() override { return _fix; }
|
||||
long getTimestamp() override { return _epoch; }
|
||||
void sendSentence(const char * sentence) override { }
|
||||
void reset() override { }
|
||||
void begin() override { }
|
||||
void stop() override { }
|
||||
void loop() override {
|
||||
if (ublox_GNSS.getGnssFixOk(8)) {
|
||||
_fix = true;
|
||||
_lat = ublox_GNSS.getLatitude(2) / 10;
|
||||
_lng = ublox_GNSS.getLongitude(2) / 10;
|
||||
_alt = ublox_GNSS.getAltitude(2);
|
||||
_sats = ublox_GNSS.getSIV(2);
|
||||
} else {
|
||||
_fix = false;
|
||||
}
|
||||
_epoch = ublox_GNSS.getUnixEpoch(2);
|
||||
}
|
||||
bool isEnabled() override { return true; }
|
||||
};
|
||||
|
||||
static RAK12500LocationProvider RAK12500_provider;
|
||||
#endif
|
||||
|
||||
bool EnvironmentSensorManager::begin() {
|
||||
@@ -251,6 +301,28 @@ bool EnvironmentSensorManager::begin() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
|
||||
BME680_initialized = true;
|
||||
} else {
|
||||
BME680_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP085
|
||||
// First argument is MODE (aka oversampling)
|
||||
// choose ULTRALOWPOWER
|
||||
if (BMP085.begin(0, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor BMP085");
|
||||
BMP085_initialized = true;
|
||||
} else {
|
||||
BMP085_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BMP085 was not found at I2C address %02X", 0x77);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -380,6 +452,27 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680_initialized) {
|
||||
if (BME680.performReading()) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity);
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903)));
|
||||
telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance);
|
||||
next_available_channel++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP085
|
||||
if (BMP085_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP085.readPressure() / 100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BMP085.readAltitude(TELEM_BMP085_SEALEVELPRESSURE_HPA * 100));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -387,27 +480,34 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
|
||||
|
||||
int EnvironmentSensorManager::getNumSettings() const {
|
||||
int settings = 0;
|
||||
#if ENV_INCLUDE_GPS
|
||||
return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected
|
||||
#else
|
||||
return 0;
|
||||
if (gps_detected) settings++; // only show GPS setting if GPS is detected
|
||||
#endif
|
||||
return settings;
|
||||
}
|
||||
|
||||
const char* EnvironmentSensorManager::getSettingName(int i) const {
|
||||
int settings = 0;
|
||||
#if ENV_INCLUDE_GPS
|
||||
return (gps_detected && i == 0) ? "gps" : NULL;
|
||||
#else
|
||||
return NULL;
|
||||
if (gps_detected && i == settings++) {
|
||||
return "gps";
|
||||
}
|
||||
#endif
|
||||
// convenient way to add params (needed for some tests)
|
||||
// if (i == settings++) return "param.2";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* EnvironmentSensorManager::getSettingValue(int i) const {
|
||||
int settings = 0;
|
||||
#if ENV_INCLUDE_GPS
|
||||
if (gps_detected && i == 0) {
|
||||
return gps_active ? "1" : "0";
|
||||
}
|
||||
if (gps_detected && i == settings++) {
|
||||
return gps_active ? "1" : "0";
|
||||
}
|
||||
#endif
|
||||
// convenient way to add params ...
|
||||
// if (i == settings++) return "2";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -514,12 +614,22 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
|
||||
//Try to init RAK12500 on I2C
|
||||
if (ublox_GNSS.begin(Wire) == true){
|
||||
MESH_DEBUG_PRINTLN("RAK12500 GPS init correctly with pin %i",ioPin);
|
||||
ublox_GNSS.setI2COutput(COM_TYPE_NMEA);
|
||||
ublox_GNSS.setI2COutput(COM_TYPE_UBX);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GALILEO);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GLONASS);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_SBAS);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_BEIDOU);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_IMES);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_QZSS);
|
||||
ublox_GNSS.setMeasurementRate(1000);
|
||||
ublox_GNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT);
|
||||
gpsResetPin = ioPin;
|
||||
i2cGPSFlag = true;
|
||||
gps_active = true;
|
||||
gps_detected = true;
|
||||
|
||||
_location = &RAK12500_provider;
|
||||
return true;
|
||||
}
|
||||
else if(Serial1){
|
||||
@@ -571,19 +681,13 @@ void EnvironmentSensorManager::stop_gps() {
|
||||
void EnvironmentSensorManager::loop() {
|
||||
static long next_gps_update = 0;
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
_location->loop();
|
||||
|
||||
if (millis() > next_gps_update) {
|
||||
if(gps_active){
|
||||
#ifdef RAK_WISBLOCK_GPS
|
||||
if(i2cGPSFlag){
|
||||
node_lat = ((double)ublox_GNSS.getLatitude())/10000000.;
|
||||
node_lon = ((double)ublox_GNSS.getLongitude())/10000000.;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
|
||||
node_altitude = ((double)ublox_GNSS.getAltitude()) / 1000.0;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude);
|
||||
}
|
||||
else if (serialGPSFlag && _location->isValid()) {
|
||||
if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) {
|
||||
node_lat = ((double)_location->getLatitude())/1000000.;
|
||||
node_lon = ((double)_location->getLongitude())/1000000.;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
|
||||
@@ -602,5 +706,6 @@ void EnvironmentSensorManager::loop() {
|
||||
}
|
||||
next_gps_update = millis() + 1000;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -20,6 +20,8 @@ protected:
|
||||
bool MLX90614_initialized = false;
|
||||
bool VL53L0X_initialized = false;
|
||||
bool SHT4X_initialized = false;
|
||||
bool BME680_initialized = false;
|
||||
bool BMP085_initialized = false;
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
@@ -39,6 +41,7 @@ protected:
|
||||
public:
|
||||
#if ENV_INCLUDE_GPS
|
||||
EnvironmentSensorManager(LocationProvider &location): _location(&location){};
|
||||
LocationProvider* getLocationProvider() { return _location; }
|
||||
#else
|
||||
EnvironmentSensorManager(){};
|
||||
#endif
|
||||
|
||||
@@ -17,8 +17,9 @@ public:
|
||||
virtual bool isValid() = 0;
|
||||
virtual long getTimestamp() = 0;
|
||||
virtual void sendSentence(const char * sentence);
|
||||
virtual void reset();
|
||||
virtual void begin();
|
||||
virtual void stop();
|
||||
virtual void loop();
|
||||
virtual void reset() = 0;
|
||||
virtual void begin() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void loop() = 0;
|
||||
virtual bool isEnabled() = 0;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "LocationProvider.h"
|
||||
#include <MicroNMEA.h>
|
||||
#include <RTClib.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
#ifndef GPS_EN
|
||||
#ifdef PIN_GPS_EN
|
||||
@@ -37,14 +38,15 @@ class MicroNMEALocationProvider : public LocationProvider {
|
||||
MicroNMEA nmea;
|
||||
mesh::RTCClock* _clock;
|
||||
Stream* _gps_serial;
|
||||
RefCountedDigitalPin* _peripher_power;
|
||||
int _pin_reset;
|
||||
int _pin_en;
|
||||
long next_check = 0;
|
||||
long time_valid = 0;
|
||||
|
||||
public :
|
||||
MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN) :
|
||||
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock) {
|
||||
MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) :
|
||||
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock), _peripher_power(peripher_power) {
|
||||
if (_pin_reset != -1) {
|
||||
pinMode(_pin_reset, OUTPUT);
|
||||
digitalWrite(_pin_reset, GPS_RESET_FORCE);
|
||||
@@ -56,6 +58,7 @@ public :
|
||||
}
|
||||
|
||||
void begin() override {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
if (_pin_en != -1) {
|
||||
digitalWrite(_pin_en, PIN_GPS_EN_ACTIVE);
|
||||
}
|
||||
@@ -75,7 +78,18 @@ public :
|
||||
void stop() override {
|
||||
if (_pin_en != -1) {
|
||||
digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE);
|
||||
}
|
||||
}
|
||||
if (_peripher_power) _peripher_power->release();
|
||||
}
|
||||
|
||||
bool isEnabled() override {
|
||||
// directly read the enable pin if present as gps can be
|
||||
// activated/deactivated outside of here ...
|
||||
if (_pin_en != -1) {
|
||||
return digitalRead(_pin_en) == PIN_GPS_EN_ACTIVE;
|
||||
} else {
|
||||
return true; // no enable so must be active
|
||||
}
|
||||
}
|
||||
|
||||
void syncTime() override { nmea.clear(); LocationProvider::syncTime(); }
|
||||
|
||||
@@ -32,6 +32,15 @@ public:
|
||||
setCursor(mid_x - w/2, y);
|
||||
print(str);
|
||||
}
|
||||
virtual void drawTextRightAlign(int x_anch, int y, const char* str) {
|
||||
int w = getTextWidth(str);
|
||||
setCursor(x_anch - w, y);
|
||||
print(str);
|
||||
}
|
||||
virtual void drawTextLeftAlign(int x_anch, int y, const char* str) {
|
||||
setCursor(x_anch, y);
|
||||
print(str);
|
||||
}
|
||||
|
||||
// convert UTF-8 characters to displayable block characters for compatibility
|
||||
virtual void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) {
|
||||
|
||||
125
src/helpers/ui/LGFXDisplay.cpp
Normal file
125
src/helpers/ui/LGFXDisplay.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "LGFXDisplay.h"
|
||||
|
||||
bool LGFXDisplay::begin() {
|
||||
turnOn();
|
||||
display->init();
|
||||
display->setRotation(1);
|
||||
display->setBrightness(64);
|
||||
display->setColorDepth(8);
|
||||
display->setTextColor(TFT_WHITE);
|
||||
|
||||
buffer.setColorDepth(8);
|
||||
buffer.setPsram(true);
|
||||
buffer.createSprite(width(), height());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LGFXDisplay::turnOn() {
|
||||
// display->wakeup();
|
||||
if (!_isOn) {
|
||||
display->wakeup();
|
||||
}
|
||||
_isOn = true;
|
||||
}
|
||||
|
||||
void LGFXDisplay::turnOff() {
|
||||
if (_isOn) {
|
||||
display->sleep();
|
||||
}
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
void LGFXDisplay::clear() {
|
||||
// display->clearDisplay();
|
||||
buffer.clearDisplay();
|
||||
}
|
||||
|
||||
void LGFXDisplay::startFrame(Color bkg) {
|
||||
// display->startWrite();
|
||||
// display->getScanLine();
|
||||
buffer.clearDisplay();
|
||||
buffer.setTextColor(TFT_WHITE);
|
||||
}
|
||||
|
||||
void LGFXDisplay::setTextSize(int sz) {
|
||||
buffer.setTextSize(sz);
|
||||
}
|
||||
|
||||
void LGFXDisplay::setColor(Color c) {
|
||||
// _color = (c != 0) ? ILI9342_WHITE : ILI9342_BLACK;
|
||||
switch (c) {
|
||||
case DARK:
|
||||
_color = TFT_BLACK;
|
||||
break;
|
||||
case LIGHT:
|
||||
_color = TFT_WHITE;
|
||||
break;
|
||||
case RED:
|
||||
_color = TFT_RED;
|
||||
break;
|
||||
case GREEN:
|
||||
_color = TFT_GREEN;
|
||||
break;
|
||||
case BLUE:
|
||||
_color = TFT_BLUE;
|
||||
break;
|
||||
case YELLOW:
|
||||
_color = TFT_YELLOW;
|
||||
break;
|
||||
case ORANGE:
|
||||
_color = TFT_ORANGE;
|
||||
break;
|
||||
default:
|
||||
_color = TFT_WHITE;
|
||||
}
|
||||
buffer.setTextColor(_color);
|
||||
}
|
||||
|
||||
void LGFXDisplay::setCursor(int x, int y) {
|
||||
buffer.setCursor(x, y);
|
||||
}
|
||||
|
||||
void LGFXDisplay::print(const char* str) {
|
||||
buffer.println(str);
|
||||
// Serial.println(str);
|
||||
}
|
||||
|
||||
void LGFXDisplay::fillRect(int x, int y, int w, int h) {
|
||||
buffer.fillRect(x, y, w, h, _color);
|
||||
}
|
||||
|
||||
void LGFXDisplay::drawRect(int x, int y, int w, int h) {
|
||||
buffer.drawRect(x, y, w, h, _color);
|
||||
}
|
||||
|
||||
void LGFXDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
buffer.drawBitmap(x, y, bits, w, h, _color);
|
||||
}
|
||||
|
||||
uint16_t LGFXDisplay::getTextWidth(const char* str) {
|
||||
return buffer.textWidth(str);
|
||||
}
|
||||
|
||||
void LGFXDisplay::endFrame() {
|
||||
display->startWrite();
|
||||
if (UI_ZOOM != 1) {
|
||||
buffer.pushRotateZoom(display, display->width()/2, display->height()/2 , 0, UI_ZOOM, UI_ZOOM);
|
||||
} else {
|
||||
buffer.pushSprite(display, 0, 0);
|
||||
}
|
||||
display->endWrite();
|
||||
}
|
||||
|
||||
bool LGFXDisplay::getTouch(int *x, int *y) {
|
||||
lgfx::v1::touch_point_t point;
|
||||
display->getTouch(&point);
|
||||
if (UI_ZOOM != 1) {
|
||||
*x = point.x / UI_ZOOM;
|
||||
*y = point.y / UI_ZOOM;
|
||||
} else {
|
||||
*x = point.x;
|
||||
*y = point.y;
|
||||
}
|
||||
return (*x >= 0) && (*y >= 0);
|
||||
}
|
||||
39
src/helpers/ui/LGFXDisplay.h
Normal file
39
src/helpers/ui/LGFXDisplay.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
|
||||
#define LGFX_USE_V1
|
||||
#include <LovyanGFX.hpp>
|
||||
|
||||
#ifndef UI_ZOOM
|
||||
#define UI_ZOOM 1
|
||||
#endif
|
||||
|
||||
class LGFXDisplay : public DisplayDriver {
|
||||
protected:
|
||||
LGFX_Device* display;
|
||||
LGFX_Sprite buffer;
|
||||
|
||||
bool _isOn = false;
|
||||
int _color = TFT_WHITE;
|
||||
|
||||
public:
|
||||
LGFXDisplay(int w, int h, LGFX_Device &disp)
|
||||
: DisplayDriver(w/UI_ZOOM, h/UI_ZOOM), display(&disp) {}
|
||||
bool begin();
|
||||
bool isOn() override { return _isOn; }
|
||||
void turnOn() override;
|
||||
void turnOff() override;
|
||||
void clear() override;
|
||||
void startFrame(Color bkg = DARK) override;
|
||||
void setTextSize(int sz) override;
|
||||
void setColor(Color c) override;
|
||||
void setCursor(int x, int y) override;
|
||||
void print(const char* str) override;
|
||||
void fillRect(int x, int y, int w, int h) override;
|
||||
void drawRect(int x, int y, int w, int h) override;
|
||||
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
|
||||
uint16_t getTextWidth(const char* str) override;
|
||||
void endFrame() override;
|
||||
virtual bool getTouch(int *x, int *y);
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#define MULTI_CLICK_WINDOW_MS 280
|
||||
|
||||
MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup) {
|
||||
MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup, bool multiclick) {
|
||||
_pin = pin;
|
||||
_reverse = reverse;
|
||||
_pull = pulldownup;
|
||||
@@ -13,7 +13,7 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse
|
||||
_threshold = 0;
|
||||
_click_count = 0;
|
||||
_last_click_time = 0;
|
||||
_multi_click_window = MULTI_CLICK_WINDOW_MS;
|
||||
_multi_click_window = multiclick ? MULTI_CLICK_WINDOW_MS : 0;
|
||||
_pending_click = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class MomentaryButton {
|
||||
bool isPressed(int level) const;
|
||||
|
||||
public:
|
||||
MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false);
|
||||
MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false, bool multiclick=true);
|
||||
MomentaryButton(int8_t pin, int long_press_mills, int analog_threshold);
|
||||
void begin();
|
||||
int check(bool repeat_click=false); // returns one of BUTTON_EVENT_*
|
||||
|
||||
@@ -24,14 +24,21 @@ bool ST7735Display::begin() {
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
|
||||
#if defined(HELTEC_TRACKER_V2)
|
||||
display.initR(INITR_MINI160x80);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR
|
||||
display.sendCommand(ST77XX_MADCTL, &madctl, 1);
|
||||
#else
|
||||
display.initR(INITR_MINI160x80_PLUGIN);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
#endif
|
||||
display.setSPISpeed(40000000);
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(2);
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
|
||||
|
||||
_isOn = true;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -39,7 +39,7 @@ bool ST7789LCDDisplay::begin() {
|
||||
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(2);
|
||||
display.setTextSize(2 * DISPLAY_SCALE_X);
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
|
||||
_isOn = true;
|
||||
@@ -70,12 +70,12 @@ void ST7789LCDDisplay::clear() {
|
||||
void ST7789LCDDisplay::startFrame(Color bkg) {
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(1); // This one affects size of Please wait... message
|
||||
display.setTextSize(1 * DISPLAY_SCALE_X); // This one affects size of Please wait... message
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::setTextSize(int sz) {
|
||||
display.setTextSize(sz);
|
||||
display.setTextSize(sz * DISPLAY_SCALE_X);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::setColor(Color c) {
|
||||
@@ -125,7 +125,22 @@ void ST7789LCDDisplay::drawRect(int x, int y, int w, int h) {
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
display.drawBitmap(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y, bits, w, h, _color);
|
||||
uint8_t byteWidth = (w + 7) / 8;
|
||||
|
||||
for (int j = 0; j < h; j++) {
|
||||
for (int i = 0; i < w; i++) {
|
||||
uint8_t byte = bits[j * byteWidth + i / 8];
|
||||
bool pixelOn = byte & (0x80 >> (i & 7));
|
||||
|
||||
if (pixelOn) {
|
||||
for (int dy = 0; dy < DISPLAY_SCALE_X; dy++) {
|
||||
for (int dx = 0; dx < DISPLAY_SCALE_X; dx++) {
|
||||
display.drawPixel(x * DISPLAY_SCALE_X + i * DISPLAY_SCALE_X + dx, y * DISPLAY_SCALE_Y + j * DISPLAY_SCALE_X + dy, _color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
|
||||
@@ -138,4 +153,4 @@ uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
|
||||
|
||||
void ST7789LCDDisplay::endFrame() {
|
||||
// display.display();
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ lib_deps =
|
||||
extends = Ebyte_EoRa-S3
|
||||
build_flags =
|
||||
${Ebyte_EoRa-S3.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -99,8 +99,8 @@ build_flags =
|
||||
${Ebyte_EoRa-S3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||
@@ -118,8 +118,8 @@ build_flags =
|
||||
${Ebyte_EoRa-S3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
|
||||
@@ -65,6 +65,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; lib_deps =
|
||||
@@ -87,7 +88,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
@@ -132,6 +133,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; lib_deps =
|
||||
@@ -154,7 +156,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
|
||||
@@ -26,7 +26,7 @@ build_src_filter = ${esp32_base.build_src_filter}
|
||||
extends = Generic_ESPNOW
|
||||
build_flags =
|
||||
${Generic_ESPNOW.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
build_src_filter = ${Generic_ESPNOW.build_src_filter}
|
||||
+<../examples/simple_secure_chat/main.cpp>
|
||||
@@ -54,7 +54,7 @@ lib_deps =
|
||||
extends = Generic_ESPNOW
|
||||
build_flags =
|
||||
${Generic_ESPNOW.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
|
||||
@@ -61,6 +61,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_ct62.build_src_filter}
|
||||
@@ -80,7 +81,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_ct62.build_src_filter}
|
||||
@@ -96,8 +97,8 @@ build_flags =
|
||||
${Heltec_ct62.build_flags}
|
||||
; -D ARDUINO_USB_MODE=1
|
||||
; -D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -114,8 +115,8 @@ build_flags =
|
||||
${Heltec_ct62.build_flags}
|
||||
; -D ARDUINO_USB_MODE=1
|
||||
; -D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D BLE_PIN_CODE=123456
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
|
||||
@@ -40,13 +40,13 @@ lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip
|
||||
|
||||
[env:Heltec_E213_companion_radio_ble]
|
||||
[env:Heltec_E213_companion_radio_ble_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E213Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -60,13 +60,13 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_E213_companion_radio_usb]
|
||||
[env:Heltec_E213_companion_radio_usb_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E213Display
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${Heltec_E213_base.build_src_filter}
|
||||
@@ -78,7 +78,7 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_E213_repeater]
|
||||
[env:Heltec_E213_repeater_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
@@ -86,6 +86,8 @@ build_flags =
|
||||
-D ADVERT_NAME='"Heltec E213 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
build_src_filter = ${Heltec_E213_base.build_src_filter}
|
||||
+<helpers/ui/E213Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
@@ -93,7 +95,7 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; [env:Heltec_E213_repeater_bridge_rs232]
|
||||
; [env:Heltec_E213_repeater_bridge_rs232_]
|
||||
; extends = Heltec_E213_base
|
||||
; build_flags =
|
||||
; ${Heltec_E213_base.build_flags}
|
||||
@@ -106,6 +108,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_E213_base.build_src_filter}
|
||||
@@ -116,7 +119,7 @@ lib_deps =
|
||||
; ${Heltec_E213_base.lib_deps}
|
||||
; ${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_E213_repeater_bridge_espnow]
|
||||
[env:Heltec_E213_repeater_bridge_espnow_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
@@ -127,7 +130,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_E213_base.build_src_filter}
|
||||
@@ -138,7 +141,7 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_E213_room_server]
|
||||
[env:Heltec_E213_room_server_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
|
||||
@@ -34,13 +34,13 @@ lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip
|
||||
|
||||
[env:Heltec_E290_companion_radio_ble]
|
||||
[env:Heltec_E290_companion_ble_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E290Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -54,13 +54,13 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_E290_companion_radio_usb]
|
||||
[env:Heltec_E290_companion_usb_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E290Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -74,7 +74,7 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_E290_repeater]
|
||||
[env:Heltec_E290_repeater_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
@@ -82,6 +82,8 @@ build_flags =
|
||||
-D ADVERT_NAME='"Heltec E290 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
build_src_filter = ${Heltec_E290_base.build_src_filter}
|
||||
+<helpers/ui/E290Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
@@ -89,7 +91,7 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; [env:Heltec_E290_repeater_bridge_rs232]
|
||||
; [env:Heltec_E290_repeater_bridge_rs232_]
|
||||
; extends = Heltec_E290_base
|
||||
; build_flags =
|
||||
; ${Heltec_E290_base.build_flags}
|
||||
@@ -102,6 +104,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_E290_base.build_src_filter}
|
||||
@@ -112,7 +115,7 @@ lib_deps =
|
||||
; ${Heltec_E290_base.lib_deps}
|
||||
; ${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_E290_repeater_bridge_espnow]
|
||||
[env:Heltec_E290_repeater_bridge_espnow_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
@@ -123,7 +126,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_E290_base.build_src_filter}
|
||||
@@ -134,7 +137,7 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_E290_room_server]
|
||||
[env:Heltec_E290_room_server_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
|
||||
@@ -24,45 +24,6 @@ void T114Board::begin() {
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
// Enable SoftDevice low-power mode
|
||||
sd_power_mode_set(NRF_POWER_MODE_LOWPWR);
|
||||
|
||||
// Enable DC/DC converter for better efficiency (REG1 stage)
|
||||
NRF_POWER->DCDCEN = 1;
|
||||
|
||||
// Power down unused communication peripherals
|
||||
// UART1 - Not used on T114
|
||||
NRF_UARTE1->ENABLE = 0;
|
||||
|
||||
// SPIM2/SPIS2 - Not used (SPI is on SPIM0)
|
||||
NRF_SPIM2->ENABLE = 0;
|
||||
NRF_SPIS2->ENABLE = 0;
|
||||
|
||||
// TWI1 (I2C1) - Not used (I2C is on TWI0)
|
||||
NRF_TWIM1->ENABLE = 0;
|
||||
NRF_TWIS1->ENABLE = 0;
|
||||
|
||||
// PWM modules - Not used for standard T114 functions
|
||||
NRF_PWM1->ENABLE = 0;
|
||||
NRF_PWM2->ENABLE = 0;
|
||||
NRF_PWM3->ENABLE = 0;
|
||||
|
||||
// PDM (Digital Microphone Interface) - Not used
|
||||
NRF_PDM->ENABLE = 0;
|
||||
|
||||
// I2S - Not used
|
||||
NRF_I2S->ENABLE = 0;
|
||||
|
||||
// QSPI - Not used (no external flash)
|
||||
NRF_QSPI->ENABLE = 0;
|
||||
|
||||
// Disable unused analog peripherals
|
||||
// SAADC channels - only keep what's needed for battery monitoring
|
||||
NRF_SAADC->ENABLE = 0; // Re-enable only when needed for measurements
|
||||
|
||||
// COMP - Comparator not used
|
||||
NRF_COMP->ENABLE = 0;
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
||||
@@ -27,9 +27,6 @@ public:
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
int adcvalue = 0;
|
||||
|
||||
NRF_SAADC->ENABLE = 1;
|
||||
|
||||
analogReadResolution(12);
|
||||
analogReference(AR_INTERNAL_3_0);
|
||||
pinMode(PIN_BAT_CTL, OUTPUT); // battery adc can be read only ctrl pin 6 set to high
|
||||
@@ -39,8 +36,6 @@ public:
|
||||
adcvalue = analogRead(PIN_VBAT_READ);
|
||||
digitalWrite(6, 0);
|
||||
|
||||
NRF_SAADC->ENABLE = 0;
|
||||
|
||||
return (uint16_t)((float)adcvalue * MV_LSB * 4.9);
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@ build_flags =
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
|
||||
@@ -74,11 +74,10 @@ bool T114SensorManager::begin() {
|
||||
|
||||
if (gps_detected) {
|
||||
MESH_DEBUG_PRINTLN("GPS detected");
|
||||
digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("No GPS detected");
|
||||
digitalWrite(GPS_EN, LOW);
|
||||
}
|
||||
digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
bool begin() override;
|
||||
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
|
||||
void loop() override;
|
||||
LocationProvider* getLocationProvider() override { return gps_detected ? _location : NULL; }
|
||||
int getNumSettings() const override;
|
||||
const char* getSettingName(int i) const override;
|
||||
const char* getSettingValue(int i) const override;
|
||||
|
||||
@@ -47,13 +47,13 @@ lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
adafruit/Adafruit GFX Library @ ^1.12.1
|
||||
|
||||
[env:Heltec_T190_companion_radio_ble]
|
||||
[env:Heltec_T190_companion_radio_ble_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -65,13 +65,13 @@ lib_deps =
|
||||
${Heltec_T190_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_T190_companion_radio_usb]
|
||||
[env:Heltec_T190_companion_radio_usb_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${Heltec_T190_base.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
@@ -81,20 +81,22 @@ lib_deps =
|
||||
${Heltec_T190_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_T190_repeater]
|
||||
[env:Heltec_T190_repeater_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
-D ADVERT_NAME='"Heltec T190 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
build_src_filter = ${Heltec_T190_base.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Heltec_T190_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; [env:Heltec_T190_repeater_bridge_rs232]
|
||||
; [env:Heltec_T190_repeater_bridge_rs232_]
|
||||
; extends = Heltec_T190_base
|
||||
; build_flags =
|
||||
; ${Heltec_T190_base.build_flags}
|
||||
@@ -106,6 +108,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_T190_base.build_src_filter}
|
||||
@@ -115,7 +118,7 @@ lib_deps =
|
||||
; ${Heltec_T190_base.lib_deps}
|
||||
; ${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_T190_repeater_bridge_espnow]
|
||||
[env:Heltec_T190_repeater_bridge_espnow_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
@@ -125,7 +128,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_T190_base.build_src_filter}
|
||||
@@ -135,7 +138,7 @@ lib_deps =
|
||||
${Heltec_T190_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_T190_room_server]
|
||||
[env:Heltec_T190_room_server_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
|
||||
@@ -5,6 +5,7 @@ build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/heltec_tracker
|
||||
-D HELTEC_LORA_V3
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
@@ -40,11 +41,10 @@ build_flags =
|
||||
${Heltec_tracker_base.build_flags}
|
||||
-I src/helpers/ui
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456 ; HWT will use display for pin
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
@@ -94,6 +94,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_tracker_base.build_src_filter}
|
||||
@@ -116,7 +117,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_base.build_src_filter}
|
||||
|
||||
88
variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp
Normal file
88
variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "HeltecTrackerV2Board.h"
|
||||
|
||||
void HeltecTrackerV2Board::begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
pinMode(PIN_ADC_CTRL, OUTPUT);
|
||||
digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive
|
||||
|
||||
pinMode(P_LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(P_LORA_PA_POWER,HIGH);
|
||||
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN);
|
||||
pinMode(P_LORA_PA_EN, OUTPUT);
|
||||
digitalWrite(P_LORA_PA_EN,HIGH);
|
||||
pinMode(P_LORA_PA_TX_EN, OUTPUT);
|
||||
digitalWrite(P_LORA_PA_TX_EN,LOW);
|
||||
|
||||
periph_power.begin();
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
|
||||
startup_reason = BD_STARTUP_RX_PACKET;
|
||||
}
|
||||
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||
}
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::onBeforeTransmit(void) {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
digitalWrite(P_LORA_PA_TX_EN,HIGH);
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::onAfterTransmit(void) {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
digitalWrite(P_LORA_PA_TX_EN,LOW);
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
||||
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode
|
||||
|
||||
if (pin_wake_btn < 0) {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
||||
} else {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
||||
}
|
||||
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000);
|
||||
}
|
||||
|
||||
// Finally set ESP32 into sleep
|
||||
esp_deep_sleep_start(); // CPU halts here and never returns!
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::powerOff() {
|
||||
enterDeepSleep(0);
|
||||
}
|
||||
|
||||
uint16_t HeltecTrackerV2Board::getBattMilliVolts() {
|
||||
analogReadResolution(10);
|
||||
digitalWrite(PIN_ADC_CTRL, HIGH);
|
||||
delay(10);
|
||||
uint32_t raw = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
raw += analogRead(PIN_VBAT_READ);
|
||||
}
|
||||
raw = raw / 8;
|
||||
|
||||
digitalWrite(PIN_ADC_CTRL, LOW);
|
||||
|
||||
return (5.42 * (3.3 / 1024.0) * raw) * 1000;
|
||||
}
|
||||
|
||||
const char* HeltecTrackerV2Board::getManufacturerName() const {
|
||||
return "Heltec Tracker V2";
|
||||
}
|
||||
23
variants/heltec_tracker_v2/HeltecTrackerV2Board.h
Normal file
23
variants/heltec_tracker_v2/HeltecTrackerV2Board.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class HeltecTrackerV2Board : public ESP32Board {
|
||||
|
||||
public:
|
||||
RefCountedDigitalPin periph_power;
|
||||
|
||||
HeltecTrackerV2Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
|
||||
|
||||
void begin();
|
||||
void onBeforeTransmit(void) override;
|
||||
void onAfterTransmit(void) override;
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
|
||||
void powerOff() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
|
||||
};
|
||||
60
variants/heltec_tracker_v2/pins_arduino.h
Normal file
60
variants/heltec_tracker_v2/pins_arduino.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef Pins_Arduino_h
|
||||
#define Pins_Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint8_t LED_BUILTIN = 18;
|
||||
#define BUILTIN_LED LED_BUILTIN // backward compatibility
|
||||
#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN
|
||||
|
||||
static const uint8_t TX = 43;
|
||||
static const uint8_t RX = 44;
|
||||
|
||||
static const uint8_t SDA = 5;
|
||||
static const uint8_t SCL = 6;
|
||||
|
||||
static const uint8_t SS = 8;
|
||||
static const uint8_t MOSI = 10;
|
||||
static const uint8_t MISO = 11;
|
||||
static const uint8_t SCK = 9;
|
||||
|
||||
static const uint8_t A0 = 1;
|
||||
static const uint8_t A1 = 2;
|
||||
static const uint8_t A2 = 3;
|
||||
static const uint8_t A3 = 4;
|
||||
static const uint8_t A4 = 5;
|
||||
static const uint8_t A5 = 6;
|
||||
static const uint8_t A6 = 7;
|
||||
static const uint8_t A7 = 8;
|
||||
static const uint8_t A8 = 9;
|
||||
static const uint8_t A9 = 10;
|
||||
static const uint8_t A10 = 11;
|
||||
static const uint8_t A11 = 12;
|
||||
static const uint8_t A12 = 13;
|
||||
static const uint8_t A13 = 14;
|
||||
static const uint8_t A14 = 15;
|
||||
static const uint8_t A15 = 16;
|
||||
static const uint8_t A16 = 17;
|
||||
static const uint8_t A17 = 18;
|
||||
static const uint8_t A18 = 19;
|
||||
static const uint8_t A19 = 20;
|
||||
|
||||
static const uint8_t T1 = 1;
|
||||
static const uint8_t T2 = 2;
|
||||
static const uint8_t T3 = 3;
|
||||
static const uint8_t T4 = 4;
|
||||
static const uint8_t T5 = 5;
|
||||
static const uint8_t T6 = 6;
|
||||
static const uint8_t T7 = 7;
|
||||
static const uint8_t T8 = 8;
|
||||
static const uint8_t T9 = 9;
|
||||
static const uint8_t T10 = 10;
|
||||
static const uint8_t T11 = 11;
|
||||
static const uint8_t T12 = 12;
|
||||
static const uint8_t T13 = 13;
|
||||
static const uint8_t T14 = 14;
|
||||
|
||||
static const uint8_t Vext = 3;
|
||||
static const uint8_t LED = 18;
|
||||
|
||||
#endif /* Pins_Arduino_h */
|
||||
218
variants/heltec_tracker_v2/platformio.ini
Normal file
218
variants/heltec_tracker_v2/platformio.ini
Normal file
@@ -0,0 +1,218 @@
|
||||
[Heltec_tracker_v2]
|
||||
extends = esp32_base
|
||||
board = heltec_tracker_v2
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/heltec_tracker_v2
|
||||
-D HELTEC_TRACKER_V2
|
||||
-D ESP32_CPU_FREQ=160
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_TX_LED=18
|
||||
-D P_LORA_DIO_1=14
|
||||
-D P_LORA_NSS=8
|
||||
-D P_LORA_RESET=12
|
||||
-D P_LORA_BUSY=13
|
||||
-D P_LORA_SCLK=9
|
||||
-D P_LORA_MISO=11
|
||||
-D P_LORA_MOSI=10
|
||||
-D P_LORA_PA_POWER=7 ;power en
|
||||
-D P_LORA_PA_EN=4
|
||||
-D P_LORA_PA_TX_EN=46 ;enable tx
|
||||
-D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm.
|
||||
-D MAX_LORA_TX_POWER=22 ;Max SX1262 output
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_BOARD_SDA=5
|
||||
-D PIN_BOARD_SCL=6
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_TFT_SDA=42 ; SDIN
|
||||
-D PIN_TFT_SCL=41 ; SCLK
|
||||
-D PIN_TFT_DC=40 ; RS (register select)
|
||||
-D PIN_TFT_RST=39 ; RES
|
||||
-D PIN_TFT_CS=38
|
||||
-D USE_PIN_TFT=1
|
||||
-D PIN_VEXT_EN=3 ; Vext is connected to VDD which is also connected to OLED & GPS
|
||||
-D PIN_VEXT_EN_ACTIVE=HIGH
|
||||
-D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground)
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D PIN_GPS_RX=34
|
||||
-D PIN_GPS_TX=33
|
||||
-D PIN_GPS_RESET=35
|
||||
-D PIN_GPS_RESET_ACTIVE=LOW
|
||||
-D GPS_BAUD_RATE=115200
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
-D PIN_ADC_CTRL=2
|
||||
-D PIN_VBAT_READ=1
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/heltec_tracker_v2>
|
||||
+<helpers/sensors>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0
|
||||
|
||||
[env:heltec_tracker_v2_repeater]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D ADVERT_NAME='"Heltec Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:heltec_tracker_v2_repeater_bridge_espnow]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D ADVERT_NAME='"ESPNow Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/bridges/ESPNowBridge.cpp>
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:heltec_tracker_v2_room_server]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D ADVERT_NAME='"Heltec Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:heltec_tracker_v2_terminal_chat]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<../examples/simple_secure_chat/main.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_companion_radio_usb]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_companion_radio_ble]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_companion_radio_wifi]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
-D WIFI_PWD='"mypwd"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_sensor]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D ADVERT_NAME='"Heltec Tracker V2 Sensor"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ENV_PIN_SDA=3
|
||||
-D ENV_PIN_SCL=4
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<../examples/simple_sensor>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
60
variants/heltec_tracker_v2/target.cpp
Normal file
60
variants/heltec_tracker_v2/target.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
HeltecTrackerV2Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, NULL, GPS_RESET, GPS_EN, &board.periph_power);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors;
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
return radio.std_init(&spi);
|
||||
#else
|
||||
return radio.std_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
30
variants/heltec_tracker_v2/target.h
Normal file
30
variants/heltec_tracker_v2/target.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <HeltecTrackerV2Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/ST7735Display.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
|
||||
extern HeltecTrackerV2Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
@@ -1,22 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// LoRa radio module pins for Heltec V2
|
||||
#define P_LORA_DIO_1 26 // DIO0
|
||||
#define P_LORA_NSS 18
|
||||
#define P_LORA_RESET RADIOLIB_NC // 14
|
||||
#define P_LORA_BUSY RADIOLIB_NC
|
||||
#define P_LORA_SCLK 5
|
||||
#define P_LORA_MISO 19
|
||||
#define P_LORA_MOSI 27
|
||||
#include <helpers/ESP32Board.h>
|
||||
|
||||
// built-ins
|
||||
#define PIN_VBAT_READ 37
|
||||
#define PIN_LED_BUILTIN 25
|
||||
|
||||
#include "ESP32Board.h"
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class HeltecV2Board : public ESP32Board {
|
||||
@@ -39,7 +29,7 @@ public:
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
@@ -7,13 +7,20 @@ build_flags =
|
||||
-D HELTEC_LORA_V2
|
||||
-D RADIO_CLASS=CustomSX1276
|
||||
-D WRAPPER_CLASS=CustomSX1276Wrapper
|
||||
-D P_LORA_DIO_1=26
|
||||
-D P_LORA_NSS=18
|
||||
-D P_LORA_RESET=RADIOLIB_NC
|
||||
-D P_LORA_BUSY=RADIOLIB_NC
|
||||
-D P_LORA_SCLK=5
|
||||
-D P_LORA_MISO=19
|
||||
-D P_LORA_MOSI=27
|
||||
-D P_LORA_TX_LED=25
|
||||
-D SX127X_CURRENT_LIMIT=120
|
||||
-D LORA_TX_POWER=20
|
||||
-D PIN_BOARD_SDA=4
|
||||
-D PIN_BOARD_SCL=15
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_OLED_RESET=16
|
||||
-D P_LORA_TX_LED=25
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/heltec_v2>
|
||||
lib_deps =
|
||||
@@ -53,6 +60,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
@@ -75,7 +83,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
@@ -111,7 +119,7 @@ lib_deps =
|
||||
extends = Heltec_lora32_v2
|
||||
build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-D MAX_CONTACTS=170
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -127,7 +135,7 @@ build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=170
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
@@ -147,7 +155,7 @@ build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=170
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -163,3 +171,26 @@ build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_v2_companion_radio_wifi]
|
||||
extends = Heltec_lora32_v2
|
||||
build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
-D WIFI_PWD='"mypwd"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/HeltecV2Board.h>
|
||||
#include <HeltecV2Board.h>
|
||||
#include <helpers/radiolib/CustomSX1276Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
// LoRa radio module pins for Heltec V3
|
||||
// Also for Heltec Wireless Tracker/Paper
|
||||
#define P_LORA_DIO_1 14
|
||||
#define P_LORA_NSS 8
|
||||
#define P_LORA_RESET RADIOLIB_NC
|
||||
#define P_LORA_BUSY 13
|
||||
#define P_LORA_SCLK 9
|
||||
#define P_LORA_MISO 11
|
||||
#define P_LORA_MOSI 10
|
||||
#include <helpers/ESP32Board.h>
|
||||
|
||||
// built-ins
|
||||
#ifndef PIN_VBAT_READ // set in platformio.ini for boards like Heltec Wireless Paper (20)
|
||||
@@ -22,9 +13,6 @@
|
||||
#endif
|
||||
#define PIN_ADC_CTRL_ACTIVE LOW
|
||||
#define PIN_ADC_CTRL_INACTIVE HIGH
|
||||
//#define PIN_LED_BUILTIN 35
|
||||
|
||||
#include "ESP32Board.h"
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
@@ -43,7 +31,7 @@ public:
|
||||
// Auto-detect correct ADC_CTRL pin polarity (different for boards >3.2)
|
||||
pinMode(PIN_ADC_CTRL, INPUT);
|
||||
adc_active_state = !digitalRead(PIN_ADC_CTRL);
|
||||
|
||||
|
||||
pinMode(PIN_ADC_CTRL, OUTPUT);
|
||||
digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive
|
||||
|
||||
@@ -64,7 +52,7 @@ public:
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
@@ -7,6 +7,13 @@ build_flags =
|
||||
-I variants/heltec_v3
|
||||
-D HELTEC_LORA_V3
|
||||
-D ESP32_CPU_FREQ=80
|
||||
-D P_LORA_DIO_1=14
|
||||
-D P_LORA_NSS=8
|
||||
-D P_LORA_RESET=RADIOLIB_NC
|
||||
-D P_LORA_BUSY=13
|
||||
-D P_LORA_SCLK=9
|
||||
-D P_LORA_MISO=11
|
||||
-D P_LORA_MOSI=10
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
@@ -62,6 +69,7 @@ build_flags =
|
||||
-D WITH_RS232_BRIDGE=Serial2
|
||||
-D WITH_RS232_BRIDGE_RX=5
|
||||
-D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -83,7 +91,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -117,7 +125,7 @@ lib_deps =
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -132,8 +140,8 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
@@ -151,8 +159,8 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
@@ -175,8 +183,8 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
@@ -243,6 +251,7 @@ build_flags =
|
||||
-D WITH_RS232_BRIDGE=Serial2
|
||||
-D WITH_RS232_BRIDGE_RX=5
|
||||
-D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -263,7 +272,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -295,8 +304,8 @@ lib_deps =
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -314,7 +323,7 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=140
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -323,6 +332,24 @@ lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_WSL3_companion_radio_wifi]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
-D WIFI_PWD='"mypwd"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_WSL3_sensor]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/HeltecV3Board.h>
|
||||
#include <HeltecV3Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
@@ -26,6 +26,7 @@ build_flags =
|
||||
-D PIN_VEXT_EN=36
|
||||
-D PIN_VEXT_EN_ACTIVE=HIGH
|
||||
-D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm.
|
||||
-D MAX_LORA_TX_POWER=22 ; Max SX1262 output
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
@@ -77,7 +78,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v4.build_src_filter}
|
||||
@@ -111,7 +112,7 @@ lib_deps =
|
||||
extends = Heltec_lora32_v4
|
||||
build_flags =
|
||||
${Heltec_lora32_v4.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -126,8 +127,8 @@ extends = Heltec_lora32_v4
|
||||
build_flags =
|
||||
${Heltec_lora32_v4.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
@@ -145,8 +146,8 @@ extends = Heltec_lora32_v4
|
||||
build_flags =
|
||||
${Heltec_lora32_v4.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
@@ -169,8 +170,8 @@ extends = Heltec_lora32_v4
|
||||
build_flags =
|
||||
${Heltec_lora32_v4.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
|
||||
@@ -38,8 +38,8 @@ extends = Heltec_Wireless_Paper_base
|
||||
build_flags =
|
||||
${Heltec_Wireless_Paper_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E213Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -61,6 +61,8 @@ build_flags =
|
||||
-D ADVERT_NAME='"Heltec WP Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
|
||||
+<helpers/ui/E213Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
@@ -81,6 +83,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
|
||||
@@ -102,7 +105,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ikoka_stick_nrf_board.h"
|
||||
#include "IkokaNanoNRFBoard.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle)
|
||||
{
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason)
|
||||
{
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
void ikoka_stick_nrf_board::begin() {
|
||||
void IkokaNanoNRFBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
@@ -50,7 +48,7 @@ void ikoka_stick_nrf_board::begin() {
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) {
|
||||
bool IkokaNanoNRFBoard::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
@@ -91,9 +89,6 @@ bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) {
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
class ikoka_stick_nrf_board : public mesh::MainBoard {
|
||||
class IkokaNanoNRFBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
@@ -16,9 +16,17 @@ public:
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
#if defined(LED_BLUE)
|
||||
// turn off that annoying blue LED before transmitting
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
#endif
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
#if defined(LED_BLUE)
|
||||
// do it after transmitting too, just in case
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -38,15 +46,15 @@ public:
|
||||
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Ikoka Stick (Xiao-nrf52)";
|
||||
const char *getManufacturerName() const override {
|
||||
return MANUFACTURER_STRING;
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
312
variants/ikoka_nano_nrf/platformio.ini
Normal file
312
variants/ikoka_nano_nrf/platformio.ini
Normal file
@@ -0,0 +1,312 @@
|
||||
[nrf52840_xiao]
|
||||
extends = nrf52_base
|
||||
platform_packages =
|
||||
toolchain-gccarmnoneeabi@~1.100301.0
|
||||
framework-arduinoadafruitnrf52
|
||||
board = seeed-xiao-afruitnrf52-nrf52840
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
-D NRF52_PLATFORM -D XIAO_NRF52
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
lvgl
|
||||
lib5b4
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
||||
adafruit/Adafruit INA219 @ ^1.2.3
|
||||
adafruit/Adafruit AHTX0 @ ^2.0.5
|
||||
adafruit/Adafruit BME280 Library @ ^2.3.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[ikoka_nano_nrf_baseboard]
|
||||
extends = nrf52840_xiao
|
||||
;board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52840_xiao.build_flags}
|
||||
-D P_LORA_TX_LED=11
|
||||
-I variants/ikoka_nano_nrf
|
||||
-I src/helpers/nrf52
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_DIO_1=D1
|
||||
; -D P_LORA_BUSY=D3
|
||||
-D P_LORA_BUSY=D2 ; specific to ikoka nano variant.
|
||||
; -D P_LORA_RESET=D2
|
||||
-D P_LORA_RESET=D3 ; specific to ikoka nano variant.
|
||||
; -D P_LORA_NSS=D4
|
||||
-D P_LORA_NSS=D0 ; specific to ikoka nano variant.
|
||||
; -D SX126X_RXEN=D5
|
||||
-D SX126X_RXEN=D7
|
||||
-D SX126X_TXEN=RADIOLIB_NC
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=1
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_WIRE_SCL=5 ; specific to ikoka nano variant.
|
||||
-D PIN_WIRE_SDA=4 ; specific to ikoka nano variant.
|
||||
-D ENV_INCLUDE_AHTX0=1
|
||||
-D ENV_INCLUDE_BME280=1
|
||||
-D ENV_INCLUDE_INA3221=1
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
|
||||
;;; abstracted hardware variants
|
||||
|
||||
[ikoka_nano_nrf_e22_22dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
; No PA in this model, full 22dBm
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-22dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=22
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
[ikoka_nano_nrf_e22_30dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
; limit txpower to 20dBm on E22-900M30S. Anything higher will
|
||||
; cause distortion in the PA output. 20dBm in -> 30dBm out
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-30dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
[ikoka_nano_nrf_e22_33dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
; limit txpower to 9dBm on E22-900M33S to avoid hardware damage
|
||||
; to the rf amplifier frontend. 9dBm in -> 33dBm out
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-33dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=9
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
;;; abstracted firmware roles
|
||||
|
||||
[ikoka_nano_nrf_companion_radio_ble]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-I examples/companion_radio/ui-new
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${ikoka_nano_nrf_baseboard.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[ikoka_nano_nrf_companion_radio_usb]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-I examples/companion_radio/ui-new
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${ikoka_nano_nrf_baseboard.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[ikoka_nano_nrf_repeater]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D ADVERT_NAME='"Ikoka Nano Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
+<../examples/simple_repeater/*.cpp>
|
||||
|
||||
[ikoka_nano_nrf_room_server]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D ADVERT_NAME='"Ikoka Nano Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
+<../examples/simple_room_server/*.cpp>
|
||||
|
||||
;;; hardware + firmware variants
|
||||
|
||||
;;; 22dBm EBYTE E22-900M22 variants
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
ikoka_nano_nrf_companion_radio_usb
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_flags}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_companion_radio_ble]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
ikoka_nano_nrf_companion_radio_ble
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_flags}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_repeater]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
ikoka_nano_nrf_repeater
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_repeater.build_flags}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_repeater.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_room_server]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
ikoka_nano_nrf_room_server
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_room_server.build_flags}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_room_server.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_src_filter}
|
||||
|
||||
|
||||
;;; 30dBm EBYTE E22-900M30 variants
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
ikoka_nano_nrf_companion_radio_usb
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_flags}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_companion_radio_ble]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
ikoka_nano_nrf_companion_radio_ble
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_flags}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_repeater]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
ikoka_nano_nrf_repeater
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_repeater.build_flags}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_repeater.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_room_server]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
ikoka_nano_nrf_room_server
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_room_server.build_flags}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_room_server.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_src_filter}
|
||||
|
||||
|
||||
;;; 33dBm EBYTE E22-900M33 variants
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
ikoka_nano_nrf_companion_radio_usb
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_flags}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_companion_radio_ble]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
ikoka_nano_nrf_companion_radio_ble
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_flags}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_repeater]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
ikoka_nano_nrf_repeater
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_repeater.build_flags}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_repeater.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_room_server]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
ikoka_nano_nrf_room_server
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_room_server.build_flags}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_room_server.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_src_filter}
|
||||
44
variants/ikoka_nano_nrf/target.cpp
Normal file
44
variants/ikoka_nano_nrf/target.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
IkokaNanoNRFBoard board;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
// MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
VolatileRTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
EnvironmentSensorManager sensors;
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
28
variants/ikoka_nano_nrf/target.h
Normal file
28
variants/ikoka_nano_nrf/target.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <IkokaNanoNRFBoard.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/NullDisplayDriver.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
extern DISPLAY_CLASS display;
|
||||
// extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
extern IkokaNanoNRFBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
86
variants/ikoka_nano_nrf/variant.cpp
Normal file
86
variants/ikoka_nano_nrf/variant.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "variant.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
#include "nrf.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] =
|
||||
{
|
||||
// D0 .. D10
|
||||
2, // D0 is P0.02 (A0)
|
||||
3, // D1 is P0.03 (A1)
|
||||
28, // D2 is P0.28 (A2)
|
||||
29, // D3 is P0.29 (A3)
|
||||
4, // D4 is P0.04 (A4,SDA)
|
||||
5, // D5 is P0.05 (A5,SCL)
|
||||
43, // D6 is P1.11 (TX)
|
||||
44, // D7 is P1.12 (RX)
|
||||
45, // D8 is P1.13 (SCK)
|
||||
46, // D9 is P1.14 (MISO)
|
||||
47, // D10 is P1.15 (MOSI)
|
||||
|
||||
// LEDs
|
||||
26, // D11 is P0.26 (LED RED)
|
||||
6, // D12 is P0.06 (LED BLUE)
|
||||
30, // D13 is P0.30 (LED GREEN)
|
||||
14, // D14 is P0.14 (READ_BAT)
|
||||
|
||||
// LSM6DS3TR
|
||||
40, // D15 is P1.08 (6D_PWR)
|
||||
27, // D16 is P0.27 (6D_I2C_SCL)
|
||||
7, // D17 is P0.07 (6D_I2C_SDA)
|
||||
11, // D18 is P0.11 (6D_INT1)
|
||||
|
||||
// MIC
|
||||
42, // D19 is P1.10 (MIC_PWR)
|
||||
32, // D20 is P1.00 (PDM_CLK)
|
||||
16, // D21 is P0.16 (PDM_DATA)
|
||||
|
||||
// BQ25100
|
||||
13, // D22 is P0.13 (HICHG)
|
||||
17, // D23 is P0.17 (~CHG)
|
||||
|
||||
//
|
||||
21, // D24 is P0.21 (QSPI_SCK)
|
||||
25, // D25 is P0.25 (QSPI_CSN)
|
||||
20, // D26 is P0.20 (QSPI_SIO_0 DI)
|
||||
24, // D27 is P0.24 (QSPI_SIO_1 DO)
|
||||
22, // D28 is P0.22 (QSPI_SIO_2 WP)
|
||||
23, // D29 is P0.23 (QSPI_SIO_3 HOLD)
|
||||
|
||||
// NFC
|
||||
9, // D30 is P0.09 (NFC1)
|
||||
10, // D31 is P0.10 (NFC2)
|
||||
|
||||
// VBAT
|
||||
31, // D32 is P0.31 (VBAT)
|
||||
};
|
||||
|
||||
void initVariant()
|
||||
{
|
||||
// Disable reading of the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
//digitalWrite(VBAT_ENABLE, HIGH);
|
||||
// This was taken from Seeed github butis not coherent with the doc,
|
||||
// VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V
|
||||
// This induces a 3mA current in the resistors :( but it's better than burning the nrf
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
|
||||
// Low charging current (50mA)
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current
|
||||
//pinMode(PIN_CHARGING_CURRENT, INPUT);
|
||||
|
||||
// High charging current (100mA)
|
||||
pinMode(PIN_CHARGING_CURRENT, OUTPUT);
|
||||
digitalWrite(PIN_CHARGING_CURRENT, LOW);
|
||||
|
||||
pinMode(PIN_QSPI_CS, OUTPUT);
|
||||
digitalWrite(PIN_QSPI_CS, HIGH);
|
||||
|
||||
pinMode(LED_RED, OUTPUT);
|
||||
digitalWrite(LED_RED, HIGH);
|
||||
pinMode(LED_GREEN, OUTPUT);
|
||||
digitalWrite(LED_GREEN, HIGH);
|
||||
pinMode(LED_BLUE, OUTPUT);
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
}
|
||||
149
variants/ikoka_nano_nrf/variant.h
Normal file
149
variants/ikoka_nano_nrf/variant.h
Normal file
@@ -0,0 +1,149 @@
|
||||
#ifndef _IKOKA_NANO_NRF_H_
|
||||
#define _IKOKA_NANO_NRF_H_
|
||||
|
||||
/** Master clock frequency */
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
#define USE_LFXO // Board uses 32khz crystal for LF
|
||||
//#define USE_LFRC // Board uses RC for LF
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Headers
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif // __cplusplus
|
||||
|
||||
#define PINS_COUNT (33)
|
||||
#define NUM_DIGITAL_PINS (33)
|
||||
#define NUM_ANALOG_INPUTS (8)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED (LED_RED)
|
||||
#define LED_PWR (PINS_COUNT)
|
||||
#define PIN_NEOPIXEL (PINS_COUNT)
|
||||
#define NEOPIXEL_NUM (0)
|
||||
|
||||
#define LED_BUILTIN (PIN_LED)
|
||||
|
||||
#define LED_RED (11)
|
||||
#define LED_GREEN (13)
|
||||
#define LED_BLUE (12)
|
||||
|
||||
#define LED_STATE_ON (0) // State when LED is litted
|
||||
|
||||
// Buttons
|
||||
// #define PIN_BUTTON1 (PINS_COUNT)
|
||||
|
||||
// Digital PINs
|
||||
static const uint8_t D0 = 0 ;
|
||||
static const uint8_t D1 = 1 ;
|
||||
static const uint8_t D2 = 2 ;
|
||||
static const uint8_t D3 = 3 ;
|
||||
static const uint8_t D4 = 4 ;
|
||||
static const uint8_t D5 = 5 ;
|
||||
static const uint8_t D6 = 6 ;
|
||||
static const uint8_t D7 = 7 ;
|
||||
static const uint8_t D8 = 8 ;
|
||||
static const uint8_t D9 = 9 ;
|
||||
static const uint8_t D10 = 10;
|
||||
|
||||
#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
#define PIN_CHARGING_CURRENT (22) // Battery Charging current
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current
|
||||
|
||||
// Analog pins
|
||||
#define PIN_A0 (0)
|
||||
#define PIN_A1 (1)
|
||||
#define PIN_A2 (2)
|
||||
#define PIN_A3 (3)
|
||||
#define PIN_A4 (4)
|
||||
#define PIN_A5 (5)
|
||||
#define PIN_VBAT (32) // Read the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
#define BAT_NOT_CHARGING (23) // LOW when charging
|
||||
|
||||
#define AREF_VOLTAGE (3.0)
|
||||
#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge
|
||||
|
||||
static const uint8_t A0 = PIN_A0;
|
||||
static const uint8_t A1 = PIN_A1;
|
||||
static const uint8_t A2 = PIN_A2;
|
||||
static const uint8_t A3 = PIN_A3;
|
||||
static const uint8_t A4 = PIN_A4;
|
||||
static const uint8_t A5 = PIN_A5;
|
||||
|
||||
#define ADC_RESOLUTION (12)
|
||||
|
||||
// Other pins
|
||||
#define PIN_NFC1 (30)
|
||||
#define PIN_NFC2 (31)
|
||||
|
||||
// Serial interfaces
|
||||
#define PIN_SERIAL1_RX (7)
|
||||
#define PIN_SERIAL1_TX (6)
|
||||
|
||||
// SPI Interfaces
|
||||
#define SPI_INTERFACES_COUNT (2)
|
||||
|
||||
#define PIN_SPI_MISO (9)
|
||||
#define PIN_SPI_MOSI (10)
|
||||
#define PIN_SPI_SCK (8)
|
||||
|
||||
#define PIN_SPI1_MISO (25)
|
||||
#define PIN_SPI1_MOSI (26)
|
||||
#define PIN_SPI1_SCK (29)
|
||||
|
||||
// Lora SPI is on SPI0
|
||||
#define P_LORA_SCLK PIN_SPI_SCK
|
||||
#define P_LORA_MISO PIN_SPI_MISO
|
||||
#define P_LORA_MOSI PIN_SPI_MOSI
|
||||
|
||||
// Wire Interfaces
|
||||
#define WIRE_INTERFACES_COUNT (1)
|
||||
|
||||
// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 !
|
||||
// #define PIN_WIRE_SCL (16) // use WIRE1_SDA
|
||||
|
||||
static const uint8_t SDA = PIN_WIRE_SDA;
|
||||
static const uint8_t SCL = PIN_WIRE_SCL;
|
||||
|
||||
//#define PIN_WIRE1_SDA (17)
|
||||
//#define PIN_WIRE1_SCL (16)
|
||||
#define PIN_LSM6DS3TR_C_POWER (15)
|
||||
#define PIN_LSM6DS3TR_C_INT1 (18)
|
||||
|
||||
// PDM Interfaces
|
||||
#define PIN_PDM_PWR (19)
|
||||
#define PIN_PDM_CLK (20)
|
||||
#define PIN_PDM_DIN (21)
|
||||
|
||||
// QSPI Pins
|
||||
#define PIN_QSPI_SCK (24)
|
||||
#define PIN_QSPI_CS (25)
|
||||
#define PIN_QSPI_IO0 (26)
|
||||
#define PIN_QSPI_IO1 (27)
|
||||
#define PIN_QSPI_IO2 (28)
|
||||
#define PIN_QSPI_IO3 (29)
|
||||
|
||||
// On-board QSPI Flash
|
||||
#define EXTERNAL_FLASH_DEVICES (P25Q16H)
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Arduino objects - C++ only
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
94
variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp
Normal file
94
variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "IkokaStickNRFBoard.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
void IkokaStickNRFBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
pinMode(PIN_VBAT, INPUT);
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
digitalWrite(VBAT_ENABLE, HIGH);
|
||||
|
||||
#ifdef PIN_USER_BTN
|
||||
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
|
||||
Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
#endif
|
||||
|
||||
// pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
// digitalWrite(SX126X_POWER_EN, HIGH);
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool IkokaStickNRFBoard::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("XIAO_NRF52_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user