Add Japan LoRa LBT compliance (ARIB STD-T108) with runtime frequency detection#2218
Add Japan LoRa LBT compliance (ARIB STD-T108) with runtime frequency detection#2218jirogit wants to merge 13 commits intomeshcore-dev:devfrom
Conversation
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.
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)
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.
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.
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.
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
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.
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.
_tx_start_ms and TX duration logging were added for 4-second airtime verification and are no longer needed.
- 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.
…GroupTextLen() 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.
| 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 { |
There was a problem hiding this comment.
The function name doesn't match the frequencies being checked. 920.8, 921.0, and 921.2 MHz are the default channels for AS923-2, which is the plan for Indonesia and Vietnam, not Japan. Japan uses AS923-1 with default channels at 923.2 and 923.4 MHz. This means the function will return false for most Japanese devices and true for Indonesian/Vietnamese ones, a false positive for an entirely different region. Suggest renaming to isAS923_2Mode() or correcting the frequencies to 923.2/923.4 MHz depending on the intended behavior.
There was a problem hiding this comment.
The function name doesn't match the frequencies being checked. 920.8, 921.0, and 921.2 MHz are the default channels for AS923-2, which is the plan for Indonesia and Vietnam, not Japan. Japan uses AS923-1 with default channels at 923.2 and 923.4 MHz. This means the function will return false for most Japanese devices and true for Indonesian/Vietnamese ones, a false positive for an entirely different region. Suggest renaming to isAS923_2Mode() or correcting the frequencies to 923.2/923.4 MHz depending on the intended behavior.
Thank you for the review. You are correct that 920.8/921.0/921.2 MHz overlap with AS923-2 (Indonesia/Vietnam in LoRaWAN terms). However, these frequencies are legal under Japanese radio law (ARIB STD-T108, 920.6–928.0 MHz) and are within the band permitted for use in Japan.
Regarding the false positive concern for Indonesian/Vietnamese users: MeshCore does not currently have a defined default frequency for Indonesia. Vietnam's current default is 920.250 MHz, which does not match any of the three frequencies checked by isJapanMode() (920.800 / 921.000 / 921.200 MHz). In practice, the risk of unintended activation on non-Japanese nodes is low.
That said, the concern is valid in principle and points toward a broader architectural question: as MeshCore expands into regions with different regulatory requirements — duty cycle limits in the EU, LBT variants in other Asian countries, TX power caps, airtime limits — a more general regional compliance framework would be valuable.
A future "set radio_law / get radio_law" command (distinct from the existing "set region") could allow users to explicitly select their regulatory domain (e.g. JP, EU, FCC), enabling the firmware to apply the appropriate constraints automatically.
The current frequency-based isJapanMode() is intentionally minimal and pragmatic given the near-zero Japanese node count today, but we recognize it is not a scalable long-term solution. We are open to adding such a mechanism if the maintainer prefers a more structured approach, and would welcome guidance on the preferred direction for regional compliance in MeshCore going forward.
Closes #2079
Summary
This PR implements Listen-Before-Talk (LBT) for Japan LoRa operation, as required by ARIB STD-T108 / Giteki (技適) certification. Activation is automatic based on operating frequency — no build flags required.
Changes
Radio accessor methods (RadioLibWrappers.h/cpp, Wrapper classes):
getCodingRate()— returns current LoRa coding rate at runtimegetFreqMHz()— returns operating frequency in MHzgetMaxTextLen()— returns dynamic DM message length limit based on CRgetMaxGroupTextLen()— returns dynamic channel message length limit based on CRisJapanMode()— returns true when operating on JP LoRa frequencies (920.800 / 921.000 / 921.200 MHz)Japan LBT (BaseChatMesh.cpp, RadioLibWrappers.cpp):
getTimeOnAir()on RAK WisMesh Tag (SF12/BW125):DM and channel packets differ by 1 byte overhead, warranting separate limits for CR4/7 and CR4/8. Non-JP returns default bytes unchanged.
Build:
YIELD_TASK()macro)Note on commit history
Early commits include CAD-based channel detection based on weebl2000's work (PR #1727). This was subsequently replaced with RSSI-based LBT, as ARIB STD-T108 requires energy detection, not LoRa preamble detection. The final code does not use CAD. The commit history reflects this exploration path.
Testing
Devices: RAK WisMesh Tag (SX1262), Wio Tracker L1 Pro (SX1262)
Settings: SF12 / BW125 / CR4-8 (JP standard)
16-byte simultaneous DM: 3/3 success
48-byte simultaneous DM: 3/3 success
Channel messages: all CR rates confirmed (CR4/5–CR4/8).
Effective text limit is (channel_limit - 2 - sender_name_length) bytes,
where 2 bytes are consumed by the ": " separator.
Example (8-byte sender name): CR4/5=54B, CR4/6=38B, CR4/7=29B, CR4/8=19B
Known limitations
getMaxTextLen()/getMaxGroupTextLen()to the UI layer and is left for a follow-up.