From 52b9d877a6f206a8a786f31bdf2a0c3a46e50b36 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 18 Feb 2026 01:16:52 +0100 Subject: [PATCH 01/13] Use hardware channel activity detection for checking interference --- examples/companion_radio/MyMesh.cpp | 2 +- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 2 +- src/helpers/radiolib/RadioLibWrappers.cpp | 33 ++++++++++++++++++++--- src/helpers/radiolib/RadioLibWrappers.h | 3 ++- 6 files changed, 36 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 60a5a75fec..c5cde20000 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -255,7 +255,7 @@ float MyMesh::getAirtimeBudgetFactor() const { } int MyMesh::getInterferenceThreshold() const { - return 0; // disabled for now, until currentRSSI() problem is resolved + return 1; // non-zero enables hardware CAD (Channel Activity Detection) before TX } int MyMesh::calcRxDelay(float score, uint32_t air_time) const { diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 24e8894927..738ffa8b88 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -871,7 +871,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.advert_interval = 1; // default to 2 minutes for NEW installs _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; - _prefs.interference_threshold = 0; // disabled + _prefs.interference_threshold = 1; // non-zero enables hardware CAD before TX // bridge defaults _prefs.bridge_enabled = 1; // enabled diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 7b94377387..3338df5ec4 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -622,7 +622,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.advert_interval = 1; // default to 2 minutes for NEW installs _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; - _prefs.interference_threshold = 0; // disabled + _prefs.interference_threshold = 1; // non-zero enables hardware CAD before TX #ifdef ROOM_PASSWORD StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 57d23a311e..37b0e8c1ab 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -723,7 +723,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.flood_advert_interval = 0; // disabled _prefs.disable_fwd = true; _prefs.flood_max = 64; - _prefs.interference_threshold = 0; // disabled + _prefs.interference_threshold = 1; // non-zero enables hardware CAD before TX // GPS defaults _prefs.gps_enabled = 0; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 2216ca8f39..a1ec7d1040 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -168,10 +168,37 @@ void RadioLibWrapper::onSendFinished() { state = STATE_IDLE; } +int16_t RadioLibWrapper::performChannelScan() { + return _radio->scanChannel(); +} + bool RadioLibWrapper::isChannelActive() { - return _threshold == 0 - ? false // interference check is disabled - : getCurrentRSSI() > _noise_floor + _threshold; + if (_threshold == 0) return false; // interference check is disabled + + int16_t result = performChannelScan(); + // scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY + // via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't + // try to read a non-existent packet and count a spurious recv error. + state = STATE_IDLE; + startRecv(); + + if (result != RADIOLIB_CHANNEL_FREE) { + // Random backoff to desynchronize retries between competing nodes + uint32_t backoff_until = millis() + random(8000, 22000); + while (millis() < backoff_until) { + vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + } + return true; + } + + // Small jitter even when channel is free to prevent simultaneous TX + // from two nodes that both detect a free channel at the same time + uint32_t jitter_until = millis() + random(0, 500); + while (millis() < jitter_until) { + vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + } + + return false; } float RadioLibWrapper::getLastRSSI() const { diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index a42e060a4c..7052ce02a9 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -31,13 +31,14 @@ class RadioLibWrapper : public mesh::Radio { bool isInRecvMode() const override; bool isChannelActive(); - bool isReceiving() override { + bool isReceiving() override { if (isReceivingPacket()) return true; return isChannelActive(); } virtual float getCurrentRSSI() =0; + virtual int16_t performChannelScan(); int getNoiseFloor() const override { return _noise_floor; } void triggerNoiseFloorCalibrate(int threshold) override; From 309a090958810e22b3fc200fa18171e9e986abc8 Mon Sep 17 00:00:00 2001 From: jirogit Date: Tue, 24 Mar 2026 22:06:14 -0700 Subject: [PATCH 02/13] Add JP_STRICT mode: enforce MAX_TEXT_LEN for ARIB STD-T108 compliance JP_STRICT limits MAX_TEXT_LEN to 1*CIPHER_BLOCK_SIZE (16 bytes) to keep TX time under 4 seconds on SF12/BW125/CR4-8 (Japan LoRa settings). SF12/BW125/CR4-8 airtime: 60 bytes total packet = ~3809ms (within 4s limit) packet overhead ~44 bytes, leaving 16 bytes for text payload Enabled only for WioTrackerL1 and RAK_WisMesh_Tag builds. --- src/helpers/BaseChatMesh.h | 9 ++++++++- variants/rak_wismesh_tag/platformio.ini | 3 ++- variants/wio-tracker-l1/platformio.ini | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index b39e736388..9382fd7cff 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -5,7 +5,14 @@ #include #include -#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1) +// JP_STRICT: limit MAX_TEXT_LEN to keep TX time under 4s (ARIB STD-T108) +// SF12/BW125/CR4-8: 60 bytes total packet = ~3809ms, overhead ~44 bytes +// leaving 16 bytes (1*CIPHER_BLOCK_SIZE) for text payload +#ifdef JP_STRICT + #define MAX_TEXT_LEN (1*CIPHER_BLOCK_SIZE) // ~16 chars, TX <= 4s for JP +#else + #define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1) +#endif #include "ContactInfo.h" diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 081cb0d077..7091917cdd 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -25,6 +25,7 @@ build_flags = ${nrf52_base.build_flags} -D PIN_BUZZER=21 -D PIN_BOARD_SDA=PIN_WIRE_SDA -D PIN_BOARD_SCL=PIN_WIRE_SCL + -D JP_STRICT build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak_wismesh_tag> + @@ -118,4 +119,4 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} - +<../examples/simple_sensor> \ No newline at end of file + +<../examples/simple_sensor> diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 6c1e8f634a..0329dd187d 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -16,6 +16,7 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_OLED_RESET=-1 -D GPS_BAUD_RATE=9600 + -D JP_STRICT build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/wio-tracker-l1> From 7bae0d8405ff69c179461c2a7903ab60bf91e596 Mon Sep 17 00:00:00 2001 From: jirogit Date: Tue, 24 Mar 2026 23:11:29 -0700 Subject: [PATCH 03/13] Add JP_STRICT 5ms continuous RSSI sensing before TX (ARIB STD-T108) Under JP_STRICT mode, add energy-based carrier sensing loop before CAD: - Sample RSSI continuously for >= 5ms before each TX attempt - If RSSI exceeds threshold at any point, trigger random backoff - ARIB STD-T108 requires energy-based sensing; LoRa CAD alone is insufficient as it only detects LoRa preambles This satisfies the minimum 5ms continuous sensing requirement for the 920.6-922.2 MHz zone (specified low power radio, LBT mode). Test results (JP LoRa SF12/BW125/CR4-8, simultaneous DM): - 16-char: 2/2 success, delivered within 1:03-1:50 - 1-char: 3/4 success, delivered within 0:46-2:11 (shorter airtime reduces RSSI detection window) --- src/helpers/radiolib/RadioLibWrappers.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index a1ec7d1040..97fdb4ab90 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -142,6 +142,7 @@ uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { _board->onBeforeTransmit(); + _tx_start_ms = millis(); // recording TX time int err = _radio->startTransmit((uint8_t *) bytes, len); if (err == RADIOLIB_ERR_NONE) { state = STATE_TX_WAIT; @@ -157,6 +158,8 @@ bool RadioLibWrapper::isSendComplete() { if (state & STATE_INT_READY) { state = STATE_IDLE; n_sent++; + uint32_t tx_duration = millis() - _tx_start_ms; + MESH_DEBUG_PRINTLN("TX duration: %lu ms (len=%d)", tx_duration); return true; } return false; @@ -175,6 +178,24 @@ int16_t RadioLibWrapper::performChannelScan() { bool RadioLibWrapper::isChannelActive() { if (_threshold == 0) return false; // interference check is disabled +#ifdef JP_STRICT + // ARIB STD-T108 compliant LBT: continuous RSSI sensing for >= 5ms + // Energy-based sensing required; LoRa CAD alone is not sufficient + uint32_t sense_start = millis(); + uint32_t sense_duration_ms = 5; + while (millis() - sense_start < sense_duration_ms) { + if (getCurrentRSSI() > _noise_floor + _threshold) { + // Channel busy detected during 5ms sensing window + uint32_t backoff_until = millis() + random(8000, 22000); + while (millis() < backoff_until) { + vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + } + return true; + } + vTaskDelay(1); // yield CPU between RSSI samples + } +#endif + int16_t result = performChannelScan(); // scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY // via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't From f4519b19c8ddfd8c147a0281dbc23c3fb0d32269 Mon Sep 17 00:00:00 2001 From: jirogit Date: Wed, 25 Mar 2026 00:41:21 -0700 Subject: [PATCH 04/13] JP_STRICT: use absolute -80dBm RSSI threshold for LBT (ARIB STD-T108) Replace relative threshold (_noise_floor + _threshold) with absolute -80dBm as specified by ARIB STD-T108 for carrier sense detection. Previous relative threshold (~-99dBm) caused false busy detection from environmental noise. -80dBm matches the legal requirement and reduces spurious backoff. --- src/helpers/radiolib/RadioLibWrappers.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 97fdb4ab90..5ef5a07605 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -168,6 +168,10 @@ bool RadioLibWrapper::isSendComplete() { void RadioLibWrapper::onSendFinished() { _radio->finishTransmit(); _board->onAfterTransmit(); +#ifdef JP_STRICT + // ARIB STD-T108: wait >= 50ms after TX before next transmission + delay(50); +#endif state = STATE_IDLE; } @@ -184,7 +188,7 @@ bool RadioLibWrapper::isChannelActive() { uint32_t sense_start = millis(); uint32_t sense_duration_ms = 5; while (millis() - sense_start < sense_duration_ms) { - if (getCurrentRSSI() > _noise_floor + _threshold) { + if (getCurrentRSSI() > -80.0f) { // Channel busy detected during 5ms sensing window uint32_t backoff_until = millis() + random(8000, 22000); while (millis() < backoff_until) { From eb585237750cc451d1da9d80eb89cf8b50cea35d Mon Sep 17 00:00:00 2001 From: jirogit Date: Wed, 25 Mar 2026 19:39:46 -0700 Subject: [PATCH 05/13] JP_STRICT: adjust MAX_TEXT_LEN based on LORA_CR setting MAX_TEXT_LEN is automatically selected based on the LORA_CR build flag defined in platformio.ini (e.g. -D LORA_CR=5). CR4/5: 48 bytes (~16 JP chars, TX ~3808ms) CR4/6: 32 bytes (~10 JP chars) CR4/7: 24 bytes (~8 JP chars) CR4/8: 16 bytes (~5 JP chars, default) To enable CR4/5 for Japan, add -D LORA_CR=5 to your board's build_flags in platformio.ini. Set LORA_CR=5 for WioTrackerL1 and RAK WisMesh Tag. --- src/helpers/BaseChatMesh.h | 20 ++++++++++++++++---- variants/rak_wismesh_tag/platformio.ini | 1 + variants/wio-tracker-l1/platformio.ini | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 9382fd7cff..65a5e558d1 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -6,12 +6,24 @@ #include // JP_STRICT: limit MAX_TEXT_LEN to keep TX time under 4s (ARIB STD-T108) -// SF12/BW125/CR4-8: 60 bytes total packet = ~3809ms, overhead ~44 bytes -// leaving 16 bytes (1*CIPHER_BLOCK_SIZE) for text payload +// SF12/BW125, packet overhead ~44 bytes +// CR4/5: 100 bytes total = ~3808ms → 56 bytes text → ~18 JP chars +// CR4/6: 80 bytes total = ~3530ms → 36 bytes text → ~12 JP chars +// CR4/7: 70 bytes total = ~3530ms → 26 bytes text → ~8 JP chars +// CR4/8: 60 bytes total = ~3809ms → 16 bytes text → ~5 JP chars #ifdef JP_STRICT - #define MAX_TEXT_LEN (1*CIPHER_BLOCK_SIZE) // ~16 chars, TX <= 4s for JP + #if defined(LORA_CR) && (LORA_CR == 5) + #define MAX_TEXT_LEN (3*CIPHER_BLOCK_SIZE) // 48 bytes ~18 JP chars + #elif defined(LORA_CR) && (LORA_CR == 6) + #define MAX_TEXT_LEN (2*CIPHER_BLOCK_SIZE) // 32 bytes ~10 JP chars + #elif defined(LORA_CR) && (LORA_CR == 7) + #define MAX_TEXT_LEN (1*CIPHER_BLOCK_SIZE+8) // 24 bytes ~8 JP chars + #else + #define MAX_TEXT_LEN (1*CIPHER_BLOCK_SIZE) // 16 bytes ~5 JP chars + #endif #else - #define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1) + #define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) +// must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1) #endif #include "ContactInfo.h" diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 7091917cdd..447bd46947 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -26,6 +26,7 @@ build_flags = ${nrf52_base.build_flags} -D PIN_BOARD_SDA=PIN_WIRE_SDA -D PIN_BOARD_SCL=PIN_WIRE_SCL -D JP_STRICT + -D LORA_CR=5 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak_wismesh_tag> + diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 0329dd187d..962a392d6c 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -17,6 +17,7 @@ build_flags = ${nrf52_base.build_flags} -D PIN_OLED_RESET=-1 -D GPS_BAUD_RATE=9600 -D JP_STRICT + -D LORA_CR=5 build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/wio-tracker-l1> From c5bacd7de9a3df37e9043e05668720214c716a13 Mon Sep 17 00:00:00 2001 From: jirogit Date: Thu, 26 Mar 2026 00:29:36 -0700 Subject: [PATCH 06/13] JP_STRICT: dynamic MAX_TEXT_LEN based on runtime CR value Instead of compile-time LORA_CR define, MAX_TEXT_LEN is now determined at runtime by reading the actual coding rate from the radio hardware. Added getMaxTextLen() to RadioLibWrapper and Dispatcher: - CR4/5: 48 bytes (~16 JP chars, TX ~3808ms) - CR4/6: 32 bytes (~10 JP chars) - CR4/7: 24 bytes (~8 JP chars) - CR4/8: 16 bytes (~5 JP chars, default) getCodingRate() added to CustomSX1262Wrapper to read codingRate from RadioLib PhysicalLayer at runtime. Tested: 48-byte limit with LORA_CR=5, 16-byte limit with LORA_CR=8. --- src/Dispatcher.h | 4 ++++ src/helpers/BaseChatMesh.cpp | 7 +++++++ src/helpers/BaseChatMesh.h | 20 +------------------- src/helpers/radiolib/CustomSX1262Wrapper.h | 4 ++++ src/helpers/radiolib/RadioLibWrappers.h | 16 ++++++++++++++-- 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 2a99d0682b..7d4e2abf29 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -38,6 +38,10 @@ class Radio { virtual float packetScore(float snr, int packet_len) = 0; +#ifdef JP_STRICT + virtual int getMaxTextLen() const { return 1 * 16; } // default: CR4/8 +#endif + /** * \brief starts the raw packet send. (no wait) * \param bytes the raw packet data diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 7ddc461d29..d414c7b73f 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -395,8 +395,15 @@ void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mes mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack) { int text_len = strlen(text); + +#ifdef JP_STRICT + int max_len = _radio->getMaxTextLen(); + if (text_len > max_len) return NULL; + if (attempt > 3 && text_len > max_len - 2) return NULL; +#else if (text_len > MAX_TEXT_LEN) return NULL; if (attempt > 3 && text_len > MAX_TEXT_LEN-2) return NULL; +#endif uint8_t temp[5+MAX_TEXT_LEN+1]; memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 65a5e558d1..a778323f72 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -5,26 +5,8 @@ #include #include -// JP_STRICT: limit MAX_TEXT_LEN to keep TX time under 4s (ARIB STD-T108) -// SF12/BW125, packet overhead ~44 bytes -// CR4/5: 100 bytes total = ~3808ms → 56 bytes text → ~18 JP chars -// CR4/6: 80 bytes total = ~3530ms → 36 bytes text → ~12 JP chars -// CR4/7: 70 bytes total = ~3530ms → 26 bytes text → ~8 JP chars -// CR4/8: 60 bytes total = ~3809ms → 16 bytes text → ~5 JP chars -#ifdef JP_STRICT - #if defined(LORA_CR) && (LORA_CR == 5) - #define MAX_TEXT_LEN (3*CIPHER_BLOCK_SIZE) // 48 bytes ~18 JP chars - #elif defined(LORA_CR) && (LORA_CR == 6) - #define MAX_TEXT_LEN (2*CIPHER_BLOCK_SIZE) // 32 bytes ~10 JP chars - #elif defined(LORA_CR) && (LORA_CR == 7) - #define MAX_TEXT_LEN (1*CIPHER_BLOCK_SIZE+8) // 24 bytes ~8 JP chars - #else - #define MAX_TEXT_LEN (1*CIPHER_BLOCK_SIZE) // 16 bytes ~5 JP chars - #endif -#else - #define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) +#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1) -#endif #include "ContactInfo.h" diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 6499deb296..5edaa25ea6 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -36,4 +36,8 @@ class CustomSX1262Wrapper : public RadioLibWrapper { bool getRxBoostedGainMode() const override { return ((CustomSX1262 *)_radio)->getRxBoostedGainMode(); } + + uint8_t getCodingRate() const override { + return ((CustomSX1262 *)_radio)->codingRate + 4; // RadioLib stores 1-4, return 5-8 + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 7052ce02a9..51d928cab1 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -7,7 +7,7 @@ class RadioLibWrapper : public mesh::Radio { protected: PhysicalLayer* _radio; mesh::MainBoard* _board; - uint32_t n_recv, n_sent, n_recv_errors; + uint32_t n_recv, n_sent, n_recv_errors, _tx_start_ms; int16_t _noise_floor, _threshold; uint16_t _num_floor_samples; int32_t _floor_sample_sum; @@ -38,8 +38,20 @@ class RadioLibWrapper : public mesh::Radio { } virtual float getCurrentRSSI() =0; - virtual int16_t performChannelScan(); + virtual uint8_t getCodingRate() const = 0; + +#ifdef JP_STRICT + int getMaxTextLen() const { + uint8_t cr = getCodingRate(); + if (cr <= 5) return 3 * 16; // 48 bytes ~16 JP chars + if (cr == 6) return 2 * 16; // 32 bytes ~10 JP chars + if (cr == 7) return 1 * 16 + 8; // 24 bytes ~8 JP chars + return 1 * 16; // 16 bytes ~5 JP chars + } +#endif + virtual int16_t performChannelScan(); + int getNoiseFloor() const override { return _noise_floor; } void triggerNoiseFloorCalibrate(int threshold) override; void resetAGC() override; From 2d11d9fedba04db2d411527bf5df656d1b41c4dc Mon Sep 17 00:00:00 2001 From: jirogit Date: Thu, 26 Mar 2026 12:29:35 -0700 Subject: [PATCH 07/13] Add exponential backoff for channel busy detection Replace fixed random(8000-22000ms) backoff with exponential backoff: - 1st busy: 3-6s - 2nd busy: 6-12s - 3rd+ busy: 12-20s (capped) - Reset counter on channel free Results (48-byte simultaneous DM, JP SF12/BW125/CR4-5): - 3/3 success, delivered within 0:23-0:45 - Previous fixed backoff: 1:03-3:55 --- src/helpers/radiolib/RadioLibWrappers.cpp | 33 +++++++++++++---------- src/helpers/radiolib/RadioLibWrappers.h | 1 + 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 5ef5a07605..915c0dad5a 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -34,6 +34,7 @@ void RadioLibWrapper::begin() { _noise_floor = 0; _threshold = 0; + _busy_count = 0; // initialize exponential backoff counter // start average out some samples _num_floor_samples = 0; @@ -183,39 +184,43 @@ bool RadioLibWrapper::isChannelActive() { if (_threshold == 0) return false; // interference check is disabled #ifdef JP_STRICT - // ARIB STD-T108 compliant LBT: continuous RSSI sensing for >= 5ms - // Energy-based sensing required; LoRa CAD alone is not sufficient + // 5ms continuous RSSI sensing uint32_t sense_start = millis(); - uint32_t sense_duration_ms = 5; - while (millis() - sense_start < sense_duration_ms) { + while (millis() - sense_start < 5) { if (getCurrentRSSI() > -80.0f) { - // Channel busy detected during 5ms sensing window - uint32_t backoff_until = millis() + random(8000, 22000); + // RSSI busy: backoff and return without CAD + _busy_count++; + uint32_t base_ms = 3000; + uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)20000); + uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); while (millis() < backoff_until) { - vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + vTaskDelay(1); } return true; } - vTaskDelay(1); // yield CPU between RSSI samples + vTaskDelay(1); } #endif + // CAD int16_t result = performChannelScan(); - // scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY - // via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't - // try to read a non-existent packet and count a spurious recv error. state = STATE_IDLE; startRecv(); if (result != RADIOLIB_CHANNEL_FREE) { - // Random backoff to desynchronize retries between competing nodes - uint32_t backoff_until = millis() + random(8000, 22000); + // CAD busy: backoff + _busy_count++; + uint32_t base_ms = 3000; + uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)20000); + uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); while (millis() < backoff_until) { - vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + vTaskDelay(1); } return true; } + _busy_count = 0; + // Small jitter even when channel is free to prevent simultaneous TX // from two nodes that both detect a free channel at the same time uint32_t jitter_until = millis() + random(0, 500); diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 51d928cab1..66ad25253b 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -9,6 +9,7 @@ class RadioLibWrapper : public mesh::Radio { mesh::MainBoard* _board; uint32_t n_recv, n_sent, n_recv_errors, _tx_start_ms; int16_t _noise_floor, _threshold; + uint8_t _busy_count; // consecutive busy detections for exponential backoff uint16_t _num_floor_samples; int32_t _floor_sample_sum; From d7045a102086141222f593ca61cff8aab2775006 Mon Sep 17 00:00:00 2001 From: jirogit Date: Thu, 26 Mar 2026 20:21:37 -0700 Subject: [PATCH 08/13] Fix missing newline at end of file in CustomSX1262.h --- src/helpers/radiolib/CustomSX1262.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/radiolib/CustomSX1262.h b/src/helpers/radiolib/CustomSX1262.h index ad20122902..ab8f2dc942 100644 --- a/src/helpers/radiolib/CustomSX1262.h +++ b/src/helpers/radiolib/CustomSX1262.h @@ -97,4 +97,4 @@ class CustomSX1262 : public SX1262 { readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1); return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED); } -}; \ No newline at end of file +}; From 73ec37657c78e5698a7c137efe827c613a135bfd Mon Sep 17 00:00:00 2001 From: jirogit Date: Thu, 26 Mar 2026 22:51:01 -0700 Subject: [PATCH 09/13] Add getCodingRate() to CustomLR1110 and CustomLR1110Wrapper LR1110 has codingRate as protected field in LR_common.h, so getCodingRate() is implemented in CustomLR1110 class directly, and exposed via CustomLR1110Wrapper override. This enables dynamic MAX_TEXT_LEN calculation for T1000-E under JP_STRICT mode. --- src/helpers/radiolib/CustomLR1110.h | 6 ++- src/helpers/radiolib/CustomLR1110Wrapper.h | 4 ++ src/helpers/radiolib/RadioLibWrappers.cpp | 44 ++++++++-------------- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index 7c3685a112..983e23d1db 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -36,4 +36,8 @@ class CustomLR1110 : public LR1110 { bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); return detected; } -}; \ No newline at end of file + + uint8_t getCodingRate() const { + return this->codingRate + 4; // RadioLib stores 1-4, return 5-8 + } +}; diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index 42d364408c..68db703df0 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -31,4 +31,8 @@ class CustomLR1110Wrapper : public RadioLibWrapper { bool getRxBoostedGainMode() const override { return ((CustomLR1110 *)_radio)->getRxBoostedGainMode(); } + + uint8_t getCodingRate() const override { + return ((CustomLR1110 *)_radio)->getCodingRate(); + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 915c0dad5a..70c163c8f8 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -184,53 +184,39 @@ bool RadioLibWrapper::isChannelActive() { if (_threshold == 0) return false; // interference check is disabled #ifdef JP_STRICT - // 5ms continuous RSSI sensing + // ARIB STD-T108 compliant LBT: continuous RSSI sensing for >= 5ms + // Energy-based sensing required; LoRa CAD not used here — + // CAD detects only LoRa preambles and is not required by ARIB STD-T108 uint32_t sense_start = millis(); while (millis() - sense_start < 5) { if (getCurrentRSSI() > -80.0f) { - // RSSI busy: backoff and return without CAD + // Channel busy: exponential backoff (tuned for JP 4s airtime) _busy_count++; - uint32_t base_ms = 3000; - uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)20000); + uint32_t base_ms = 2000; + uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)16000); uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); while (millis() < backoff_until) { - vTaskDelay(1); + vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE } return true; } - vTaskDelay(1); + vTaskDelay(1); // yield CPU between RSSI samples } -#endif - - // CAD - int16_t result = performChannelScan(); - state = STATE_IDLE; - startRecv(); - - if (result != RADIOLIB_CHANNEL_FREE) { - // CAD busy: backoff - _busy_count++; - uint32_t base_ms = 3000; - uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)20000); - uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); - while (millis() < backoff_until) { - vTaskDelay(1); - } - return true; - } - + // Channel free: reset busy counter and add small jitter _busy_count = 0; - - // Small jitter even when channel is free to prevent simultaneous TX - // from two nodes that both detect a free channel at the same time uint32_t jitter_until = millis() + random(0, 500); while (millis() < jitter_until) { vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE } - return false; + +#else + // Non-JP: original behavior (RSSI threshold only) + return getCurrentRSSI() > _noise_floor + _threshold; +#endif } + float RadioLibWrapper::getLastRSSI() const { return _radio->getRSSI(); } From c725b82e779bb14a16b3e9ada7810a3521ca0c83 Mon Sep 17 00:00:00 2001 From: jirogit Date: Fri, 27 Mar 2026 23:42:37 -0700 Subject: [PATCH 10/13] Replace JP_STRICT build flag with runtime frequency detection JP LBT mode now activates automatically based on operating frequency: - CH25: 920.800MHz - CH26: 921.000MHz - CH27: 921.200MHz (ARIB STD-T108, 200kHz grid) Changes: - Add isJapanMode() to RadioLibWrapper using getFreqMHz() - Add getFreqMHz() to CustomSX1262Wrapper and CustomLR1110Wrapper - Remove #ifdef JP_STRICT throughout, replaced by isJapanMode() - Remove -D JP_STRICT build flag from platformio.ini - MAX_TEXT_LEN dynamically determined by CR at runtime via getMaxTextLen() No build flags required: JP compliance activates automatically when device is configured to Japan 3 frequencies. --- src/Dispatcher.h | 4 +- src/helpers/BaseChatMesh.cpp | 7 --- src/helpers/radiolib/CustomLR1110Wrapper.h | 3 ++ src/helpers/radiolib/CustomSX1262Wrapper.h | 3 ++ src/helpers/radiolib/RadioLibWrappers.cpp | 59 +++++++++++----------- src/helpers/radiolib/RadioLibWrappers.h | 17 +++++-- variants/rak_wismesh_tag/platformio.ini | 2 - variants/wio-tracker-l1/platformio.ini | 2 - 8 files changed, 48 insertions(+), 49 deletions(-) diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 7d4e2abf29..0abfa9ab79 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -38,9 +38,7 @@ class Radio { virtual float packetScore(float snr, int packet_len) = 0; -#ifdef JP_STRICT - virtual int getMaxTextLen() const { return 1 * 16; } // default: CR4/8 -#endif + virtual int getMaxTextLen() const { return 10 * 16; } // default: non-JP /** * \brief starts the raw packet send. (no wait) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index d414c7b73f..2003db4468 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -395,16 +395,9 @@ void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mes mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack) { int text_len = strlen(text); - -#ifdef JP_STRICT int max_len = _radio->getMaxTextLen(); if (text_len > max_len) return NULL; if (attempt > 3 && text_len > max_len - 2) return NULL; -#else - if (text_len > MAX_TEXT_LEN) return NULL; - if (attempt > 3 && text_len > MAX_TEXT_LEN-2) return NULL; -#endif - uint8_t temp[5+MAX_TEXT_LEN+1]; memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = (attempt & 3); diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index 68db703df0..ad05bfca38 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -35,4 +35,7 @@ class CustomLR1110Wrapper : public RadioLibWrapper { uint8_t getCodingRate() const override { return ((CustomLR1110 *)_radio)->getCodingRate(); } + float getFreqMHz() const override { + return ((CustomLR1110 *)_radio)->getFreqMHz(); + } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 5edaa25ea6..e9c0255434 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -40,4 +40,7 @@ class CustomSX1262Wrapper : public RadioLibWrapper { uint8_t getCodingRate() const override { return ((CustomSX1262 *)_radio)->codingRate + 4; // RadioLib stores 1-4, return 5-8 } + float getFreqMHz() const override { + return ((CustomSX1262 *)_radio)->freqMHz; + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 70c163c8f8..d0a0a94c30 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -169,10 +169,10 @@ bool RadioLibWrapper::isSendComplete() { void RadioLibWrapper::onSendFinished() { _radio->finishTransmit(); _board->onAfterTransmit(); -#ifdef JP_STRICT - // ARIB STD-T108: wait >= 50ms after TX before next transmission - delay(50); -#endif + if (isJapanMode()) { + // ARIB STD-T108: wait >= 50ms after TX before next transmission + delay(50); + } state = STATE_IDLE; } @@ -183,40 +183,39 @@ int16_t RadioLibWrapper::performChannelScan() { bool RadioLibWrapper::isChannelActive() { if (_threshold == 0) return false; // interference check is disabled -#ifdef JP_STRICT - // ARIB STD-T108 compliant LBT: continuous RSSI sensing for >= 5ms - // Energy-based sensing required; LoRa CAD not used here — - // CAD detects only LoRa preambles and is not required by ARIB STD-T108 - uint32_t sense_start = millis(); - while (millis() - sense_start < 5) { - if (getCurrentRSSI() > -80.0f) { - // Channel busy: exponential backoff (tuned for JP 4s airtime) - _busy_count++; - uint32_t base_ms = 2000; - uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)16000); - uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); - while (millis() < backoff_until) { - vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + // Activate JP_STRICT LBT on Japan 920MHz band 3 channels only + // CH25=920.800MHz, CH26=921.000MHz, CH27=921.200MHz (ARIB STD-T108) + if (isJapanMode()) { + // ARIB STD-T108 compliant LBT: continuous RSSI sensing for >= 5ms + // Energy-based sensing required; LoRa CAD not used + uint32_t sense_start = millis(); + while (millis() - sense_start < 5) { + if (getCurrentRSSI() > -80.0f) { + // Channel busy: exponential backoff (tuned for JP 4s airtime) + _busy_count++; + uint32_t base_ms = 2000; + uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)16000); + uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); + while (millis() < backoff_until) { + vTaskDelay(1); + } + return true; } - return true; + vTaskDelay(1); } - vTaskDelay(1); // yield CPU between RSSI samples - } - // Channel free: reset busy counter and add small jitter - _busy_count = 0; - uint32_t jitter_until = millis() + random(0, 500); - while (millis() < jitter_until) { - vTaskDelay(1); // yield CPU to FreeRTOS tasks including BLE + // Channel free: reset busy counter and add small jitter + _busy_count = 0; + uint32_t jitter_until = millis() + random(0, 500); + while (millis() < jitter_until) { + vTaskDelay(1); + } + return false; } - return false; -#else // Non-JP: original behavior (RSSI threshold only) return getCurrentRSSI() > _noise_floor + _threshold; -#endif } - float RadioLibWrapper::getLastRSSI() const { return _radio->getRSSI(); } diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 66ad25253b..2ca2f59a26 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -40,16 +40,23 @@ class RadioLibWrapper : public mesh::Radio { virtual float getCurrentRSSI() =0; virtual uint8_t getCodingRate() const = 0; + virtual float getFreqMHz() const = 0; + + bool isJapanMode() const { + float freq = getFreqMHz(); + return (fabsf(freq - 920.800f) < 0.05f || + fabsf(freq - 921.000f) < 0.05f || + fabsf(freq - 921.200f) < 0.05f); + } -#ifdef JP_STRICT int getMaxTextLen() const { + if (!isJapanMode()) return 10 * 16; // default uint8_t cr = getCodingRate(); - if (cr <= 5) return 3 * 16; // 48 bytes ~16 JP chars - if (cr == 6) return 2 * 16; // 32 bytes ~10 JP chars + if (cr <= 5) return 3 * 16; // 48 bytes ~16 JP chars + if (cr == 6) return 2 * 16; // 32 bytes ~10 JP chars if (cr == 7) return 1 * 16 + 8; // 24 bytes ~8 JP chars - return 1 * 16; // 16 bytes ~5 JP chars + return 1 * 16; // 16 bytes ~5 JP chars } -#endif virtual int16_t performChannelScan(); diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 447bd46947..79de2096d0 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -25,8 +25,6 @@ build_flags = ${nrf52_base.build_flags} -D PIN_BUZZER=21 -D PIN_BOARD_SDA=PIN_WIRE_SDA -D PIN_BOARD_SCL=PIN_WIRE_SCL - -D JP_STRICT - -D LORA_CR=5 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak_wismesh_tag> + diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 962a392d6c..6c1e8f634a 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -16,8 +16,6 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_OLED_RESET=-1 -D GPS_BAUD_RATE=9600 - -D JP_STRICT - -D LORA_CR=5 build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/wio-tracker-l1> From 4276d84070213084c53ca2b3722dd0a205b6e2cd Mon Sep 17 00:00:00 2001 From: jirogit Date: Sat, 28 Mar 2026 00:02:00 -0700 Subject: [PATCH 11/13] Remove TX duration debug logging _tx_start_ms and TX duration logging were added for 4-second airtime verification and are no longer needed. --- src/helpers/radiolib/RadioLibWrappers.cpp | 3 --- src/helpers/radiolib/RadioLibWrappers.h | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index d0a0a94c30..e4f9e566ba 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -143,7 +143,6 @@ uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { _board->onBeforeTransmit(); - _tx_start_ms = millis(); // recording TX time int err = _radio->startTransmit((uint8_t *) bytes, len); if (err == RADIOLIB_ERR_NONE) { state = STATE_TX_WAIT; @@ -159,8 +158,6 @@ bool RadioLibWrapper::isSendComplete() { if (state & STATE_INT_READY) { state = STATE_IDLE; n_sent++; - uint32_t tx_duration = millis() - _tx_start_ms; - MESH_DEBUG_PRINTLN("TX duration: %lu ms (len=%d)", tx_duration); return true; } return false; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 2ca2f59a26..8c3a6e74eb 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -7,7 +7,7 @@ class RadioLibWrapper : public mesh::Radio { protected: PhysicalLayer* _radio; mesh::MainBoard* _board; - uint32_t n_recv, n_sent, n_recv_errors, _tx_start_ms; + uint32_t n_recv, n_sent, n_recv_errors; int16_t _noise_floor, _threshold; uint8_t _busy_count; // consecutive busy detections for exponential backoff uint16_t _num_floor_samples; From 56a2d93aa407203a245440c4e99a79729224dad3 Mon Sep 17 00:00:00 2001 From: jirogit Date: Sat, 28 Mar 2026 02:50:06 -0700 Subject: [PATCH 12/13] Fix build for non-FreeRTOS and non-SX1262 platforms - Replace vTaskDelay(1) with YIELD_TASK() macro for platform compatibility (FreeRTOS: vTaskDelay, others: delay) - Make getCodingRate() and getFreqMHz() non-pure virtual with defaults (default CR4/8, default freq 0.0f for unknown platforms) - Add getCodingRate() and getFreqMHz() to CustomSTM32WLxWrapper All environments now build successfully. --- src/helpers/radiolib/CustomSTM32WLxWrapper.h | 7 +++++++ src/helpers/radiolib/RadioLibWrappers.cpp | 13 ++++++++++--- src/helpers/radiolib/RadioLibWrappers.h | 6 +++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index e3e5202949..c6caa3a4ab 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -23,4 +23,11 @@ class CustomSTM32WLxWrapper : public RadioLibWrapper { } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } + + uint8_t getCodingRate() const override { + return ((CustomSTM32WLx *)_radio)->codingRate + 4; + } + float getFreqMHz() const override { + return ((CustomSTM32WLx *)_radio)->freqMHz; + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index e4f9e566ba..79d211d636 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -2,6 +2,13 @@ #define RADIOLIB_STATIC_ONLY 1 #include "RadioLibWrappers.h" +// Platform-safe yield for use in busy-wait loops +#ifdef NRF52_PLATFORM + #define YIELD_TASK() vTaskDelay(1) +#else + #define YIELD_TASK() delay(1) +#endif + #define STATE_IDLE 0 #define STATE_RX 1 #define STATE_TX_WAIT 3 @@ -194,17 +201,17 @@ bool RadioLibWrapper::isChannelActive() { uint32_t max_backoff = min(base_ms * (1u << _busy_count), (uint32_t)16000); uint32_t backoff_until = millis() + random(max_backoff / 2, max_backoff); while (millis() < backoff_until) { - vTaskDelay(1); + YIELD_TASK(); } return true; } - vTaskDelay(1); + YIELD_TASK(); } // Channel free: reset busy counter and add small jitter _busy_count = 0; uint32_t jitter_until = millis() + random(0, 500); while (millis() < jitter_until) { - vTaskDelay(1); + YIELD_TASK(); } return false; } diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 8c3a6e74eb..6c1035de9f 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -39,9 +39,9 @@ class RadioLibWrapper : public mesh::Radio { } virtual float getCurrentRSSI() =0; - virtual uint8_t getCodingRate() const = 0; - virtual float getFreqMHz() const = 0; - + virtual uint8_t getCodingRate() const { return 8; } // default CR4/8, override in subclass + virtual float getFreqMHz() const { return 0.0f; } // default unknown, override in subclass + // bool isJapanMode() const { float freq = getFreqMHz(); return (fabsf(freq - 920.800f) < 0.05f || From 9e03dc63664dba0c483e797bee437d44a6f63d51 Mon Sep 17 00:00:00 2001 From: jirogit Date: Wed, 1 Apr 2026 00:39:19 -0700 Subject: [PATCH 13/13] JP: update MAX_TEXT_LEN limits based on measured airtimes, add getMaxGroupTextLen() Measured actual TimeOnAir via RadioLib getTimeOnAir() on RAK WisMesh Tag (SF12/BW125, CR4/5-8) to verify ARIB STD-T108 4-second TX limit compliance. DM limits (getMaxTextLen): CR4/5: 64 bytes (3874ms) CR4/6: 48 bytes (3874ms) CR4/7: 32 bytes (3678ms) CR4/8: 24 bytes (3547ms) Channel message limits (getMaxGroupTextLen): CR4/5: 64 bytes (3710ms) CR4/6: 48 bytes (3678ms) CR4/7: 39 bytes (3907ms) CR4/8: 29 bytes (3809ms) DM and channel packets differ by 1 byte overhead, warranting separate limits for CR4/7 and CR4/8. Non-JP returns default 160 bytes unchanged. Also apply dynamic limit to sendCommandData() and sendGroupMessage() via getMaxTextLen()/getMaxGroupTextLen() instead of hardcoded MAX_TEXT_LEN. --- src/Dispatcher.h | 1 + src/helpers/BaseChatMesh.cpp | 7 +++++-- src/helpers/radiolib/RadioLibWrappers.h | 19 ++++++++++++++----- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 0abfa9ab79..d32633f1b9 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -39,6 +39,7 @@ class Radio { virtual float packetScore(float snr, int packet_len) = 0; virtual int getMaxTextLen() const { return 10 * 16; } // default: non-JP + virtual int getMaxGroupTextLen() const { return 10 * 16; } // default: non-JP /** * \brief starts the raw packet send. (no wait) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 2003db4468..220a375b3c 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -436,7 +436,8 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout) { int text_len = strlen(text); - if (text_len > MAX_TEXT_LEN) return MSG_SEND_FAILED; + int max_len = _radio->getMaxTextLen(); + if (text_len > max_len) return MSG_SEND_FAILED; uint8_t temp[5+MAX_TEXT_LEN+1]; memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique @@ -469,7 +470,9 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan char *ep = strchr((char *) &temp[5], 0); int prefix_len = ep - (char *) &temp[5]; - if (text_len + prefix_len > MAX_TEXT_LEN) text_len = MAX_TEXT_LEN - prefix_len; + int max_len = _radio->getMaxGroupTextLen(); + if (text_len + prefix_len > max_len) text_len = max_len - prefix_len; + memcpy(ep, text, text_len); ep[text_len] = 0; // null terminator diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 6c1035de9f..dfa702ce44 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -50,12 +50,21 @@ class RadioLibWrapper : public mesh::Radio { } int getMaxTextLen() const { - if (!isJapanMode()) return 10 * 16; // default + if (!isJapanMode()) return 10 * 16; // default 160 bytes uint8_t cr = getCodingRate(); - if (cr <= 5) return 3 * 16; // 48 bytes ~16 JP chars - if (cr == 6) return 2 * 16; // 32 bytes ~10 JP chars - if (cr == 7) return 1 * 16 + 8; // 24 bytes ~8 JP chars - return 1 * 16; // 16 bytes ~5 JP chars + if (cr <= 5) return 64; // 3874ms @ SF12/BW125/CR4-5 + if (cr == 6) return 48; // 3874ms @ SF12/BW125/CR4-6 + if (cr == 7) return 32; // 3678ms @ SF12/BW125/CR4-7 + return 24; // 3547ms @ SF12/BW125/CR4-8 + } + + int getMaxGroupTextLen() const { + if (!isJapanMode()) return 10 * 16; // default 160 bytes + uint8_t cr = getCodingRate(); + if (cr <= 5) return 64; // 3710ms @ SF12/BW125/CR4-5 + if (cr == 6) return 48; // 3678ms @ SF12/BW125/CR4-6 + if (cr == 7) return 39; // 3907ms @ SF12/BW125/CR4-7 + return 29; // 3809ms @ SF12/BW125/CR4-8 } virtual int16_t performChannelScan();