diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml new file mode 100644 index 00000000..5ba677cd --- /dev/null +++ b/.github/workflows/pr-build-check.yml @@ -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 }} diff --git a/boards/t_beams3_supreme.json b/boards/t_beams3_supreme.json index 6a725247..3eb9c016 100644 --- a/boards/t_beams3_supreme.json +++ b/boards/t_beams3_supreme.json @@ -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 diff --git a/build.sh b/build.sh index b7f95dd7..313c4c47 100755 --- a/build.sh +++ b/build.sh @@ -7,6 +7,7 @@ sh build.sh [target] Commands: help|usage|-h|--help: Shows this message. + list|-l: List firmwares available to build. build-firmware : Build the firmware for the given build target. build-firmwares: Build all firmwares for all targets. build-matching-firmwares : Build all firmwares for build targets containing the string given for . @@ -46,19 +47,25 @@ $ sh build.sh build-firmware RAK_4631_repeater EOF } +# get a list of pio env names that start with "env:" +get_pio_envs() { + pio project config | grep 'env:' | sed 's/env://' +} + # Catch cries for help before doing anything else. case $1 in help|usage|-h|--help) global_usage exit 1 ;; + list|-l) + get_pio_envs + exit 0 + ;; esac - -# get a list of pio env names that start with "env:" -get_pio_envs() { - echo $(pio project config | grep 'env:' | sed 's/env://') -} +# 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() { @@ -82,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 @@ -91,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) @@ -121,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 } diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 6b4f6157..c316bd6c 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -642,7 +642,7 @@ **Usage:** - `region` -**Serial Only:** Yes +**Serial Only:** For firmware older than 1.12.0 --- diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 72c5df8d..a4e8a619 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -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 @@ -248,7 +249,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 @@ -258,7 +259,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 diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3e4ea17f..e2f877c2 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -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() {} @@ -893,6 +907,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(); @@ -916,6 +948,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 @@ -1093,7 +1127,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); } @@ -1105,7 +1140,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(); @@ -1220,13 +1255,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); @@ -1283,6 +1325,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(); @@ -1420,7 +1470,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) { @@ -1580,7 +1630,7 @@ void MyMesh::handleCmdFrame(size_t len) { sendDirect(pkt, &cmd_frame[10], path_len); uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); - uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); + uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len >> path_sz); out_frame[0] = RESP_CODE_SENT; out_frame[1] = 0; @@ -1657,11 +1707,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); } @@ -1753,6 +1804,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]); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 95265a19..87e6cf33 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -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); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index a8a8a83c..49e758a5 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -29,4 +29,6 @@ struct NodePrefs { // persisted to file uint32_t gps_interval; // GPS read interval in seconds uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t sx126x_rx_boosted_gain; // SX126x RX boosted gain mode (0=power saving, 1=boosted) + uint8_t client_repeat; + uint8_t path_hash_mode; // which path mode to use when sending }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ae2d9375..265532be 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -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; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 02c3cafb..a77ad6e7 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -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(); diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index 5220f409..cbe23790 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -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 }; \ No newline at end of file diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index efdc63ea..9787f3ae 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -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,7 +398,7 @@ 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; @@ -480,11 +493,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); } @@ -534,13 +547,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 +623,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 +661,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 +689,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 +711,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 +753,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); } } @@ -771,7 +827,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f - _prefs.direct_tx_delay_factor = 0.2f; // was zero + _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; @@ -809,6 +865,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.sx126x_rx_boosted_gain = 1; // enabled by default; #endif #endif + + pending_discover_tag = 0; + pending_discover_until = 0; } void MyMesh::begin(FILESYSTEM *fs) { @@ -872,7 +931,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); } @@ -1182,6 +1241,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 } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 4988bdfd..27b1e190 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -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); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 598b14de..5451505a 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -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,17 @@ 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; return true; } @@ -333,7 +335,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 +355,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 +450,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 +484,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 +523,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 +540,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 +565,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); @@ -679,7 +681,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); } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index b4529e77..d21e225f 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -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 diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index a389ec74..c1ed710a 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -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 { diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index f05fb245..68fea474 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -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?? @@ -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) diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 4bc0d784..b15a400a 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -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; diff --git a/platformio.ini b/platformio.ini index 69883271..ba601c26 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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} diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 0a154985..35eca0a9 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -68,7 +68,7 @@ void Dispatcher::loop() { next_tx_time = futureMillis(t * getAirtimeBudgetFactor()); _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 +80,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 +108,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 +164,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 { @@ -249,16 +260,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 +320,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 { diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 25a41d82..0a448c40 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -184,6 +184,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(); }; diff --git a/src/Identity.h b/src/Identity.h index c3ffcd75..008f7b5b 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -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; } diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 0548c907..57fee140 100644 --- a/src/Mesh.cpp +++ b/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 { diff --git a/src/Mesh.h b/src/Mesh.h index 00f7ed00..f9f87863 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -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 diff --git a/src/MeshCore.h b/src/MeshCore.h index f194cdeb..70cd0f06 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -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) diff --git a/src/Packet.cpp b/src/Packet.cpp index 2d54ca45..aad3e2f4 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -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 diff --git a/src/Packet.h b/src/Packet.h index 42d73f41..78619546 100644 --- a/src/Packet.h +++ b/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; } diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 6de7469d..5ec678c7 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -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 :-( diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 55b70ca5..12823827 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -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; } diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index dfbc3fce..b758f706 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -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) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 67f56714..e7f29ccf 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -64,7 +64,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { 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((uint8_t *)&_prefs->sx126x_rx_boosted_gain, sizeof(_prefs->sx126x_rx_boosted_gain)); // 121 - file.read(pad, 2); // 122 : 2 byte unused + file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 122 + file.read(pad, 1); // 123 : 1 byte unused 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 @@ -96,6 +97,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); @@ -152,7 +154,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { 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((uint8_t *)&_prefs->sx126x_rx_boosted_gain, sizeof(_prefs->sx126x_rx_boosted_gain)); // 121 - file.write(pad, 2); // 122 : 2 byte unused + file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 122 + file.write(pad, 1); // 123 : 1 byte unused 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 @@ -334,6 +337,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) { @@ -371,6 +376,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) { @@ -561,6 +577,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(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index e750fd2e..7005556e 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -52,8 +52,8 @@ struct NodePrefs { // persisted to file uint32_t discovery_mod_timestamp; float adc_multiplier; char owner_info[120]; - // Power settings - uint8_t sx126x_rx_boosted_gain; + uint8_t sx126x_rx_boosted_gain; // power settings + uint8_t path_hash_mode; // which path mode to use when sending }; class CommonCLICallbacks { diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index eff07741..ede977ca 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -3,12 +3,14 @@ #include #include +#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 diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 1db858f5..2c8753d4 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -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 diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 0332af07..96f67dc9 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -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; diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 4f28eac6..67d63979 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -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); diff --git a/src/helpers/StaticPoolPacketManager.h b/src/helpers/StaticPoolPacketManager.h index bbf4b193..52c299db 100644 --- a/src/helpers/StaticPoolPacketManager.h +++ b/src/helpers/StaticPoolPacketManager.h @@ -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]; } diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index eccfeca6..dcfa0e1e 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -1,4 +1,5 @@ #include "SerialBLEInterface.h" +#include "esp_mac.h" // See the following for generating UUIDs: // https://www.uuidgenerator.net/ diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 4d549790..e5abf79c 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -44,6 +44,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} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.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 = diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index b520e342..e92b155f 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -27,6 +27,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 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 8cf9e68f..ffee37a9 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -27,7 +27,9 @@ build_src_filter = ${esp32_base.build_src_filter} + + + -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 @@ -132,3 +134,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} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${T_Beam_S3_Supreme_SX1262.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index f27f57fd..7e1330e6 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -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} \ No newline at end of file + ${esp32_ota.lib_deps} diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini index ed84af6c..84c6562a 100644 --- a/variants/m5stack_unit_c6l/platformio.ini +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -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 @@ -95,6 +95,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} + - diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index 26996ce1..b4165bb2 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -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 @@ -132,14 +133,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 @@ -148,7 +149,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 @@ -160,7 +161,7 @@ build_flags = 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 @@ -169,7 +170,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 @@ -181,7 +182,7 @@ build_flags = 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 diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 3d0578cc..6c1e8f63 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -97,6 +97,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}