Compare commits
90 Commits
v1.12.0_0.
...
meshcore-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e22e63c9f8 | ||
|
|
81406cdb25 | ||
|
|
baa17d204b | ||
|
|
cf5c268cfd | ||
|
|
1ae9b18e10 | ||
|
|
291eb6d305 | ||
|
|
37d3afc17e | ||
|
|
516d784087 | ||
|
|
3088d1862a | ||
|
|
6f1e01a750 | ||
|
|
06ab9f7f6b | ||
|
|
8ad17d1022 | ||
|
|
eee42c5099 | ||
|
|
87717610f5 | ||
|
|
dcfc83fb3d | ||
|
|
b90da8e1c0 | ||
|
|
cb2ebbf2f8 | ||
|
|
34c1f13d55 | ||
|
|
bb5bdcf9e5 | ||
|
|
de9100785e | ||
|
|
b67decfba0 | ||
|
|
ca81f645ed | ||
|
|
5280433098 | ||
|
|
def01889aa | ||
|
|
8737c64fdb | ||
|
|
e6e87fb8ca | ||
|
|
15cce12efd | ||
|
|
f4748a7f9d | ||
|
|
b777a7c635 | ||
|
|
b14879ce2d | ||
|
|
f7c8cf1146 | ||
|
|
f864c5f547 | ||
|
|
9f4eeeeceb | ||
|
|
9d5c4865c3 | ||
|
|
213d085012 | ||
|
|
45564bad9b | ||
|
|
5b0884ad2d | ||
|
|
e52d57c065 | ||
|
|
a66773bac0 | ||
|
|
05e7b682b9 | ||
|
|
9c318561da | ||
|
|
2e0fa3ec46 | ||
|
|
8ee4867397 | ||
|
|
5a885bffe4 | ||
|
|
011edd3c99 | ||
|
|
3dc14976a0 | ||
|
|
b039af0b52 | ||
|
|
3e76161e9c | ||
|
|
d05d6abab8 | ||
|
|
c2abe894c9 | ||
|
|
13d0dff918 | ||
|
|
44b80d00c2 | ||
|
|
f6603fe7a5 | ||
|
|
39fb2902ec | ||
|
|
063f5056f2 | ||
|
|
1500a5a9cb | ||
|
|
ffc9815e9a | ||
|
|
bbc5f0c11a | ||
|
|
2e00298128 | ||
|
|
5de3e1bf32 | ||
|
|
a073ba4707 | ||
|
|
3e53df5082 | ||
|
|
0770618ee2 | ||
|
|
bf9c6cb50f | ||
|
|
87c78a98bd | ||
|
|
e8785dd9b0 | ||
|
|
2005977403 | ||
|
|
cafc212bb2 | ||
|
|
e2571accbe | ||
|
|
88452c412e | ||
|
|
2220eca4f3 | ||
|
|
a6e741e30e | ||
|
|
0abac35744 | ||
|
|
564a19d125 | ||
|
|
5df139f3d6 | ||
|
|
77675ab496 | ||
|
|
5ccae4bddc | ||
|
|
fb025fb67e | ||
|
|
beff18c53b | ||
|
|
f720338c03 | ||
|
|
e33d93dc7f | ||
|
|
8db42146d1 | ||
|
|
e418b0c0ab | ||
|
|
d11d8ea626 | ||
|
|
810fd561d2 | ||
|
|
519b97a90a | ||
|
|
30d6588792 | ||
|
|
23b4baa066 | ||
|
|
2b754d4295 | ||
|
|
eb4fa032ff |
43
.github/workflows/pr-build-check.yml
vendored
Normal file
43
.github/workflows/pr-build-check.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: PR Build Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'examples/**'
|
||||
- 'variants/**'
|
||||
- 'platformio.ini'
|
||||
- '.github/workflows/pr-build-check.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
environment:
|
||||
# ESP32-S3 (most common platform)
|
||||
- Heltec_v3_companion_radio_ble
|
||||
- Heltec_v3_repeater
|
||||
- Heltec_v3_room_server
|
||||
# nRF52
|
||||
- RAK_4631_companion_radio_ble
|
||||
- RAK_4631_repeater
|
||||
- RAK_4631_room_server
|
||||
# RP2040
|
||||
- PicoW_repeater
|
||||
# STM32
|
||||
- wio-e5-mini_repeater
|
||||
# ESP32-C6
|
||||
- LilyGo_Tlora_C6_repeater_
|
||||
|
||||
steps:
|
||||
- name: Clone Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Build Environment
|
||||
uses: ./.github/actions/setup-build-environment
|
||||
|
||||
- name: Build ${{ matrix.environment }}
|
||||
run: pio run -e ${{ matrix.environment }}
|
||||
@@ -41,7 +41,7 @@
|
||||
"name": "LilyGo T-Beam supreme (8MB Flash 8MB PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_ram_size": 8388608,
|
||||
"maximum_size": 8388608,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
|
||||
55
build.sh
55
build.sh
@@ -64,6 +64,8 @@ case $1 in
|
||||
;;
|
||||
esac
|
||||
|
||||
# cache project config json for use in get_platform_for_env()
|
||||
PIO_CONFIG_JSON=$(pio project config --json-output)
|
||||
|
||||
# $1 should be the string to find (case insensitive)
|
||||
get_pio_envs_containing_string() {
|
||||
@@ -87,6 +89,25 @@ get_pio_envs_ending_with_string() {
|
||||
done
|
||||
}
|
||||
|
||||
# get platform flag for a given environment
|
||||
# $1 should be the environment name
|
||||
get_platform_for_env() {
|
||||
local env_name=$1
|
||||
echo "$PIO_CONFIG_JSON" | python3 -c "
|
||||
import sys, json, re
|
||||
data = json.load(sys.stdin)
|
||||
for section, options in data:
|
||||
if section == 'env:$env_name':
|
||||
for key, value in options:
|
||||
if key == 'build_flags':
|
||||
for flag in value:
|
||||
match = re.search(r'(ESP32_PLATFORM|NRF52_PLATFORM|STM32_PLATFORM|RP2040_PLATFORM)', flag)
|
||||
if match:
|
||||
print(match.group(1))
|
||||
sys.exit(0)
|
||||
"
|
||||
}
|
||||
|
||||
# disable all debug logging flags if DISABLE_DEBUG=1 is set
|
||||
disable_debug_flags() {
|
||||
if [ "$DISABLE_DEBUG" == "1" ]; then
|
||||
@@ -96,6 +117,8 @@ disable_debug_flags() {
|
||||
|
||||
# build firmware for the provided pio env in $1
|
||||
build_firmware() {
|
||||
# get env platform for post build actions
|
||||
ENV_PLATFORM=($(get_platform_for_env $1))
|
||||
|
||||
# get git commit sha
|
||||
COMMIT_HASH=$(git rev-parse --short HEAD)
|
||||
@@ -126,27 +149,31 @@ build_firmware() {
|
||||
# build firmware target
|
||||
pio run -e $1
|
||||
|
||||
# build merge-bin for esp32 fresh install
|
||||
if [ -f .pio/build/$1/firmware.bin ]; then
|
||||
# build merge-bin for esp32 fresh install, copy .bins to out folder (e.g: Heltec_v3_room_server-v1.0.0-SHA.bin)
|
||||
if [ "$ENV_PLATFORM" == "ESP32_PLATFORM" ]; then
|
||||
pio run -t mergebin -e $1
|
||||
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# build .uf2 for nrf52 boards
|
||||
if [[ -f .pio/build/$1/firmware.zip && -f .pio/build/$1/firmware.hex ]]; then
|
||||
# build .uf2 for nrf52 boards, copy .uf2 and .zip to out folder (e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2)
|
||||
if [ "$ENV_PLATFORM" == "NRF52_PLATFORM" ]; then
|
||||
python3 bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840
|
||||
cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# copy .bin, .uf2, and .zip to out folder
|
||||
# e.g: Heltec_v3_room_server-v1.0.0-SHA.bin
|
||||
# e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2
|
||||
# for stm32, copy .bin and .hex to out folder
|
||||
if [ "$ENV_PLATFORM" == "STM32_PLATFORM" ]; then
|
||||
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware.hex out/${FIRMWARE_FILENAME}.hex 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# copy .bin for esp32 boards
|
||||
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true
|
||||
|
||||
# copy .zip and .uf2 of nrf52 boards
|
||||
cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true
|
||||
# for rp2040, copy .bin and .uf2 to out folder
|
||||
if [ "$ENV_PLATFORM" == "RP2040_PLATFORM" ]; then
|
||||
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
|
||||
cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -642,7 +642,7 @@
|
||||
**Usage:**
|
||||
- `region`
|
||||
|
||||
**Serial Only:** Yes
|
||||
**Serial Only:** For firmware older than 1.12.0
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56
|
||||
file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
|
||||
file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
|
||||
file.read(pad, 1); // 62
|
||||
file.read((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62
|
||||
file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
||||
file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64
|
||||
file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
||||
@@ -222,7 +222,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
|
||||
file.read((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76
|
||||
file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77
|
||||
file.read(pad, 2); // 78
|
||||
file.read((uint8_t *)&_prefs.path_hash_mode, sizeof(_prefs.path_hash_mode)); // 78
|
||||
file.read(pad, 1); // 79
|
||||
file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
||||
file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
||||
file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||
@@ -247,7 +248,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56
|
||||
file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
|
||||
file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
|
||||
file.write(pad, 1); // 62
|
||||
file.write((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62
|
||||
file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
||||
file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64
|
||||
file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
||||
@@ -257,7 +258,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
|
||||
file.write((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76
|
||||
file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77
|
||||
file.write(pad, 2); // 78
|
||||
file.write((uint8_t *)&_prefs.path_hash_mode, sizeof(_prefs.path_hash_mode)); // 78
|
||||
file.write(pad, 1); // 79
|
||||
file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
||||
file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
||||
file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
#define CMD_SEND_ANON_REQ 57
|
||||
#define CMD_SET_AUTOADD_CONFIG 58
|
||||
#define CMD_GET_AUTOADD_CONFIG 59
|
||||
#define CMD_GET_ALLOWED_REPEAT_FREQ 60
|
||||
#define CMD_SET_PATH_HASH_MODE 61
|
||||
|
||||
// Stats sub-types for CMD_GET_STATS
|
||||
#define STATS_TYPE_CORE 0
|
||||
@@ -88,6 +90,7 @@
|
||||
#define RESP_CODE_TUNING_PARAMS 23
|
||||
#define RESP_CODE_STATS 24 // v8+, second byte is stats type
|
||||
#define RESP_CODE_AUTOADD_CONFIG 25
|
||||
#define RESP_ALLOWED_REPEAT_FREQ 26
|
||||
|
||||
#define SEND_TIMEOUT_BASE_MILLIS 500
|
||||
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
|
||||
@@ -255,6 +258,15 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
|
||||
}
|
||||
|
||||
uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.5f);
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.2f);
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
uint8_t MyMesh::getExtraAckTransmitCount() const {
|
||||
return _prefs.multi_acks;
|
||||
}
|
||||
@@ -338,7 +350,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
||||
}
|
||||
|
||||
// add inbound-path to mem cache
|
||||
if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid
|
||||
if (path && mesh::Packet::isValidPathLen(path_len)) { // check path is valid
|
||||
AdvertPath* p = advert_paths;
|
||||
uint32_t oldest = 0xFFFFFFFF;
|
||||
for (int i = 0; i < ADVERT_PATH_TABLE_SIZE; i++) { // check if already in table, otherwise evict oldest
|
||||
@@ -355,8 +367,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
||||
memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix));
|
||||
strcpy(p->name, contact.name);
|
||||
p->recv_timestamp = getRTCClock()->getCurrentTime();
|
||||
p->path_len = path_len;
|
||||
memcpy(p->path, path, p->path_len);
|
||||
p->path_len = mesh::Packet::copyPath(p->path, path, path_len);
|
||||
}
|
||||
|
||||
if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[]
|
||||
@@ -455,26 +466,30 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MyMesh::allowPacketForward(const mesh::Packet* packet) {
|
||||
return _prefs.client_repeat != 0;
|
||||
}
|
||||
|
||||
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);
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
} 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);
|
||||
sendFlood(pkt, codes, delay_millis, _prefs.path_hash_mode + 1);
|
||||
}
|
||||
}
|
||||
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);
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
} 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);
|
||||
sendFlood(pkt, codes, delay_millis, _prefs.path_hash_mode + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,7 +686,7 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i
|
||||
if (tag == pending_discovery) { // check for matching response tag)
|
||||
pending_discovery = 0;
|
||||
|
||||
if (in_path_len > MAX_PATH_SIZE || out_path_len > MAX_PATH_SIZE) {
|
||||
if (!mesh::Packet::isValidPathLen(in_path_len) || !mesh::Packet::isValidPathLen(out_path_len)) {
|
||||
MESH_DEBUG_PRINTLN("onContactPathRecv, invalid path sizes: %d, %d", in_path_len, out_path_len);
|
||||
} else {
|
||||
int i = 0;
|
||||
@@ -680,11 +695,9 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6);
|
||||
i += 6; // pub_key_prefix
|
||||
out_frame[i++] = out_path_len;
|
||||
memcpy(&out_frame[i], out_path, out_path_len);
|
||||
i += out_path_len;
|
||||
i += mesh::Packet::writePath(&out_frame[i], out_path, out_path_len);
|
||||
out_frame[i++] = in_path_len;
|
||||
memcpy(&out_frame[i], in_path, in_path_len);
|
||||
i += in_path_len;
|
||||
i += mesh::Packet::writePath(&out_frame[i], in_path, in_path_len);
|
||||
// NOTE: telemetry data in 'extra' is discarded at present
|
||||
|
||||
_serial->writeFrame(out_frame, i);
|
||||
@@ -770,9 +783,10 @@ uint32_t MyMesh::calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const {
|
||||
return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis);
|
||||
}
|
||||
uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const {
|
||||
uint8_t path_hash_count = path_len & 63;
|
||||
return SEND_TIMEOUT_BASE_MILLIS +
|
||||
((pkt_airtime_millis * DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) *
|
||||
(path_len + 1));
|
||||
(path_hash_count + 1));
|
||||
}
|
||||
|
||||
void MyMesh::onSendTimeout() {}
|
||||
@@ -793,7 +807,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
strcpy(_prefs.node_name, "NONAME");
|
||||
_prefs.freq = LORA_FREQ;
|
||||
_prefs.sf = LORA_SF;
|
||||
@@ -881,6 +895,24 @@ uint32_t MyMesh::getBLEPin() {
|
||||
return _active_ble_pin;
|
||||
}
|
||||
|
||||
struct FreqRange {
|
||||
uint32_t lower_freq, upper_freq;
|
||||
};
|
||||
|
||||
static FreqRange repeat_freq_ranges[] = {
|
||||
{ 433000, 433000 },
|
||||
{ 869000, 869000 },
|
||||
{ 918000, 918000 }
|
||||
};
|
||||
|
||||
bool MyMesh::isValidClientRepeatFreq(uint32_t f) const {
|
||||
for (int i = 0; i < sizeof(repeat_freq_ranges)/sizeof(repeat_freq_ranges[0]); i++) {
|
||||
auto r = &repeat_freq_ranges[i];
|
||||
if (f >= r->lower_freq && f <= r->upper_freq) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyMesh::startInterface(BaseSerialInterface &serial) {
|
||||
_serial = &serial;
|
||||
serial.enable();
|
||||
@@ -904,6 +936,8 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
i += 40;
|
||||
StrHelper::strzcpy((char *)&out_frame[i], FIRMWARE_VERSION, 20);
|
||||
i += 20;
|
||||
out_frame[i++] = _prefs.client_repeat; // v9+
|
||||
out_frame[i++] = _prefs.path_hash_mode; // v10+
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (cmd_frame[0] == CMD_APP_START &&
|
||||
len >= 8) { // sent when app establishes connection, respond with node ID
|
||||
@@ -1081,7 +1115,8 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
}
|
||||
if (pkt) {
|
||||
if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop)
|
||||
sendFlood(pkt);
|
||||
unsigned long delay_millis = 0;
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
} else {
|
||||
sendZeroHop(pkt);
|
||||
}
|
||||
@@ -1093,7 +1128,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
uint8_t *pub_key = &cmd_frame[1];
|
||||
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
if (recipient) {
|
||||
recipient->out_path_len = -1;
|
||||
recipient->out_path_len = OUT_PATH_UNKNOWN;
|
||||
// recipient->lastmod = ?? shouldn't be needed, app already has this version of contact
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
writeOKFrame();
|
||||
@@ -1208,13 +1243,20 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
i += 4;
|
||||
uint8_t sf = cmd_frame[i++];
|
||||
uint8_t cr = cmd_frame[i++];
|
||||
uint8_t repeat = 0; // default - false
|
||||
if (len > i) {
|
||||
repeat = cmd_frame[i++]; // FIRMWARE_VER_CODE 9+
|
||||
}
|
||||
|
||||
if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 &&
|
||||
if (repeat && !isValidClientRepeatFreq(freq)) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 &&
|
||||
bw <= 500000) {
|
||||
_prefs.sf = sf;
|
||||
_prefs.cr = cr;
|
||||
_prefs.freq = (float)freq / 1000.0;
|
||||
_prefs.bw = (float)bw / 1000.0;
|
||||
_prefs.client_repeat = repeat;
|
||||
savePrefs();
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
@@ -1271,6 +1313,14 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
}
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_SET_PATH_HASH_MODE && cmd_frame[1] == 0 && len >= 3) {
|
||||
if (cmd_frame[2] >= 3) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else {
|
||||
_prefs.path_hash_mode = cmd_frame[2];
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) {
|
||||
if (dirty_contacts_expiry) { // is there are pending dirty contacts write needed?
|
||||
saveContacts();
|
||||
@@ -1408,7 +1458,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
memset(&req_data[2], 0, 3); // reserved
|
||||
getRNG()->random(&req_data[5], 4); // random blob to help make packet-hash unique
|
||||
auto save = recipient->out_path_len; // temporarily force sendRequest() to flood
|
||||
recipient->out_path_len = -1;
|
||||
recipient->out_path_len = OUT_PATH_UNKNOWN;
|
||||
int result = sendRequest(*recipient, req_data, sizeof(req_data), tag, est_timeout);
|
||||
recipient->out_path_len = save;
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
@@ -1645,11 +1695,12 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
out_frame[0] = RESP_CODE_ADVERT_PATH;
|
||||
memcpy(&out_frame[1], &found->recv_timestamp, 4);
|
||||
out_frame[5] = found->path_len;
|
||||
memcpy(&out_frame[6], found->path, found->path_len);
|
||||
_serial->writeFrame(out_frame, 6 + found->path_len);
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_ADVERT_PATH;
|
||||
memcpy(&out_frame[i], &found->recv_timestamp, 4); i += 4;
|
||||
out_frame[i++] = found->path_len;
|
||||
i += mesh::Packet::writePath(&out_frame[i], found->path, found->path_len);
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND);
|
||||
}
|
||||
@@ -1741,6 +1792,15 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
out_frame[i++] = RESP_CODE_AUTOADD_CONFIG;
|
||||
out_frame[i++] = _prefs.autoadd_config;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (cmd_frame[0] == CMD_GET_ALLOWED_REPEAT_FREQ) {
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_ALLOWED_REPEAT_FREQ;
|
||||
for (int k = 0; k < sizeof(repeat_freq_ranges)/sizeof(repeat_freq_ranges[0]) && i + 8 < sizeof(out_frame); k++) {
|
||||
auto r = &repeat_freq_ranges[k];
|
||||
memcpy(&out_frame[i], &r->lower_freq, 4); i += 4;
|
||||
memcpy(&out_frame[i], &r->upper_freq, 4); i += 4;
|
||||
}
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} 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 8
|
||||
#define FIRMWARE_VER_CODE 10
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "29 Jan 2026"
|
||||
#define FIRMWARE_BUILD_DATE "15 Feb 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.12.0"
|
||||
#define FIRMWARE_VERSION "v1.13.0"
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -106,8 +106,11 @@ protected:
|
||||
float getAirtimeBudgetFactor() const override;
|
||||
int getInterferenceThreshold() const override;
|
||||
int calcRxDelay(float score, uint32_t air_time) const override;
|
||||
uint32_t getRetransmitDelay(const mesh::Packet *packet) override;
|
||||
uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override;
|
||||
uint8_t getExtraAckTransmitCount() const override;
|
||||
bool filterRecvFloodPacket(mesh::Packet* packet) override;
|
||||
bool allowPacketForward(const 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;
|
||||
@@ -176,6 +179,7 @@ private:
|
||||
|
||||
void checkCLIRescueCmd();
|
||||
void checkSerialInterface();
|
||||
bool isValidClientRepeatFreq(uint32_t f) const;
|
||||
|
||||
// helpers, short-cuts
|
||||
void saveChannels() { _store->saveChannels(this); }
|
||||
|
||||
@@ -28,4 +28,6 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled)
|
||||
uint32_t gps_interval; // GPS read interval in seconds
|
||||
uint8_t autoadd_config; // bitmask for auto-add contacts config
|
||||
uint8_t client_repeat;
|
||||
uint8_t path_hash_mode; // which path mode to use when sending
|
||||
};
|
||||
@@ -131,6 +131,14 @@ class HomeScreen : public UIScreen {
|
||||
// fill the battery based on the percentage
|
||||
int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100;
|
||||
display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4);
|
||||
|
||||
// show muted icon if buzzer is muted
|
||||
#ifdef PIN_BUZZER
|
||||
if (_task->isBuzzerQuiet()) {
|
||||
display.setColor(DisplayDriver::RED);
|
||||
display.drawXbm(iconX - 9, iconY + 1, muted_icon, 8, 8);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
CayenneLPP sensors_lpp;
|
||||
|
||||
@@ -78,6 +78,14 @@ public:
|
||||
bool hasDisplay() const { return _display != NULL; }
|
||||
bool isButtonPressed() const;
|
||||
|
||||
bool isBuzzerQuiet() {
|
||||
#ifdef PIN_BUZZER
|
||||
return buzzer.isQuiet();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void toggleBuzzer();
|
||||
bool getGPSState();
|
||||
void toggleGPS();
|
||||
|
||||
@@ -115,4 +115,8 @@ static const uint8_t advert_icon[] = {
|
||||
0x38, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x30,
|
||||
0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t muted_icon[] = {
|
||||
0x20, 0x6a, 0xea, 0xe4, 0xe4, 0xea, 0x6a, 0x20
|
||||
};
|
||||
@@ -129,7 +129,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
|
||||
}
|
||||
|
||||
if (is_flood) {
|
||||
client->out_path_len = -1; // need to rediscover out_path
|
||||
client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path
|
||||
}
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
@@ -147,9 +147,12 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
|
||||
uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
// request data has: {reply-path-len}{reply-path}
|
||||
reply_path_len = *data++ & 0x3F;
|
||||
memcpy(reply_path, data, reply_path_len);
|
||||
// data += reply_path_len;
|
||||
reply_path_len = *data & 63;
|
||||
reply_path_hash_size = (*data >> 6) + 1;
|
||||
data++;
|
||||
|
||||
memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size);
|
||||
// data += (uint8_t)reply_path_len * reply_path_hash_size;
|
||||
|
||||
memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
@@ -163,9 +166,12 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send
|
||||
uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
// request data has: {reply-path-len}{reply-path}
|
||||
reply_path_len = *data++ & 0x3F;
|
||||
memcpy(reply_path, data, reply_path_len);
|
||||
// data += reply_path_len;
|
||||
reply_path_len = *data & 63;
|
||||
reply_path_hash_size = (*data >> 6) + 1;
|
||||
data++;
|
||||
|
||||
memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size);
|
||||
// data += (uint8_t)reply_path_len * reply_path_hash_size;
|
||||
|
||||
memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
@@ -180,9 +186,12 @@ uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender
|
||||
uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
// request data has: {reply-path-len}{reply-path}
|
||||
reply_path_len = *data++ & 0x3F;
|
||||
memcpy(reply_path, data, reply_path_len);
|
||||
// data += reply_path_len;
|
||||
reply_path_len = *data & 63;
|
||||
reply_path_hash_size = (*data >> 6) + 1;
|
||||
data++;
|
||||
|
||||
memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size);
|
||||
// data += (uint8_t)reply_path_len * reply_path_hash_size;
|
||||
|
||||
memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
@@ -292,6 +301,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
|
||||
// create copy of neighbours list, skipping empty entries so we can sort it separately from main list
|
||||
int16_t neighbours_count = 0;
|
||||
#if MAX_NEIGHBOURS
|
||||
NeighbourInfo* sorted_neighbours[MAX_NEIGHBOURS];
|
||||
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
|
||||
auto neighbour = &neighbours[i];
|
||||
@@ -327,6 +337,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
return a->snr < b->snr; // asc
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
// build results buffer
|
||||
int results_count = 0;
|
||||
@@ -341,6 +352,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
break;
|
||||
}
|
||||
|
||||
#if MAX_NEIGHBOURS
|
||||
// add next neighbour to results
|
||||
auto neighbour = sorted_neighbours[index + offset];
|
||||
uint32_t heard_seconds_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp;
|
||||
@@ -348,6 +360,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
memcpy(&results_buffer[results_offset], &heard_seconds_ago, 4); results_offset += 4;
|
||||
memcpy(&results_buffer[results_offset], &neighbour->snr, 1); results_offset += 1;
|
||||
results_count++;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -385,11 +398,19 @@ 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() && packet->getPathHashCount() >= _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;
|
||||
}
|
||||
// Limit flood advert paket forwarding using a probabilistic reduction defined by P(h) = 0.308^(hops-1)
|
||||
// https://github.com/meshcore-dev/MeshCore/issues/1223
|
||||
double_t roll_dice = (double)rand() / RAND_MAX;
|
||||
double_t forw_prob = pow(_prefs.flood_advert_base, packet->path_len - 1);
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->isRouteFlood() && roll_dice > forw_prob)
|
||||
return false;
|
||||
|
||||
// all other packets
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -480,11 +501,11 @@ 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);
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
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);
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
@@ -493,7 +514,10 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
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) {
|
||||
if ((pkt->getPayloadType() == PAYLOAD_TYPE_GRP_TXT ||
|
||||
pkt->getPayloadType() == PAYLOAD_TYPE_GRP_DATA ||
|
||||
pkt->getPayloadType() == PAYLOAD_TYPE_ADVERT) &&
|
||||
region_map.getWildcard().flags & REGION_DENY_FLOOD) {
|
||||
recv_pkt_region = NULL;
|
||||
} else {
|
||||
recv_pkt_region = ®ion_map.getWildcard();
|
||||
@@ -534,13 +558,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else if (reply_path_len < 0) {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
||||
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
||||
if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY);
|
||||
uint8_t path_len = ((reply_path_hash_size - 1) << 6) | (reply_path_len & 63);
|
||||
if (reply) sendDirect(reply, reply_path, path_len, SERVER_RESPONSE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -609,15 +634,15 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
mesh::Packet *reply =
|
||||
createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
|
||||
if (reply) {
|
||||
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -647,8 +672,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
|
||||
mesh::Packet *ack = createAck(ack_hash);
|
||||
if (ack) {
|
||||
if (client->out_path_len < 0) {
|
||||
sendFlood(ack, TXT_ACK_DELAY);
|
||||
if (client->out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFlood(ack, TXT_ACK_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY);
|
||||
}
|
||||
@@ -675,8 +700,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
|
||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
|
||||
if (reply) {
|
||||
if (client->out_path_len < 0) {
|
||||
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
|
||||
if (client->out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFlood(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize());
|
||||
} else {
|
||||
sendDirect(reply, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS);
|
||||
}
|
||||
@@ -697,7 +722,8 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
||||
MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len);
|
||||
auto client = acl.getClientByIdx(i);
|
||||
|
||||
memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect()
|
||||
// store a copy of path, for sendDirect()
|
||||
client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len);
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i);
|
||||
@@ -738,6 +764,47 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this
|
||||
}
|
||||
}
|
||||
} else if (type == CTL_TYPE_NODE_DISCOVER_RESP && packet->payload_len >= 6) {
|
||||
uint8_t node_type = packet->payload[0] & 0x0F;
|
||||
if (node_type != ADV_TYPE_REPEATER) {
|
||||
return;
|
||||
}
|
||||
if (packet->payload_len < 6 + PUB_KEY_SIZE) {
|
||||
MESH_DEBUG_PRINTLN("onControlDataRecv: DISCOVER_RESP pubkey too short: %d", (uint32_t)packet->payload_len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pending_discover_tag == 0 || millisHasNowPassed(pending_discover_until)) {
|
||||
pending_discover_tag = 0;
|
||||
return;
|
||||
}
|
||||
uint32_t tag;
|
||||
memcpy(&tag, &packet->payload[2], 4);
|
||||
if (tag != pending_discover_tag) {
|
||||
return;
|
||||
}
|
||||
|
||||
mesh::Identity id(&packet->payload[6]);
|
||||
if (id.matches(self_id)) {
|
||||
return;
|
||||
}
|
||||
putNeighbour(id, rtc_clock.getCurrentTime(), packet->getSNR());
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::sendNodeDiscoverReq() {
|
||||
uint8_t data[10];
|
||||
data[0] = CTL_TYPE_NODE_DISCOVER_REQ; // prefix_only=0
|
||||
data[1] = (1 << ADV_TYPE_REPEATER);
|
||||
getRNG()->random(&data[2], 4); // tag
|
||||
memcpy(&pending_discover_tag, &data[2], 4);
|
||||
pending_discover_until = futureMillis(60000);
|
||||
uint32_t since = 0;
|
||||
memcpy(&data[6], &since, 4);
|
||||
|
||||
auto pkt = createControlData(data, sizeof(data));
|
||||
if (pkt) {
|
||||
sendZeroHop(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -768,10 +835,10 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_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
|
||||
_prefs.direct_tx_delay_factor = 0.3f; // was 0.2
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -782,7 +849,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.cr = LORA_CR;
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
|
||||
_prefs.flood_advert_interval = 12; // 12 hours
|
||||
_prefs.flood_advert_interval = 0; // 12 hours
|
||||
_prefs.flood_advert_base = 0.308f;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
|
||||
@@ -801,6 +869,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
|
||||
_prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier
|
||||
|
||||
pending_discover_tag = 0;
|
||||
pending_discover_until = 0;
|
||||
}
|
||||
|
||||
void MyMesh::begin(FILESYSTEM *fs) {
|
||||
@@ -858,7 +929,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) {
|
||||
mesh::Packet *pkt = createSelfAdvert();
|
||||
if (pkt) {
|
||||
if (flood) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
} else {
|
||||
sendZeroHop(pkt, delay_millis);
|
||||
}
|
||||
@@ -1168,6 +1239,15 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
} else {
|
||||
strcpy(reply, "Err - ??");
|
||||
}
|
||||
} else if (memcmp(command, "discover.neighbors", 18) == 0) {
|
||||
const char* sub = command + 18;
|
||||
while (*sub == ' ') sub++;
|
||||
if (*sub != 0) {
|
||||
strcpy(reply, "Err - discover.neighbors has no options");
|
||||
} else {
|
||||
sendNodeDiscoverReq();
|
||||
strcpy(reply, "OK - Discover sent");
|
||||
}
|
||||
} else{
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
}
|
||||
|
||||
@@ -69,11 +69,11 @@ struct NeighbourInfo {
|
||||
};
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "29 Jan 2026"
|
||||
#define FIRMWARE_BUILD_DATE "15 Feb 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.12.0"
|
||||
#define FIRMWARE_VERSION "v1.13.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "repeater"
|
||||
@@ -92,11 +92,14 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||
uint8_t reply_path[MAX_PATH_SIZE];
|
||||
int8_t reply_path_len;
|
||||
uint8_t reply_path_hash_size;
|
||||
TransportKeyStore key_store;
|
||||
RegionMap region_map, temp_map;
|
||||
RegionEntry* load_stack[8];
|
||||
RegionEntry* recv_pkt_region;
|
||||
RateLimiter discover_limiter, anon_limiter;
|
||||
uint32_t pending_discover_tag;
|
||||
unsigned long pending_discover_until;
|
||||
bool region_load_active;
|
||||
unsigned long dirty_contacts_expiry;
|
||||
#if MAX_NEIGHBOURS
|
||||
@@ -116,6 +119,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
#endif
|
||||
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||
void sendNodeDiscoverReq();
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
||||
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
|
||||
@@ -73,13 +73,15 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) {
|
||||
|
||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len);
|
||||
if (reply) {
|
||||
if (client->out_path_len < 0) {
|
||||
sendFlood(reply);
|
||||
if (client->out_path_len == OUT_PATH_UNKNOWN) {
|
||||
unsigned long delay_millis = 0;
|
||||
sendFlood(reply, delay_millis, _prefs.path_hash_mode + 1);
|
||||
client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD);
|
||||
} else {
|
||||
sendDirect(reply, client->out_path, client->out_path_len);
|
||||
client->extra.room.ack_timeout =
|
||||
futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1));
|
||||
|
||||
uint8_t path_hash_count = client->out_path_len & 63;
|
||||
client->extra.room.ack_timeout = futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (path_hash_count + 1));
|
||||
}
|
||||
_num_post_pushes++; // stats
|
||||
} else {
|
||||
@@ -264,17 +266,26 @@ 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);
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
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);
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
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() && packet->getPathHashCount() >= _prefs.flood_max) return false;
|
||||
|
||||
// Limit flood advert paket forwarding using a probabilistic reduction defined by P(h) = 0.308^(hops-1)
|
||||
// https://github.com/meshcore-dev/MeshCore/issues/1223
|
||||
double_t roll_dice = (double)rand() / RAND_MAX;
|
||||
double_t forw_prob = pow(_prefs.flood_advert_base, packet->path_len - 1);
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->isRouteFlood() && roll_dice > forw_prob)
|
||||
return false;
|
||||
|
||||
// all other packets
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -333,7 +344,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
}
|
||||
|
||||
if (packet->isRouteFlood()) {
|
||||
client->out_path_len = -1; // need to rediscover out_path
|
||||
client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path
|
||||
}
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
@@ -353,14 +364,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, 13);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13);
|
||||
if (reply) {
|
||||
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,9 +459,9 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
|
||||
uint32_t delay_millis;
|
||||
if (send_ack) {
|
||||
if (client->out_path_len < 0) {
|
||||
if (client->out_path_len == OUT_PATH_UNKNOWN) {
|
||||
mesh::Packet *ack = createAck(ack_hash);
|
||||
if (ack) sendFlood(ack, TXT_ACK_DELAY);
|
||||
if (ack) sendFlood(ack, TXT_ACK_DELAY, packet->getPathHashSize());
|
||||
delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS;
|
||||
} else {
|
||||
uint32_t d = TXT_ACK_DELAY;
|
||||
@@ -482,8 +493,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
|
||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
|
||||
if (reply) {
|
||||
if (client->out_path_len < 0) {
|
||||
sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY);
|
||||
if (client->out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
sendDirect(reply, client->out_path, client->out_path_len, delay_millis + SERVER_RESPONSE_DELAY);
|
||||
}
|
||||
@@ -521,7 +532,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
// if client sends too quickly, evict()
|
||||
|
||||
// RULE: only send keep_alive response DIRECT!
|
||||
if (client->out_path_len >= 0) {
|
||||
if (client->out_path_len != OUT_PATH_UNKNOWN) {
|
||||
uint32_t ack_hash; // calc ACK to prove to sender that we got request
|
||||
mesh::Utils::sha256((uint8_t *)&ack_hash, 4, data, 9, client->id.pub_key, PUB_KEY_SIZE);
|
||||
|
||||
@@ -538,14 +549,14 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
|
||||
if (reply) {
|
||||
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -563,7 +574,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
||||
if (i >= 0 && i < acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context)
|
||||
MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len);
|
||||
auto client = acl.getClientByIdx(i);
|
||||
memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect()
|
||||
client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); // store a copy of path, for sendDirect()
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i);
|
||||
@@ -597,7 +608,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_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
|
||||
@@ -612,7 +623,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
_prefs.disable_fwd = 1;
|
||||
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
|
||||
_prefs.flood_advert_interval = 12; // 12 hours
|
||||
_prefs.flood_advert_interval = 0; // 12 hours
|
||||
_prefs.flood_advert_base = 0.308f;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
#ifdef ROOM_PASSWORD
|
||||
@@ -679,7 +691,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) {
|
||||
mesh::Packet *pkt = createSelfAdvert();
|
||||
if (pkt) {
|
||||
if (flood) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
} else {
|
||||
sendZeroHop(pkt, delay_millis);
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "29 Jan 2026"
|
||||
#define FIRMWARE_BUILD_DATE "15 Feb 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.12.0"
|
||||
#define FIRMWARE_VERSION "v1.13.0"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
|
||||
@@ -213,7 +213,7 @@ protected:
|
||||
}
|
||||
|
||||
void onContactPathUpdated(const ContactInfo& contact) override {
|
||||
Serial.printf("PATH to: %s, path_len=%d\n", contact.name, (int32_t) contact.out_path_len);
|
||||
Serial.printf("PATH to: %s, path_len=%d\n", contact.name, (uint32_t) contact.out_path_len);
|
||||
saveContacts();
|
||||
}
|
||||
|
||||
@@ -266,8 +266,9 @@ protected:
|
||||
return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis);
|
||||
}
|
||||
uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override {
|
||||
uint8_t path_hash_count = path_len & 63;
|
||||
return SEND_TIMEOUT_BASE_MILLIS +
|
||||
( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_len + 1));
|
||||
( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_hash_count + 1));
|
||||
}
|
||||
|
||||
void onSendTimeout() override {
|
||||
@@ -280,7 +281,7 @@ public:
|
||||
{
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 2.0; // one third
|
||||
_prefs.airtime_factor = 1.0;
|
||||
strcpy(_prefs.node_name, "NONAME");
|
||||
_prefs.freq = LORA_FREQ;
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
|
||||
@@ -258,10 +258,11 @@ void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) {
|
||||
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len);
|
||||
if (pkt) {
|
||||
if (c->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
if (c->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
|
||||
sendDirect(pkt, c->out_path, c->out_path_len);
|
||||
} else {
|
||||
sendFlood(pkt);
|
||||
unsigned long delay_millis = 0;
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
}
|
||||
}
|
||||
t->send_expiry = futureMillis(ALERT_ACK_EXPIRY_MILLIS);
|
||||
@@ -302,7 +303,7 @@ float SensorMesh::getAirtimeBudgetFactor() const {
|
||||
|
||||
bool SensorMesh::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() && packet->getPathHashCount() >= _prefs.flood_max) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -312,11 +313,11 @@ int SensorMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
}
|
||||
|
||||
uint32_t SensorMesh::getRetransmitDelay(const mesh::Packet* packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6)*t;
|
||||
}
|
||||
uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6)*t;
|
||||
}
|
||||
int SensorMesh::getInterferenceThreshold() const {
|
||||
@@ -360,7 +361,7 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t*
|
||||
}
|
||||
|
||||
if (is_flood) {
|
||||
client->out_path_len = -1; // need to rediscover out_path
|
||||
client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path
|
||||
}
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
@@ -468,10 +469,10 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
||||
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,10 +497,10 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash) {
|
||||
if (dest.out_path_len < 0) {
|
||||
void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size) {
|
||||
if (dest.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
mesh::Packet* ack = createAck(ack_hash);
|
||||
if (ack) sendFlood(ack, TXT_ACK_DELAY);
|
||||
if (ack) sendFlood(ack, TXT_ACK_DELAY, path_hash_size);
|
||||
} else {
|
||||
uint32_t d = TXT_ACK_DELAY;
|
||||
if (getExtraAckTransmitCount() > 0) {
|
||||
@@ -537,14 +538,14 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
// 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, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len);
|
||||
if (reply) {
|
||||
if (from->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
if (from->out_path_len != OUT_PATH_UNKNOWN) { // 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);
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,9 +568,9 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
// 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) sendFlood(path, TXT_ACK_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
sendAckTo(*from, ack_hash);
|
||||
sendAckTo(*from, ack_hash, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
} else if (flags == TXT_TYPE_CLI_DATA) {
|
||||
@@ -596,8 +597,8 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
|
||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len);
|
||||
if (reply) {
|
||||
if (from->out_path_len < 0) {
|
||||
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
|
||||
if (from->out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFlood(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize());
|
||||
} else {
|
||||
sendDirect(reply, from->out_path, from->out_path_len, CLI_REPLY_DELAY_MILLIS);
|
||||
}
|
||||
@@ -666,7 +667,7 @@ bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint
|
||||
MESH_DEBUG_PRINTLN("PATH to contact, path_len=%d", (uint32_t) path_len);
|
||||
// NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path.
|
||||
// FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?)
|
||||
memcpy(from->out_path, path, from->out_path_len = path_len); // store a copy of path, for sendDirect()
|
||||
from->out_path_len = mesh::Packet::copyPath(from->out_path, path, path_len); // store a copy of path, for sendDirect()
|
||||
from->last_activity = getRTCClock()->getCurrentTime();
|
||||
|
||||
// REVISIT: maybe make ALL out_paths non-persisted to minimise flash writes??
|
||||
@@ -705,7 +706,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_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
|
||||
@@ -791,7 +792,7 @@ void SensorMesh::sendSelfAdvertisement(int delay_millis, bool flood) {
|
||||
mesh::Packet* pkt = createSelfAdvert();
|
||||
if (pkt) {
|
||||
if (flood) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
} else {
|
||||
sendZeroHop(pkt, delay_millis);
|
||||
}
|
||||
@@ -868,7 +869,8 @@ void SensorMesh::loop() {
|
||||
|
||||
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
|
||||
mesh::Packet* pkt = createSelfAdvert();
|
||||
if (pkt) sendFlood(pkt);
|
||||
unsigned long delay_millis = 0;
|
||||
if (pkt) sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
|
||||
updateFloodAdvertTimer(); // schedule next flood advert
|
||||
updateAdvertTimer(); // also schedule local advert (so they don't overlap)
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "29 Jan 2026"
|
||||
#define FIRMWARE_BUILD_DATE "15 Feb 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.12.0"
|
||||
#define FIRMWARE_VERSION "v1.13.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "sensor"
|
||||
@@ -128,7 +128,7 @@ protected:
|
||||
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, uint8_t flags, size_t len);
|
||||
void sendAckTo(const ClientInfo& dest, uint32_t ack_hash);
|
||||
void sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size=1);
|
||||
private:
|
||||
FILESYSTEM* _fs;
|
||||
unsigned long next_local_advert, next_flood_advert;
|
||||
|
||||
@@ -24,9 +24,9 @@ lib_deps =
|
||||
melopero/Melopero RV3028 @ ^1.1.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
|
||||
-D LORA_SF=11
|
||||
-D LORA_FREQ=869.618
|
||||
-D LORA_BW=62.5
|
||||
-D LORA_SF=8
|
||||
-D ENABLE_ADVERT_ON_BOOT=1
|
||||
-D ENABLE_PRIVATE_KEY_IMPORT=1 ; NOTE: comment these out for more secure firmware
|
||||
-D ENABLE_PRIVATE_KEY_EXPORT=1
|
||||
@@ -59,6 +59,7 @@ platform = platformio/espressif32@6.11.0
|
||||
monitor_filters = esp32_exception_decoder
|
||||
extra_scripts = merge-bin.py
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
-D ESP32_PLATFORM
|
||||
; -D ESP32_CPU_FREQ=80 ; change it to your need
|
||||
build_src_filter = ${arduino_base.build_src_filter}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
#define MAX_RX_DELAY_MILLIS 32000 // 32 seconds
|
||||
#define MAX_RX_DELAY_MILLIS 32000 // 32 seconds
|
||||
#define MIN_TX_BUDGET_RESERVE_MS 100 // min budget (ms) required before allowing next TX
|
||||
#define MIN_TX_BUDGET_AIRTIME_DIV 2 // require at least 1/N of estimated airtime as budget before TX
|
||||
|
||||
#ifndef NOISE_FLOOR_CALIB_INTERVAL
|
||||
#define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds
|
||||
@@ -20,12 +22,34 @@ void Dispatcher::begin() {
|
||||
_err_flags = 0;
|
||||
radio_nonrx_start = _ms->getMillis();
|
||||
|
||||
duty_cycle_window_ms = getDutyCycleWindowMs();
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
tx_budget_ms = (unsigned long)(duty_cycle_window_ms * duty_cycle);
|
||||
last_budget_update = _ms->getMillis();
|
||||
|
||||
_radio->begin();
|
||||
prev_isrecv_mode = _radio->isInRecvMode();
|
||||
}
|
||||
|
||||
float Dispatcher::getAirtimeBudgetFactor() const {
|
||||
return 2.0; // default, 33.3% (1/3rd)
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
void Dispatcher::updateTxBudget() {
|
||||
unsigned long now = _ms->getMillis();
|
||||
unsigned long elapsed = now - last_budget_update;
|
||||
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
unsigned long max_budget = (unsigned long)(getDutyCycleWindowMs() * duty_cycle);
|
||||
unsigned long refill = (unsigned long)(elapsed * duty_cycle);
|
||||
|
||||
if (refill > 0) {
|
||||
tx_budget_ms += refill;
|
||||
if (tx_budget_ms > max_budget) {
|
||||
tx_budget_ms = max_budget;
|
||||
}
|
||||
last_budget_update = now;
|
||||
}
|
||||
}
|
||||
|
||||
int Dispatcher::calcRxDelay(float score, uint32_t air_time) const {
|
||||
@@ -61,14 +85,27 @@ void Dispatcher::loop() {
|
||||
if (outbound) { // waiting for outbound send to be completed
|
||||
if (_radio->isSendComplete()) {
|
||||
long t = _ms->getMillis() - outbound_start;
|
||||
total_air_time += t; // keep track of how much air time we are using
|
||||
total_air_time += t;
|
||||
//Serial.print(" airtime="); Serial.println(t);
|
||||
|
||||
// will need radio silence up to next_tx_time
|
||||
next_tx_time = futureMillis(t * getAirtimeBudgetFactor());
|
||||
updateTxBudget();
|
||||
|
||||
if (t > tx_budget_ms) {
|
||||
tx_budget_ms = 0;
|
||||
} else {
|
||||
tx_budget_ms -= t;
|
||||
}
|
||||
|
||||
if (tx_budget_ms < MIN_TX_BUDGET_RESERVE_MS) {
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
unsigned long needed = MIN_TX_BUDGET_RESERVE_MS - tx_budget_ms;
|
||||
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
|
||||
} else {
|
||||
next_tx_time = _ms->getMillis();
|
||||
}
|
||||
|
||||
_radio->onSendFinished();
|
||||
logTx(outbound, 2 + outbound->path_len + outbound->payload_len);
|
||||
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
|
||||
if (outbound->isRouteFlood()) {
|
||||
n_sent_flood++;
|
||||
} else {
|
||||
@@ -80,7 +117,7 @@ void Dispatcher::loop() {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): WARNING: outbound packed send timed out!", getLogDateTime());
|
||||
|
||||
_radio->onSendFinished();
|
||||
logTxFail(outbound, 2 + outbound->path_len + outbound->payload_len);
|
||||
logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
|
||||
|
||||
releasePacket(outbound); // return to pool
|
||||
outbound = NULL;
|
||||
@@ -108,6 +145,48 @@ void Dispatcher::loop() {
|
||||
checkSend();
|
||||
}
|
||||
|
||||
bool Dispatcher::tryParsePacket(Packet* pkt, const uint8_t* raw, int len) {
|
||||
int i = 0;
|
||||
|
||||
pkt->header = raw[i++];
|
||||
if (pkt->getPayloadVer() > PAYLOAD_VER_1) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): unsupported packet version", getLogDateTime());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pkt->hasTransportCodes()) {
|
||||
memcpy(&pkt->transport_codes[0], &raw[i], 2); i += 2;
|
||||
memcpy(&pkt->transport_codes[1], &raw[i], 2); i += 2;
|
||||
} else {
|
||||
pkt->transport_codes[0] = pkt->transport_codes[1] = 0;
|
||||
}
|
||||
|
||||
pkt->path_len = raw[i++];
|
||||
uint8_t path_mode = pkt->path_len >> 6; // upper 2 bits (legacy firmware: 00)
|
||||
if (path_mode == 3) { // Reserved for future
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): unsupported path mode: 3", getLogDateTime());
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t path_byte_len = (pkt->path_len & 63) * pkt->getPathHashSize();
|
||||
if (path_byte_len > MAX_PATH_SIZE || i + path_byte_len > len) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", getLogDateTime(), len);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(pkt->path, &raw[i], path_byte_len); i += path_byte_len;
|
||||
|
||||
pkt->payload_len = len - i; // payload is remainder
|
||||
if (pkt->payload_len > sizeof(pkt->payload)) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): packet payload too big, payload_len=%d", getLogDateTime(), (uint32_t)pkt->payload_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(pkt->payload, &raw[i], pkt->payload_len);
|
||||
|
||||
return true; // success
|
||||
}
|
||||
|
||||
void Dispatcher::checkRecv() {
|
||||
Packet* pkt;
|
||||
float score;
|
||||
@@ -122,45 +201,14 @@ void Dispatcher::checkRecv() {
|
||||
if (pkt == NULL) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): WARNING: received data, no unused packets available!", getLogDateTime());
|
||||
} else {
|
||||
int i = 0;
|
||||
#ifdef NODE_ID
|
||||
uint8_t sender_id = raw[i++];
|
||||
if (sender_id == NODE_ID - 1 || sender_id == NODE_ID + 1) { // simulate that NODE_ID can only hear NODE_ID-1 or NODE_ID+1, eg. 3 can't hear 1
|
||||
if (tryParsePacket(pkt, raw, len)) {
|
||||
pkt->_snr = _radio->getLastSNR() * 4.0f;
|
||||
score = _radio->packetScore(_radio->getLastSNR(), len);
|
||||
air_time = _radio->getEstAirtimeFor(len);
|
||||
rx_air_time += air_time;
|
||||
} else {
|
||||
_mgr->free(pkt); // put back into pool
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
pkt->header = raw[i++];
|
||||
if (pkt->hasTransportCodes()) {
|
||||
memcpy(&pkt->transport_codes[0], &raw[i], 2); i += 2;
|
||||
memcpy(&pkt->transport_codes[1], &raw[i], 2); i += 2;
|
||||
} else {
|
||||
pkt->transport_codes[0] = pkt->transport_codes[1] = 0;
|
||||
}
|
||||
pkt->path_len = raw[i++];
|
||||
|
||||
if (pkt->path_len > MAX_PATH_SIZE || i + pkt->path_len > len) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", getLogDateTime(), len);
|
||||
_mgr->free(pkt); // put back into pool
|
||||
pkt = NULL;
|
||||
} else {
|
||||
memcpy(pkt->path, &raw[i], pkt->path_len); i += pkt->path_len;
|
||||
|
||||
pkt->payload_len = len - i; // payload is remainder
|
||||
if (pkt->payload_len > sizeof(pkt->payload)) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): packet payload too big, payload_len=%d", getLogDateTime(), (uint32_t)pkt->payload_len);
|
||||
_mgr->free(pkt); // put back into pool
|
||||
pkt = NULL;
|
||||
} else {
|
||||
memcpy(pkt->payload, &raw[i], pkt->payload_len);
|
||||
|
||||
pkt->_snr = _radio->getLastSNR() * 4.0f;
|
||||
score = _radio->packetScore(_radio->getLastSNR(), len);
|
||||
air_time = _radio->getEstAirtimeFor(len);
|
||||
rx_air_time += air_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -224,9 +272,20 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
|
||||
}
|
||||
|
||||
void Dispatcher::checkSend() {
|
||||
if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return; // nothing waiting to send
|
||||
if (!millisHasNowPassed(next_tx_time)) return; // still in 'radio silence' phase (from airtime budget setting)
|
||||
if (_radio->isReceiving()) { // LBT - check if radio is currently mid-receive, or if channel activity
|
||||
if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return;
|
||||
|
||||
updateTxBudget();
|
||||
|
||||
uint32_t est_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
|
||||
if (tx_budget_ms < est_airtime / MIN_TX_BUDGET_AIRTIME_DIV) {
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
unsigned long needed = est_airtime / MIN_TX_BUDGET_AIRTIME_DIV - tx_budget_ms;
|
||||
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!millisHasNowPassed(next_tx_time)) return;
|
||||
if (_radio->isReceiving()) {
|
||||
if (cad_busy_start == 0) {
|
||||
cad_busy_start = _ms->getMillis(); // record when CAD busy state started
|
||||
}
|
||||
@@ -249,16 +308,13 @@ void Dispatcher::checkSend() {
|
||||
int len = 0;
|
||||
uint8_t raw[MAX_TRANS_UNIT];
|
||||
|
||||
#ifdef NODE_ID
|
||||
raw[len++] = NODE_ID;
|
||||
#endif
|
||||
raw[len++] = outbound->header;
|
||||
if (outbound->hasTransportCodes()) {
|
||||
memcpy(&raw[len], &outbound->transport_codes[0], 2); len += 2;
|
||||
memcpy(&raw[len], &outbound->transport_codes[1], 2); len += 2;
|
||||
}
|
||||
raw[len++] = outbound->path_len;
|
||||
memcpy(&raw[len], outbound->path, outbound->path_len); len += outbound->path_len;
|
||||
len += Packet::writePath(&raw[len], outbound->path, outbound->path_len);
|
||||
|
||||
if (len + outbound->payload_len > MAX_TRANS_UNIT) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): FATAL: Invalid packet queued... too long, len=%d", getLogDateTime(), len + outbound->payload_len);
|
||||
@@ -312,7 +368,7 @@ void Dispatcher::releasePacket(Packet* packet) {
|
||||
}
|
||||
|
||||
void Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
|
||||
if (packet->path_len > MAX_PATH_SIZE || packet->payload_len > MAX_PACKET_PAYLOAD) {
|
||||
if (!Packet::isValidPathLen(packet->path_len) || packet->payload_len > MAX_PACKET_PAYLOAD) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::sendPacket(): ERROR: invalid packet... path_len=%d, payload_len=%d", getLogDateTime(), (uint32_t) packet->path_len, (uint32_t) packet->payload_len);
|
||||
_mgr->free(packet);
|
||||
} else {
|
||||
|
||||
@@ -122,8 +122,12 @@ class Dispatcher {
|
||||
bool prev_isrecv_mode;
|
||||
uint32_t n_sent_flood, n_sent_direct;
|
||||
uint32_t n_recv_flood, n_recv_direct;
|
||||
unsigned long tx_budget_ms;
|
||||
unsigned long last_budget_update;
|
||||
unsigned long duty_cycle_window_ms;
|
||||
|
||||
void processRecvPacket(Packet* pkt);
|
||||
void updateTxBudget();
|
||||
|
||||
protected:
|
||||
PacketManager* _mgr;
|
||||
@@ -142,6 +146,9 @@ protected:
|
||||
_err_flags = 0;
|
||||
radio_nonrx_start = 0;
|
||||
prev_isrecv_mode = true;
|
||||
tx_budget_ms = 0;
|
||||
last_budget_update = 0;
|
||||
duty_cycle_window_ms = 3600000;
|
||||
}
|
||||
|
||||
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
|
||||
@@ -159,6 +166,7 @@ protected:
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
|
||||
virtual int getAGCResetInterval() const { return 0; } // disabled by default
|
||||
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
|
||||
|
||||
public:
|
||||
void begin();
|
||||
@@ -168,8 +176,9 @@ public:
|
||||
void releasePacket(Packet* packet);
|
||||
void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
|
||||
|
||||
unsigned long getTotalAirTime() const { return total_air_time; } // in milliseconds
|
||||
unsigned long getTotalAirTime() const { return total_air_time; }
|
||||
unsigned long getReceiveAirTime() const {return rx_air_time; }
|
||||
unsigned long getRemainingTxBudget() const { return tx_budget_ms; }
|
||||
uint32_t getNumSentFlood() const { return n_sent_flood; }
|
||||
uint32_t getNumSentDirect() const { return n_sent_direct; }
|
||||
uint32_t getNumRecvFlood() const { return n_recv_flood; }
|
||||
@@ -184,6 +193,7 @@ public:
|
||||
unsigned long futureMillis(int millis_from_now) const;
|
||||
|
||||
private:
|
||||
bool tryParsePacket(Packet* pkt, const uint8_t* raw, int len);
|
||||
void checkRecv();
|
||||
void checkSend();
|
||||
};
|
||||
|
||||
@@ -20,6 +20,10 @@ public:
|
||||
memcpy(dest, pub_key, PATH_HASH_SIZE); // hash is just prefix of pub_key
|
||||
return PATH_HASH_SIZE;
|
||||
}
|
||||
int copyHashTo(uint8_t* dest, uint8_t len) const {
|
||||
memcpy(dest, pub_key, len); // hash is just prefix of pub_key
|
||||
return len;
|
||||
}
|
||||
bool isHashMatch(const uint8_t* hash) const {
|
||||
return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0;
|
||||
}
|
||||
|
||||
74
src/Mesh.cpp
74
src/Mesh.cpp
@@ -39,11 +39,6 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int
|
||||
}
|
||||
|
||||
DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
if (pkt->getPayloadVer() > PAYLOAD_VER_1) { // not supported in this firmware version
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unsupported packet version", getLogDateTime());
|
||||
return ACTION_RELEASE;
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
if (pkt->path_len < MAX_PATH_SIZE) {
|
||||
uint8_t i = 0;
|
||||
@@ -70,14 +65,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_CONTROL && (pkt->payload[0] & 0x80) != 0) {
|
||||
if (pkt->path_len == 0) {
|
||||
if (pkt->getPathHashCount() == 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 (pkt->isRouteDirect() && pkt->getPathHashCount() > 0) {
|
||||
// check for 'early received' ACK
|
||||
if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
int i = 0;
|
||||
@@ -88,7 +83,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
}
|
||||
}
|
||||
|
||||
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
|
||||
if (self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) && allowPacketForward(pkt)) {
|
||||
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
|
||||
return forwardMultipartDirect(pkt);
|
||||
} else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
@@ -158,7 +153,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||
int k = 0;
|
||||
uint8_t path_len = data[k++];
|
||||
uint8_t* path = &data[k]; k += path_len;
|
||||
uint8_t hash_size = (path_len >> 6) + 1;
|
||||
uint8_t hash_count = path_len & 63;
|
||||
uint8_t* path = &data[k]; k += hash_size*hash_count;
|
||||
uint8_t extra_type = data[k++] & 0x0F; // upper 4 bits reserved for future use
|
||||
uint8_t* extra = &data[k];
|
||||
uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!)
|
||||
@@ -293,8 +290,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK
|
||||
Packet tmp;
|
||||
tmp.header = pkt->header;
|
||||
tmp.path_len = pkt->path_len;
|
||||
memcpy(tmp.path, pkt->path, pkt->path_len);
|
||||
tmp.path_len = Packet::copyPath(tmp.path, pkt->path, pkt->path_len);
|
||||
tmp.payload_len = pkt->payload_len - 1;
|
||||
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
|
||||
|
||||
@@ -321,27 +317,25 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
|
||||
void Mesh::removeSelfFromPath(Packet* pkt) {
|
||||
// remove our hash from 'path'
|
||||
pkt->path_len -= PATH_HASH_SIZE;
|
||||
#if 0
|
||||
memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len);
|
||||
#elif PATH_HASH_SIZE == 1
|
||||
for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1
|
||||
pkt->path[k] = pkt->path[k + 1];
|
||||
pkt->setPathHashCount(pkt->getPathHashCount() - 1); // decrement the count
|
||||
|
||||
uint8_t sz = pkt->getPathHashSize();
|
||||
for (int k = 0; k < pkt->getPathHashCount()*sz; k += sz) { // shuffle path by 1 'entry'
|
||||
memcpy(&pkt->path[k], &pkt->path[k + sz], sz);
|
||||
}
|
||||
#else
|
||||
#error "need path remove impl"
|
||||
#endif
|
||||
}
|
||||
|
||||
DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
|
||||
uint8_t n = packet->getPathHashCount();
|
||||
if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit()
|
||||
&& packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) {
|
||||
&& (n + 1)*packet->getPathHashSize() <= MAX_PATH_SIZE && allowPacketForward(packet)) {
|
||||
// append this node's hash to 'path'
|
||||
packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]);
|
||||
self_id.copyHashTo(&packet->path[n * packet->getPathHashSize()], packet->getPathHashSize());
|
||||
packet->setPathHashCount(n + 1);
|
||||
|
||||
uint32_t d = getRetransmitDelay(packet);
|
||||
// as this propagates outwards, give it lower and lower priority
|
||||
return ACTION_RETRANSMIT_DELAYED(packet->path_len, d); // give priority to closer sources, than ones further away
|
||||
return ACTION_RETRANSMIT_DELAYED(packet->getPathHashCount(), d); // give priority to closer sources, than ones further away
|
||||
}
|
||||
return ACTION_RELEASE;
|
||||
}
|
||||
@@ -353,8 +347,7 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) {
|
||||
if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK
|
||||
Packet tmp;
|
||||
tmp.header = pkt->header;
|
||||
tmp.path_len = pkt->path_len;
|
||||
memcpy(tmp.path, pkt->path, pkt->path_len);
|
||||
tmp.path_len = Packet::copyPath(tmp.path, pkt->path, pkt->path_len);
|
||||
tmp.payload_len = pkt->payload_len - 1;
|
||||
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
|
||||
|
||||
@@ -376,7 +369,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
delay_millis += getDirectRetransmitDelay(packet) + 300;
|
||||
auto a1 = createMultiAck(crc, extra);
|
||||
if (a1) {
|
||||
memcpy(a1->path, packet->path, a1->path_len = packet->path_len);
|
||||
a1->path_len = Packet::copyPath(a1->path, packet->path, packet->path_len);
|
||||
a1->header &= ~PH_ROUTE_MASK;
|
||||
a1->header |= ROUTE_TYPE_DIRECT;
|
||||
sendPacket(a1, 0, delay_millis);
|
||||
@@ -386,7 +379,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
|
||||
auto a2 = createAck(crc);
|
||||
if (a2) {
|
||||
memcpy(a2->path, packet->path, a2->path_len = packet->path_len);
|
||||
a2->path_len = Packet::copyPath(a2->path, packet->path, packet->path_len);
|
||||
a2->header &= ~PH_ROUTE_MASK;
|
||||
a2->header |= ROUTE_TYPE_DIRECT;
|
||||
sendPacket(a2, 0, delay_millis);
|
||||
@@ -439,7 +432,10 @@ Packet* Mesh::createPathReturn(const Identity& dest, const uint8_t* secret, cons
|
||||
}
|
||||
|
||||
Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len) {
|
||||
if (path_len + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!!
|
||||
uint8_t path_hash_size = (path_len >> 6) + 1;
|
||||
uint8_t path_hash_count = path_len & 63;
|
||||
|
||||
if (path_hash_count*path_hash_size + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!!
|
||||
|
||||
Packet* packet = obtainNewPacket();
|
||||
if (packet == NULL) {
|
||||
@@ -457,7 +453,7 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret,
|
||||
uint8_t data[MAX_PACKET_PAYLOAD];
|
||||
|
||||
data[data_len++] = path_len;
|
||||
memcpy(&data[data_len], path, path_len); data_len += path_len;
|
||||
memcpy(&data[data_len], path, path_hash_count*path_hash_size); data_len += path_hash_count*path_hash_size;
|
||||
if (extra_len > 0) {
|
||||
data[data_len++] = extra_type;
|
||||
memcpy(&data[data_len], extra, extra_len); data_len += extra_len;
|
||||
@@ -624,15 +620,19 @@ Packet* Mesh::createControlData(const uint8_t* data, size_t len) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
|
||||
void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_size) {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
||||
return;
|
||||
}
|
||||
if (path_hash_size == 0 || path_hash_size > 3) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): invalid path_hash_size", getLogDateTime());
|
||||
return;
|
||||
}
|
||||
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_FLOOD;
|
||||
packet->path_len = 0;
|
||||
packet->setPathHashSizeAndCount(path_hash_size, 0);
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
@@ -647,17 +647,21 @@ 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) {
|
||||
void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis, uint8_t path_hash_size) {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
||||
return;
|
||||
}
|
||||
if (path_hash_size == 0 || path_hash_size > 3) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): invalid path_hash_size", 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;
|
||||
packet->setPathHashSizeAndCount(path_hash_size, 0);
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
@@ -679,13 +683,13 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin
|
||||
uint8_t pri;
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { // TRACE packets are different
|
||||
// for TRACE packets, path is appended to end of PAYLOAD. (path is used for SNR's)
|
||||
memcpy(&packet->payload[packet->payload_len], path, path_len);
|
||||
memcpy(&packet->payload[packet->payload_len], path, path_len); // NOTE: path_len here can be > 64, and NOT in the new scheme
|
||||
packet->payload_len += path_len;
|
||||
|
||||
packet->path_len = 0;
|
||||
pri = 5; // maybe make this configurable
|
||||
} else {
|
||||
memcpy(packet->path, path, packet->path_len = path_len);
|
||||
packet->path_len = Packet::copyPath(packet->path, path, path_len);
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||
pri = 1; // slightly less priority
|
||||
} else {
|
||||
|
||||
@@ -196,13 +196,13 @@ public:
|
||||
/**
|
||||
* \brief send a locally-generated Packet with flood routing
|
||||
*/
|
||||
void sendFlood(Packet* packet, uint32_t delay_millis=0);
|
||||
void sendFlood(Packet* packet, uint32_t delay_millis=0, uint8_t path_hash_size=1);
|
||||
|
||||
/**
|
||||
* \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);
|
||||
void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0, uint8_t path_hash_size=1);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet with Direct routing
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
virtual uint32_t getGpio() { return 0; }
|
||||
virtual void setGpio(uint32_t values) {}
|
||||
virtual uint8_t getStartupReason() const = 0;
|
||||
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
|
||||
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
|
||||
|
||||
// Power management interface (boards with power management override these)
|
||||
|
||||
@@ -10,8 +10,32 @@ Packet::Packet() {
|
||||
payload_len = 0;
|
||||
}
|
||||
|
||||
bool Packet::isValidPathLen(uint8_t path_len) {
|
||||
uint8_t hash_count = path_len & 63;
|
||||
uint8_t hash_size = (path_len >> 6) + 1;
|
||||
if (hash_size == 4) return false; // Reserved for future
|
||||
return hash_count*hash_size <= MAX_PATH_SIZE;
|
||||
}
|
||||
|
||||
size_t Packet::writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len) {
|
||||
uint8_t hash_count = path_len & 63;
|
||||
uint8_t hash_size = (path_len >> 6) + 1;
|
||||
size_t len = hash_count*hash_size;
|
||||
if (len > MAX_PATH_SIZE) {
|
||||
MESH_DEBUG_PRINTLN("Packet::copyPath, invalid path_len=%d", (uint32_t)path_len);
|
||||
return 0; // Error
|
||||
}
|
||||
memcpy(dest, src, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) {
|
||||
writePath(dest, src, path_len);
|
||||
return path_len;
|
||||
}
|
||||
|
||||
int Packet::getRawLength() const {
|
||||
return 2 + path_len + payload_len + (hasTransportCodes() ? 4 : 0);
|
||||
return 2 + getPathByteLen() + payload_len + (hasTransportCodes() ? 4 : 0);
|
||||
}
|
||||
|
||||
void Packet::calculatePacketHash(uint8_t* hash) const {
|
||||
@@ -33,7 +57,7 @@ uint8_t Packet::writeTo(uint8_t dest[]) const {
|
||||
memcpy(&dest[i], &transport_codes[1], 2); i += 2;
|
||||
}
|
||||
dest[i++] = path_len;
|
||||
memcpy(&dest[i], path, path_len); i += path_len;
|
||||
i += writePath(&dest[i], path, path_len);
|
||||
memcpy(&dest[i], payload, payload_len); i += payload_len;
|
||||
return i;
|
||||
}
|
||||
@@ -48,8 +72,11 @@ bool Packet::readFrom(const uint8_t src[], uint8_t len) {
|
||||
transport_codes[0] = transport_codes[1] = 0;
|
||||
}
|
||||
path_len = src[i++];
|
||||
if (path_len > sizeof(path)) return false; // bad encoding
|
||||
memcpy(path, &src[i], path_len); i += path_len;
|
||||
if (!isValidPathLen(path_len)) return false; // bad encoding
|
||||
|
||||
uint8_t bl = getPathByteLen();
|
||||
memcpy(path, &src[i], bl); i += bl;
|
||||
|
||||
if (i >= len) return false; // bad encoding
|
||||
payload_len = len - i;
|
||||
if (payload_len > sizeof(payload)) return false; // bad encoding
|
||||
|
||||
10
src/Packet.h
10
src/Packet.h
@@ -76,6 +76,16 @@ public:
|
||||
*/
|
||||
uint8_t getPayloadVer() const { return (header >> PH_VER_SHIFT) & PH_VER_MASK; }
|
||||
|
||||
uint8_t getPathHashSize() const { return (path_len >> 6) + 1; }
|
||||
uint8_t getPathHashCount() const { return path_len & 63; }
|
||||
uint8_t getPathByteLen() const { return getPathHashCount() * getPathHashSize(); }
|
||||
void setPathHashCount(uint8_t n) { path_len &= ~63; path_len |= n; }
|
||||
void setPathHashSizeAndCount(uint8_t sz, uint8_t n) { path_len = ((sz - 1) << 6) | (n & 63); }
|
||||
|
||||
static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len); // returns path_len
|
||||
static size_t writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len); // returns byte length written
|
||||
static bool isValidPathLen(uint8_t path_len);
|
||||
|
||||
void markDoNotRetransmit() { header = 0xFF; }
|
||||
bool isMarkedDoNotRetransmit() const { return header == 0xFF; }
|
||||
|
||||
|
||||
@@ -39,7 +39,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) {
|
||||
if (dest.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
mesh::Packet* ack = createAck(ack_hash);
|
||||
if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY);
|
||||
} else {
|
||||
@@ -92,7 +92,7 @@ ContactInfo* BaseChatMesh::allocateContactSlot() {
|
||||
void BaseChatMesh::populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp) {
|
||||
memset(&ci, 0, sizeof(ci));
|
||||
ci.id = id;
|
||||
ci.out_path_len = -1; // initially out_path is unknown
|
||||
ci.out_path_len = OUT_PATH_UNKNOWN;
|
||||
StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name));
|
||||
ci.type = parser.getType();
|
||||
if (parser.hasLatLon()) {
|
||||
@@ -263,7 +263,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
} 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
|
||||
if (from.out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY);
|
||||
@@ -273,7 +273,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
}
|
||||
} else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) {
|
||||
onContactResponse(from, data, len);
|
||||
if (packet->isRouteFlood() && from.out_path_len >= 0) {
|
||||
if (packet->isRouteFlood() && from.out_path_len != OUT_PATH_UNKNOWN) {
|
||||
// we have direct path, but other node is still sending flood response, so maybe they didn't receive reciprocal path properly(?)
|
||||
handleReturnPathRetry(from, packet->path, packet->path_len);
|
||||
}
|
||||
@@ -295,7 +295,7 @@ bool BaseChatMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const ui
|
||||
bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
|
||||
// NOTE: default impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path.
|
||||
// FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?)
|
||||
memcpy(from.out_path, out_path, from.out_path_len = out_path_len); // store a copy of path, for sendDirect()
|
||||
from.out_path_len = mesh::Packet::copyPath(from.out_path, out_path, out_path_len); // store a copy of path, for sendDirect()
|
||||
from.lastmod = getRTCClock()->getCurrentTime();
|
||||
|
||||
onContactPathUpdated(from);
|
||||
@@ -317,7 +317,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer
|
||||
packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit
|
||||
|
||||
if (packet->isRouteFlood() && from->out_path_len >= 0) {
|
||||
if (packet->isRouteFlood() && from->out_path_len != OUT_PATH_UNKNOWN) {
|
||||
// we have direct path, but other node is still sending flood, so maybe they didn't receive reciprocal path properly(?)
|
||||
handleReturnPathRetry(*from, packet->path, packet->path_len);
|
||||
}
|
||||
@@ -386,7 +386,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp,
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
|
||||
int rc;
|
||||
if (recipient.out_path_len < 0) {
|
||||
if (recipient.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFloodScoped(recipient, pkt);
|
||||
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
||||
rc = MSG_SEND_SENT_FLOOD;
|
||||
@@ -412,7 +412,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) {
|
||||
if (recipient.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFloodScoped(recipient, pkt);
|
||||
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
||||
rc = MSG_SEND_SENT_FLOOD;
|
||||
@@ -500,7 +500,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) {
|
||||
if (recipient.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
@@ -525,7 +525,7 @@ int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data,
|
||||
}
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
if (recipient.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
@@ -552,7 +552,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) {
|
||||
if (recipient.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
@@ -579,7 +579,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) {
|
||||
if (recipient.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
@@ -683,7 +683,7 @@ void BaseChatMesh::checkConnections() {
|
||||
MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact not found!");
|
||||
continue;
|
||||
}
|
||||
if (contact->out_path_len < 0) {
|
||||
if (contact->out_path_len == OUT_PATH_UNKNOWN) {
|
||||
MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact, no out_path!");
|
||||
continue;
|
||||
}
|
||||
@@ -710,7 +710,7 @@ void BaseChatMesh::checkConnections() {
|
||||
}
|
||||
|
||||
void BaseChatMesh::resetPathTo(ContactInfo& recipient) {
|
||||
recipient.out_path_len = -1;
|
||||
recipient.out_path_len = OUT_PATH_UNKNOWN;
|
||||
}
|
||||
|
||||
static ContactInfo* table; // pass via global :-(
|
||||
|
||||
@@ -114,7 +114,7 @@ ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) {
|
||||
memset(c, 0, sizeof(*c));
|
||||
c->permissions = init_perms;
|
||||
c->id = id;
|
||||
c->out_path_len = -1; // initially out_path is unknown
|
||||
c->out_path_len = OUT_PATH_UNKNOWN;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
#define PERM_ACL_READ_WRITE 2
|
||||
#define PERM_ACL_ADMIN 3
|
||||
|
||||
#define OUT_PATH_UNKNOWN 0xFF
|
||||
|
||||
struct ClientInfo {
|
||||
mesh::Identity id;
|
||||
uint8_t permissions;
|
||||
int8_t out_path_len;
|
||||
uint8_t out_path_len;
|
||||
uint8_t out_path[MAX_PATH_SIZE];
|
||||
uint8_t shared_secret[PUB_KEY_SIZE];
|
||||
uint32_t last_timestamp; // by THEIR clock (transient)
|
||||
|
||||
@@ -63,7 +63,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
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->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
|
||||
file.read(pad, 2); // 122
|
||||
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
|
||||
@@ -81,7 +82,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
// 290
|
||||
file.read((uint8_t *)&_prefs->flood_advert_base, sizeof(_prefs->flood_advert_base)); // 290
|
||||
|
||||
// 294
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -95,6 +98,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, -9, 30);
|
||||
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
|
||||
_prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f);
|
||||
_prefs->path_hash_mode = constrain(_prefs->path_hash_mode, 0, 2); // NOTE: mode 3 reserved for future
|
||||
|
||||
// sanitise bad bridge pref values
|
||||
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
|
||||
@@ -108,6 +112,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
|
||||
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
||||
|
||||
_prefs->flood_advert_base = constrain(_prefs->flood_advert_base, 0, 1);
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
@@ -147,7 +153,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
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->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
|
||||
file.write(pad, 2); // 122
|
||||
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
|
||||
@@ -165,7 +172,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
// 290
|
||||
file.write((uint8_t *)&_prefs->flood_advert_base, sizeof(_prefs->flood_advert_base)); // 290
|
||||
|
||||
// 294
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -325,6 +334,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
sp++;
|
||||
}
|
||||
*reply = 0; // set null terminator
|
||||
} else if (memcmp(config, "path.hash.mode", 14) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode);
|
||||
} else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
|
||||
sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm);
|
||||
} else if (memcmp(config, "freq", 4) == 0) {
|
||||
@@ -362,6 +373,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (memcmp(config, "bridge.secret", 13) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_secret);
|
||||
#endif
|
||||
} else if (memcmp(config, "bootloader.ver", 14) == 0) {
|
||||
#ifdef NRF52_PLATFORM
|
||||
char ver[32];
|
||||
if (_board->getBootloaderVersion(ver, sizeof(ver))) {
|
||||
sprintf(reply, "> %s", ver);
|
||||
} else {
|
||||
strcpy(reply, "> unknown");
|
||||
}
|
||||
#else
|
||||
strcpy(reply, "ERROR: unsupported");
|
||||
#endif
|
||||
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
|
||||
float adc_mult = _board->getAdcMultiplier();
|
||||
if (adc_mult == 0.0f) {
|
||||
@@ -369,6 +391,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else {
|
||||
sprintf(reply, "> %.3f", adc_mult);
|
||||
}
|
||||
} else if (memcmp(config, "flood.advert.base", 17) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->flood_advert_base));
|
||||
// Power management commands
|
||||
} else if (memcmp(config, "pwrmgt.support", 14) == 0) {
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
@@ -545,6 +569,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
*dp = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "path.hash.mode ", 15) == 0) {
|
||||
config += 15;
|
||||
uint8_t mode = atoi(config);
|
||||
if (mode < 3) {
|
||||
_prefs->path_hash_mode = mode;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be 0,1, or 2");
|
||||
}
|
||||
} else if (memcmp(config, "tx ", 3) == 0) {
|
||||
_prefs->tx_power_dbm = atoi(&config[3]);
|
||||
savePrefs();
|
||||
@@ -616,6 +650,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
_prefs->adc_multiplier = 0.0f;
|
||||
strcpy(reply, "Error: unsupported by this board");
|
||||
};
|
||||
} else if (memcmp(config, "flood.advert.base ", 18) == 0) {
|
||||
float f = atof(&config[18]);
|
||||
if((f > 0) || (f<1)) {
|
||||
_prefs->flood_advert_base = f;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: base must be between 0 and 1");
|
||||
}
|
||||
} else {
|
||||
sprintf(reply, "unknown config: %s", config);
|
||||
}
|
||||
@@ -691,6 +734,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
LocationProvider * l = _sensors->getLocationProvider();
|
||||
if (l != NULL) {
|
||||
l->syncTime();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "gps provider not found");
|
||||
}
|
||||
} else if (memcmp(command, "gps setloc", 10) == 0) {
|
||||
_prefs->node_lat = _sensors->node_lat;
|
||||
|
||||
@@ -36,6 +36,7 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t flood_max;
|
||||
uint8_t interference_threshold;
|
||||
uint8_t agc_reset_interval; // secs / 4
|
||||
float flood_advert_base;
|
||||
// Bridge settings
|
||||
uint8_t bridge_enabled; // boolean
|
||||
uint16_t bridge_delay; // milliseconds (default 500 ms)
|
||||
@@ -52,6 +53,7 @@ struct NodePrefs { // persisted to file
|
||||
uint32_t discovery_mod_timestamp;
|
||||
float adc_multiplier;
|
||||
char owner_info[120];
|
||||
uint8_t path_hash_mode; // which path mode to use when sending
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
#include <Arduino.h>
|
||||
#include <Mesh.h>
|
||||
|
||||
#define OUT_PATH_UNKNOWN 0xFF
|
||||
|
||||
struct ContactInfo {
|
||||
mesh::Identity id;
|
||||
char name[32];
|
||||
uint8_t type; // on of ADV_TYPE_*
|
||||
uint8_t flags;
|
||||
int8_t out_path_len;
|
||||
uint8_t out_path_len;
|
||||
mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated
|
||||
uint8_t out_path[MAX_PATH_SIZE];
|
||||
uint32_t last_advert_timestamp; // by THEIR clock
|
||||
|
||||
@@ -297,6 +297,25 @@ float NRF52Board::getMCUTemperature() {
|
||||
return temp * 0.25f; // Convert to *C
|
||||
}
|
||||
|
||||
bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) {
|
||||
static const char BOOTLOADER_MARKER[] = "UF2 Bootloader ";
|
||||
const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B
|
||||
|
||||
for (uint32_t i = 0; i < 0x3000 - (sizeof(BOOTLOADER_MARKER) - 1); i++) {
|
||||
if (memcmp(&flash[i], BOOTLOADER_MARKER, sizeof(BOOTLOADER_MARKER) - 1) == 0) {
|
||||
const char* ver = (const char*)&flash[i + sizeof(BOOTLOADER_MARKER) - 1];
|
||||
size_t len = 0;
|
||||
while (len < max_len - 1 && ver[len] != '\0' && ver[len] != ' ' && ver[len] != '\n' && ver[len] != '\r') {
|
||||
out[len] = ver[len];
|
||||
len++;
|
||||
}
|
||||
out[len] = '\0';
|
||||
return len > 0; // bootloader string is non-empty
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NRF52Board::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
|
||||
@@ -50,6 +50,7 @@ public:
|
||||
virtual uint8_t getStartupReason() const override { return startup_reason; }
|
||||
virtual float getMCUTemperature() override;
|
||||
virtual void reboot() override { NVIC_SystemReset(); }
|
||||
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
|
||||
virtual bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
virtual void sleep(uint32_t secs) override;
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@ public:
|
||||
digitalWrite(_pin, _active);
|
||||
}
|
||||
}
|
||||
|
||||
void release() {
|
||||
if (_claims == 0) return; // avoid negative _claims
|
||||
|
||||
_claims--;
|
||||
if (_claims == 0) {
|
||||
digitalWrite(_pin, !_active);
|
||||
|
||||
@@ -11,7 +11,7 @@ PacketQueue::PacketQueue(int max_entries) {
|
||||
int PacketQueue::countBefore(uint32_t now) const {
|
||||
int n = 0;
|
||||
for (int j = 0; j < _num; j++) {
|
||||
if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now
|
||||
if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
@@ -21,7 +21,7 @@ mesh::Packet* PacketQueue::get(uint32_t now) {
|
||||
uint8_t min_pri = 0xFF;
|
||||
int best_idx = -1;
|
||||
for (int j = 0; j < _num; j++) {
|
||||
if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now
|
||||
if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now
|
||||
if (_pri_table[j] < min_pri) { // select most important priority amongst non-future entries
|
||||
min_pri = _pri_table[j];
|
||||
best_idx = j;
|
||||
@@ -55,15 +55,15 @@ mesh::Packet* PacketQueue::removeByIdx(int i) {
|
||||
return item;
|
||||
}
|
||||
|
||||
void PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
|
||||
bool PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
|
||||
if (_num == _size) {
|
||||
// TODO: log "FATAL: queue is full!"
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
_table[_num] = packet;
|
||||
_pri_table[_num] = priority;
|
||||
_schedule_table[_num] = scheduled_for;
|
||||
_num++;
|
||||
return true;
|
||||
}
|
||||
|
||||
StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): unused(pool_size), send_queue(pool_size), rx_queue(pool_size) {
|
||||
@@ -82,7 +82,10 @@ void StaticPoolPacketManager::free(mesh::Packet* packet) {
|
||||
}
|
||||
|
||||
void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
|
||||
send_queue.add(packet, priority, scheduled_for);
|
||||
if (!send_queue.add(packet, priority, scheduled_for)) {
|
||||
MESH_DEBUG_PRINTLN("queueOutbound: send queue full, dropping packet");
|
||||
free(packet);
|
||||
}
|
||||
}
|
||||
|
||||
mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) {
|
||||
@@ -106,7 +109,10 @@ mesh::Packet* StaticPoolPacketManager::removeOutboundByIdx(int i) {
|
||||
}
|
||||
|
||||
void StaticPoolPacketManager::queueInbound(mesh::Packet* packet, uint32_t scheduled_for) {
|
||||
rx_queue.add(packet, 0, scheduled_for);
|
||||
if (!rx_queue.add(packet, 0, scheduled_for)) {
|
||||
MESH_DEBUG_PRINTLN("queueInbound: rx queue full, dropping packet");
|
||||
free(packet);
|
||||
}
|
||||
}
|
||||
mesh::Packet* StaticPoolPacketManager::getNextInbound(uint32_t now) {
|
||||
return rx_queue.get(now);
|
||||
|
||||
@@ -11,7 +11,7 @@ class PacketQueue {
|
||||
public:
|
||||
PacketQueue(int max_entries);
|
||||
mesh::Packet* get(uint32_t now);
|
||||
void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
|
||||
bool add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
|
||||
int count() const { return _num; }
|
||||
int countBefore(uint32_t now) const;
|
||||
mesh::Packet* itemAt(int i) const { return _table[i]; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "SerialBLEInterface.h"
|
||||
#include "esp_mac.h"
|
||||
|
||||
// See the following for generating UUIDs:
|
||||
// https://www.uuidgenerator.net/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CustomLLCC68.h"
|
||||
#include "RadioLibWrappers.h"
|
||||
#include "SX126xReset.h"
|
||||
|
||||
class CustomLLCC68Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
@@ -19,4 +20,6 @@ public:
|
||||
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
};
|
||||
|
||||
@@ -20,6 +20,8 @@ class CustomLR1110 : public LR1110 {
|
||||
return len;
|
||||
}
|
||||
|
||||
float getFreqMHz() const { return freqMHz; }
|
||||
|
||||
bool isReceiving() {
|
||||
uint16_t irq = getIrqStatus();
|
||||
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
#include "CustomLR1110.h"
|
||||
#include "RadioLibWrappers.h"
|
||||
#include "LR11x0Reset.h"
|
||||
|
||||
class CustomLR1110Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool isReceivingPacket() override {
|
||||
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomLR1110 *)_radio)->isReceiving();
|
||||
}
|
||||
float getCurrentRSSI() override {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CustomSTM32WLx.h"
|
||||
#include "RadioLibWrappers.h"
|
||||
#include "SX126xReset.h"
|
||||
#include <math.h>
|
||||
|
||||
class CustomSTM32WLxWrapper : public RadioLibWrapper {
|
||||
@@ -20,4 +21,6 @@ public:
|
||||
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CustomSX1262.h"
|
||||
#include "RadioLibWrappers.h"
|
||||
#include "SX126xReset.h"
|
||||
|
||||
class CustomSX1262Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
@@ -22,4 +23,6 @@ public:
|
||||
virtual void powerOff() override {
|
||||
((CustomSX1262 *)_radio)->sleep(false);
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CustomSX1268.h"
|
||||
#include "RadioLibWrappers.h"
|
||||
#include "SX126xReset.h"
|
||||
|
||||
class CustomSX1268Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
@@ -19,4 +20,6 @@ public:
|
||||
int sf = ((CustomSX1268 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
};
|
||||
|
||||
21
src/helpers/radiolib/LR11x0Reset.h
Normal file
21
src/helpers/radiolib/LR11x0Reset.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
// Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121).
|
||||
// Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks,
|
||||
// then re-applies RX settings that calibration may reset.
|
||||
inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) {
|
||||
radio->sleep(true, 0);
|
||||
radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true);
|
||||
|
||||
radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL);
|
||||
|
||||
// calibrate(0x3F) defaults image calibration to 902-928MHz band.
|
||||
// Re-calibrate for the actual operating frequency (band=4MHz matches RadioLib default).
|
||||
radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f);
|
||||
|
||||
#ifdef RX_BOOSTED_GAIN
|
||||
radio->setRxBoostedGainMode(RX_BOOSTED_GAIN);
|
||||
#endif
|
||||
}
|
||||
@@ -53,13 +53,24 @@ void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) {
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibWrapper::doResetAGC() {
|
||||
_radio->sleep(); // warm sleep to reset analog frontend
|
||||
}
|
||||
|
||||
void RadioLibWrapper::resetAGC() {
|
||||
// make sure we're not mid-receive of packet!
|
||||
if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return;
|
||||
|
||||
// NOTE: according to higher powers, just issuing RadioLib's startReceive() will reset the AGC.
|
||||
// revisit this if a better impl is discovered.
|
||||
doResetAGC();
|
||||
state = STATE_IDLE; // trigger a startReceive()
|
||||
|
||||
// Reset noise floor sampling so it reconverges from scratch.
|
||||
// Without this, a stuck _noise_floor of -120 makes the sampling threshold
|
||||
// too low (-106) to accept normal samples (~-105), self-reinforcing the
|
||||
// stuck value even after the receiver has recovered.
|
||||
_noise_floor = 0;
|
||||
_num_floor_samples = 0;
|
||||
_floor_sample_sum = 0;
|
||||
}
|
||||
|
||||
void RadioLibWrapper::loop() {
|
||||
|
||||
@@ -16,6 +16,7 @@ protected:
|
||||
void startRecv();
|
||||
float packetScoreInt(float snr, int sf, int packet_len);
|
||||
virtual bool isReceivingPacket() =0;
|
||||
virtual void doResetAGC();
|
||||
|
||||
public:
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; }
|
||||
|
||||
37
src/helpers/radiolib/SX126xReset.h
Normal file
37
src/helpers/radiolib/SX126xReset.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
// Full receiver reset for all SX126x-family chips (SX1262, SX1268, LLCC68, STM32WLx).
|
||||
// Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration,
|
||||
// then re-applies RX settings that calibration may reset.
|
||||
inline void sx126xResetAGC(SX126x* radio) {
|
||||
radio->sleep(true);
|
||||
radio->standby(RADIOLIB_SX126X_STANDBY_RC, true);
|
||||
|
||||
uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL;
|
||||
radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false);
|
||||
radio->mod->hal->delay(5);
|
||||
uint32_t start = millis();
|
||||
while (radio->mod->hal->digitalRead(radio->mod->getGpio())) {
|
||||
if (millis() - start > 50) break;
|
||||
radio->mod->hal->yield();
|
||||
}
|
||||
|
||||
// Calibrate(0x7F) defaults image calibration to 902-928MHz band.
|
||||
// Re-calibrate for the actual operating frequency.
|
||||
radio->calibrateImage(radio->freqMHz);
|
||||
|
||||
#ifdef SX126X_DIO2_AS_RF_SWITCH
|
||||
radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
|
||||
#endif
|
||||
#ifdef SX126X_RX_BOOSTED_GAIN
|
||||
radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
|
||||
#endif
|
||||
#ifdef SX126X_REGISTER_PATCH
|
||||
uint8_t r_data = 0;
|
||||
radio->readRegister(0x8B5, &r_data, 1);
|
||||
r_data |= 0x01;
|
||||
radio->writeRegister(0x8B5, &r_data, 1);
|
||||
#endif
|
||||
}
|
||||
@@ -43,6 +43,31 @@ lib_deps =
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0
|
||||
|
||||
[env:Heltec_Wireless_Tracker_companion_radio_usb]
|
||||
extends = Heltec_tracker_base
|
||||
build_flags =
|
||||
${Heltec_tracker_base.build_flags}
|
||||
-I src/helpers/ui
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-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
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_base.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_Wireless_Tracker_companion_radio_ble]
|
||||
extends = Heltec_tracker_base
|
||||
build_flags =
|
||||
|
||||
@@ -26,6 +26,7 @@ build_flags =
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D SX126X_REGISTER_PATCH=1
|
||||
-D PIN_BOARD_SDA=5
|
||||
-D PIN_BOARD_SCL=6
|
||||
-D PIN_USER_BTN=0
|
||||
|
||||
@@ -22,7 +22,7 @@ build_flags =
|
||||
-D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low)
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_VEXT_EN=36
|
||||
-D PIN_VEXT_EN_ACTIVE=LOW
|
||||
-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_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX
|
||||
@@ -54,8 +54,6 @@ build_flags =
|
||||
-D PIN_BOARD_SDA=17
|
||||
-D PIN_BOARD_SCL=18
|
||||
-D PIN_OLED_RESET=21
|
||||
-D ENV_PIN_SDA=4
|
||||
-D ENV_PIN_SCL=3
|
||||
build_src_filter= ${Heltec_lora32_v4.build_src_filter}
|
||||
lib_deps = ${Heltec_lora32_v4.lib_deps}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display(&(board.periph_power));
|
||||
DISPLAY_CLASS display(NULL);
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<helpers/ui/SH1106Display.cpp>
|
||||
+<helpers/esp32/TBeamBoard.cpp>
|
||||
+<helpers/sensors>
|
||||
board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
|
||||
board_build.partitions = default_8MB.csv
|
||||
board_upload.flash_size = 8MB
|
||||
board_upload.maximum_size = 8388608
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
lewisxhe/XPowersLib @ ^0.2.7
|
||||
@@ -131,3 +133,27 @@ build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter}
|
||||
lib_deps =
|
||||
${T_Beam_S3_Supreme_SX1262.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:T_Beam_S3_Supreme_SX1262_companion_radio_wifi]
|
||||
extends = T_Beam_S3_Supreme_SX1262
|
||||
build_flags =
|
||||
${T_Beam_S3_Supreme_SX1262.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D WIFI_SSID='"WIFI_SSID"'
|
||||
-D WIFI_PWD='"Password"'
|
||||
; -D WIFI_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=8
|
||||
; -D MESH_DEBUG=1
|
||||
; -D ARDUHAL_LOG_LEVEL=4
|
||||
; -D CORE_DEBUG_LEVEL=4
|
||||
build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${T_Beam_S3_Supreme_SX1262.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
@@ -18,7 +18,7 @@ build_flags =
|
||||
-D P_LORA_SCLK=5 ; SPI clock
|
||||
-D P_LORA_MISO=19 ; SPI MISO
|
||||
-D P_LORA_MOSI=27 ; SPI MOSI
|
||||
-D P_LORA_TX_LED=2 ; LED pin for TX indication
|
||||
-D P_LORA_TX_LED=25 ; LED pin for TX indication
|
||||
-D PIN_BOARD_SDA=21
|
||||
-D PIN_BOARD_SCL=22
|
||||
-D PIN_VBAT_READ=35 ; Battery voltage reading (analog pin)
|
||||
@@ -65,7 +65,7 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
+<../examples/simple_secure_chat/main.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
@@ -191,4 +191,4 @@ build_flags =
|
||||
; -D CORE_DEBUG_LEVEL=3
|
||||
lib_deps =
|
||||
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
@@ -5,7 +5,7 @@ board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
|
||||
build_flags =
|
||||
${esp32c6_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/M5Stack_Unit_C6L
|
||||
-I variants/m5stack_unit_c6l
|
||||
-D P_LORA_TX_LED=15
|
||||
-D P_LORA_SCLK=20
|
||||
-D P_LORA_MISO=22
|
||||
@@ -94,6 +94,8 @@ build_flags = ${M5Stack_Unit_C6L.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D ARDUINO_USB_MODE=1
|
||||
build_src_filter = ${M5Stack_Unit_C6L.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
-<helpers/esp32/ESPNOWRadio.cpp>
|
||||
|
||||
@@ -7,6 +7,7 @@ build_flags =
|
||||
-I variants/rak3112
|
||||
-D RAK_3112=1
|
||||
-D ESP32_CPU_FREQ=80
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D P_LORA_DIO_1=47
|
||||
-D P_LORA_NSS=7
|
||||
-D P_LORA_RESET=8
|
||||
@@ -131,14 +132,14 @@ lib_deps =
|
||||
extends = rak3112
|
||||
build_flags =
|
||||
${rak3112.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-I examples/companion_radio/ui-orig
|
||||
-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 = ${rak3112.build_src_filter}
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
+<../examples/companion_radio/ui-orig/*.cpp>
|
||||
lib_deps =
|
||||
${rak3112.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
@@ -147,7 +148,7 @@ lib_deps =
|
||||
extends = rak3112
|
||||
build_flags =
|
||||
${rak3112.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-I examples/companion_radio/ui-orig
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
@@ -159,7 +160,7 @@ build_flags =
|
||||
build_src_filter = ${rak3112.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
+<../examples/companion_radio/ui-orig/*.cpp>
|
||||
lib_deps =
|
||||
${rak3112.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
@@ -168,7 +169,7 @@ lib_deps =
|
||||
extends = rak3112
|
||||
build_flags =
|
||||
${rak3112.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-I examples/companion_radio/ui-orig
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
@@ -180,7 +181,7 @@ build_flags =
|
||||
build_src_filter = ${rak3112.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
+<../examples/companion_radio/ui-orig/*.cpp>
|
||||
lib_deps =
|
||||
${rak3112.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
@@ -106,7 +106,7 @@ extern "C"
|
||||
|
||||
// Power management boot protection threshold (millivolts)
|
||||
// Set to 0 to disable boot protection
|
||||
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV)
|
||||
#define PWRMGT_VOLTAGE_BOOTLOCK 0 // Won't boot below this voltage (mV)
|
||||
// LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
|
||||
// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ
|
||||
#define PWRMGT_LPCOMP_AIN 3
|
||||
|
||||
@@ -96,6 +96,8 @@ build_flags = ${WioTrackerL1.build_flags}
|
||||
-D PIN_BUZZER=12
|
||||
-D QSPIFLASH=1
|
||||
-D ADVERT_NAME='"@@MAC"'
|
||||
-D ENV_PIN_SDA=PIN_WIRE1_SDA
|
||||
-D ENV_PIN_SCL=PIN_WIRE1_SCL
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${WioTrackerL1.build_src_filter}
|
||||
|
||||
Reference in New Issue
Block a user