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/Dispatcher.h b/src/Dispatcher.h index 2a99d0682b..d32633f1b9 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -38,6 +38,9 @@ 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) * \param bytes the raw packet data diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 7ddc461d29..220a375b3c 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -395,9 +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); - if (text_len > MAX_TEXT_LEN) return NULL; - if (attempt > 3 && text_len > MAX_TEXT_LEN-2) return NULL; - + int max_len = _radio->getMaxTextLen(); + if (text_len > max_len) return NULL; + if (attempt > 3 && text_len > max_len - 2) return NULL; 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); @@ -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/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index b39e736388..a778323f72 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -5,7 +5,8 @@ #include #include -#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) #include "ContactInfo.h" 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..ad05bfca38 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -31,4 +31,11 @@ class CustomLR1110Wrapper : public RadioLibWrapper { bool getRxBoostedGainMode() const override { return ((CustomLR1110 *)_radio)->getRxBoostedGainMode(); } + + uint8_t getCodingRate() const override { + return ((CustomLR1110 *)_radio)->getCodingRate(); + } + float getFreqMHz() const override { + return ((CustomLR1110 *)_radio)->getFreqMHz(); + } }; 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/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 +}; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 6499deb296..e9c0255434 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -36,4 +36,11 @@ 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 + } + float getFreqMHz() const override { + return ((CustomSX1262 *)_radio)->freqMHz; + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 2216ca8f39..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 @@ -34,6 +41,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; @@ -165,13 +173,51 @@ bool RadioLibWrapper::isSendComplete() { void RadioLibWrapper::onSendFinished() { _radio->finishTransmit(); _board->onAfterTransmit(); + if (isJapanMode()) { + // ARIB STD-T108: wait >= 50ms after TX before next transmission + delay(50); + } 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 + + // 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) { + YIELD_TASK(); + } + return true; + } + 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) { + YIELD_TASK(); + } + return false; + } + + // Non-JP: original behavior (RSSI threshold only) + return getCurrentRSSI() > _noise_floor + _threshold; } float RadioLibWrapper::getLastRSSI() const { diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index a42e060a4c..dfa702ce44 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; 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; @@ -31,14 +32,43 @@ 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 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 || + fabsf(freq - 921.000f) < 0.05f || + fabsf(freq - 921.200f) < 0.05f); + } + + int getMaxTextLen() const { + if (!isJapanMode()) return 10 * 16; // default 160 bytes + uint8_t cr = getCodingRate(); + 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(); + int getNoiseFloor() const override { return _noise_floor; } void triggerNoiseFloorCalibrate(int threshold) override; void resetAGC() override; diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 081cb0d077..79de2096d0 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -118,4 +118,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>