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 9 commits into
Conversation
| 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.
|
Related RFC Discussion: #2285 |
|
Question from #1727 (comment): How does it differ from the current Regards! |
Thank you very much for your question! This PR does not use The ARIB STD-T108 LBT requires an absolute −80 dBm threshold, a minimum 5ms continuous RSSI sensing window before each TX, and a defined backoff protocol — these are legal requirements, not preferences. The So the two mechanisms are complementary: |
|
One more note on the jitter mechanism in this PR, in context of the The 0–500ms random jitter added on channel-free detection serves a similar purpose to
Both mechanisms address the same root problem — synchronized transmission from multiple nodes — at different points in the chain. txdelay acts on the repeater receive side (after a packet arrives); JP LBT jitter acts on the transmit side (just before each TX attempt by any node, when the channel is free). syssi's video in #1727 clearly demonstrates what happens without staggering: with |
|
Update: airtime-scaled jitter (f2aad64) Replaced fixed random(0, 500) jitter with airtime-scaled random(0, airtime_ms / JP_LBT_JITTER_DIVISOR) (divisor=32). Jitter upper bound by setting: Tested divisors 16 and 32 at SF12/BW125/CR4/8 — both 3/3 success with no measurable difference in delivery time. Divisor 32 adopted as it halves the unnecessary wait without observable downside. |
|
I've renamed isJapanMode() to isAS923_1_JP() to address @4np's concern about AS923-2 frequency overlap. |
|
Added commit 4e09f6a: JP LBT txdelay suppression. Problem In JP LBT mode (SF12/BW125/CR4/8), the standard Why txdelay has limited value in JP LBT The purpose of
With very few MeshCore nodes currently deployed in Japan, repeater-to-repeater collision is not a practical concern. Fix When Collision probability analysis (4 simultaneous repeaters, 1ms detection threshold):
This scales naturally as CR decreases over time (CR4/8 → CR4/5):
Applied to |
Add getCodingRate() and getFreqMHz() virtual methods to RadioLibWrapper base class, enabling subclasses to report their operating frequency. isJapanMode() detects JP 920MHz band (CH25-27) for ARIB STD-T108 compliance. getMaxTextLen() and getMaxGroupTextLen() enforce 4-second airtime limits per SF12/BW125 measurements.
In JP 920MHz band (CH25-27): 5ms continuous RSSI sensing at -80dBm absolute threshold, exponential backoff (2000-16000ms) on busy, jitter (0-500ms) on free, then falls through to CAD if enabled. Non-JP path: existing relative-RSSI threshold + CAD unchanged. Add YIELD_TASK() macro and _busy_count field for backoff tracking.
Add isJapanMode() virtual to mesh::Radio (default false) so Dispatcher can query frequency context without depending on RadioLibWrapper. Dispatcher::getCADFailMaxDuration() returns UINT32_MAX for JP nodes, eliminating the 4-second forced-TX that would violate ARIB STD-T108. Non-JP nodes retain the original 4-second safety timeout unchanged.
getCodingRate() returns codingRate+4 (RadioLib stores 1-4 internally, expose as CR4/5-8 matching the rest of the codebase). CustomLR1110Wrapper overrides both getCodingRate() and getFreqMHz() so isJapanMode() and getMaxTextLen() work correctly on T1000-E.
Enables isJapanMode() and getMaxTextLen() to work on SX1262-based targets (WisMesh Tag, T-Echo Lite, T114, XIAO nRF52, etc.). codingRate accessed as SX1262 base class member +4 (same pattern as LR1110).
Add getMaxTextLen() and getMaxGroupTextLen() virtual to mesh::Radio (default 160 bytes). BaseChatMesh uses these instead of the hardcoded MAX_TEXT_LEN macro for limit checks in composeMsgPacket(), sendCommandData(), and sendGroupMessage(). Stack buffers remain sized to MAX_TEXT_LEN as a safe upper bound.
|
Rebased onto upstream/dev (includes #1727) This branch has been completely rebuilt on top of the current upstream/dev (which includes the merged CAD LBT from #1727). The previous history had #1727's CAD commits mixed in, causing conflicts across RadioLibWrappers.cpp/.h. Rather than resolving those conflict-by-conflict, I created a clean branch from upstream/dev and re-implemented the JP LBT changes on top. isAS923_1_JP() LBT and CAD now operate independently: JP mode: isAS923_1_JP() is active when frequency is set to 920.8 / 921.0 / 921.2 MHz, CAD optional — runs after RSSI clear if set cad on Non-JP: isAS923_1_JP() not active, CAD optional, existing behavior unchanged JP nodes run RSSI sensing first (ARIB requirement), then fall through to CAD if _cad_enabled. No existing behavior changes for non-JP nodes. Also recovered two orphaned commits (f2aad64, 4e09f6a) that were not on any branch: Airtime-scaled jitter (random(0, airtime/32)) instead of fixed 500ms Hardware tested: RAK WisMesh Tag (SX1262) + LilyGo T1000-E (LR1110), companion firmware, SF12/BW125/CR4/8 at 920.8 MHz. DM and group messages at maximum text length delivered successfully. |
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, Dispatcher.h):
getCADFailMaxDuration()toUINT32_MAXin Japan mode — prevents forced TX during LBT backoff (Dispatcher default is 4s, shorter than max backoff of 16s)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.
As of the current implementation (rebased onto upstream/dev which includes merged #1727), RSSI LBT and CAD operate independently. JP nodes run RSSI sensing first (ARIB requirement), then fall through to CAD if _cad_enabled.
Non-JP nodes use the existing _threshold and CAD path unchanged.
The commit history reflects the earlier exploration path before #1727 was merged.
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.