Skip to content

Worldwide LoRa Regulatory Survey

Research note for extending chIRpChat beyond US915. Not yet normative for core/ — no code in this repo implements the tables below. Once a Region table lands in core/include/lrc/presets.h (or a successor), this document becomes the spec it must match, and any deviation is a bug.

Scope: sub-GHz (and 2.4 GHz LoRa-capable-chip) ISM/SRD bands used by LoRa mesh systems. LoRaWAN-specific MAC parameters (ADR tables, CFList encoding, Class B beacon slots) are out of scope — chIRpChat is not LoRaWAN — but the underlying PHY-layer regulatory limits (frequency edges, EIRP, duty cycle, LBT, dwell time) apply regardless of MAC layer and are what this document captures.

Primary sources consulted directly (full URLs in Citations):

  • LoRa Alliance RP002-1.0.3 “LoRaWAN Regional Parameters” (FINAL, 2021-05-05), fetched and parsed in full — this is the most detailed public regional-parameters PDF found; RP002-1.0.4 and RP002-1.0.5 exist behind a registration wall on resources.lora-alliance.org and could not be fetched directly (see UNVERIFIED notes below). Where RP002-1.0.4/1.0.5 are known to have changed something (e.g. ChMaskCtrl fix for US915/AU915), it is noted but not incorporated, because it does not affect PHY-layer regulatory numbers.
  • ETSI EN 300 220-2 (V3.1.1 / V3.2.1 / V3.3.1) sub-band duty-cycle table.
  • Meshtastic firmware source, read directly from this repo’s reference/meshtastic/ checkout: src/mesh/RadioInterface.cpp (the regions[] table), src/mesh/MeshRadio.h (RegionInfo/RegionProfile structs), src/airtime.cpp/src/airtime.h (duty-cycle/channel-utilization enforcement).
  • FCC 47 CFR §15.247, ARIB STD-T108, Ofcom IR 2030, and country-specific regulator documents as cited inline.

Where sources disagree, the disagreement is written out rather than resolved by guessing — see the “Conflicts” callouts.


All frequencies in MHz unless noted; duty cycle “100%” means unlimited (no regulatory duty-cycle cap — dwell time and/or LBT may still apply). EIRP is the regulatory ceiling for typical class of use; real designs sit under this by antenna-gain/PA headroom (see §3).

Plan Freq range (MHz) Max EIRP/ERP Duty cycle LBT required? Dwell time Channel-plan notes
US915 (US902-928) 902.0–928.0 +30 dBm EIRP (FHSS); ≈+26 dBm conducted DTS; ≈+21 dBm conducted Hybrid — see §1.2 No regulatory limit (FCC Part 15.247 has no duty-cycle cap) No 400 ms per channel/hop dwell (FCC 15.247(f) frequency-hopping) on the 64×125 kHz lane group; the 8×500 kHz group has no dwell cap LoRaWAN fixed plan: 64 channels @125 kHz (902.3–914.9, +200 kHz) + 8 @500 kHz (903.0–914.2, +1.6 MHz) uplink; 8×500 kHz downlink (923.3–927.5, +600 kHz). Meshtastic treats the whole 902–928 MHz span as one region with no channel-plan enforcement.
AU915 (AU915-928) 915.0–928.0 +30 dBm No regulatory limit in RP002’s reading of AU/NZ SRD rules No 400 ms dwell “regional dependence” per RP002 §2.8 — Australia’s ACMA LIPD class license does not itself impose 400 ms; RP002 inherits the FCC-style dwell as a conservative default and lets TxParamSetupReq relax it Same 64+8 up / 8 down fixed-channel structure as US915, shifted to 915.2–927.8 (+200 kHz) / 915.9–927.1 (+1.6 MHz) / 923.3–927.5 (+600 kHz).
EU868 (EU863-870, LoRaWAN default) 868.1/868.3/868.5 (3 mandatory channels), extensible 863–870 +14 dBm ERP typ. (25 mW), +27 dBm ERP (500 mW) only in the 869.4–869.65 sub-band <1% default LoRaWAN 3-channel set; varies 0.1–10% by ETSI sub-band — see §1.1a No (duty-cycle is the LoRaWAN default; ETSI itself permits LBT+AFA as an alternative — not implemented by mainline LoRaWAN or by Meshtastic) None LoRaWAN’s “EU868” is a narrow slice of the much wider ETSI 863–870 MHz allocation; see the ETSI sub-band table below for the full regulatory picture LBT/duty-cycle enforcement must actually respect.
EU433 (EU433 ISM) 433.05–434.79 (LoRaWAN default channels 433.175/433.375/433.575) +12 dBm EIRP (≈16 mW), RP002 §2.7.2 <10% (RP002 explicit: “duty-cycle SHALL be lower than 10%”) No None ITU Region 1 ISM band; also used as fallback in countries with no 868/915 allocation (Meshtastic ANZ_433, UA_433, MY_433, PH_433, KZ_433 all derive from this same 433 MHz ISM allocation, each with country-specific power caps below 12 dBm — see §1.1b).
AS923-1 Base plan 915–928, default channels offset via AS923_FREQ_OFFSET to 923.2/923.4 +16 dBm EIRP default, up to per-country ceiling <1% in non-LBT sub-bands Yes in Japan (ARIB STD-T108 — see §1.3); optional elsewhere 400 ms mandatory uplink dwell until TxParamSetupReq relaxes it Frequency-offset mechanism lets one PHY spec cover many national sub-allocations (Japan, Brunei, Singapore, Hong Kong, Thailand-1, etc.) inside the nominal 915–928 window.
AS923-2 920–923 (Indonesia carve-out) +16 dBm default <1% No (Indonesia does not mandate LBT) 400 ms AS923_FREQ_OFFSET = −1.8 MHz from AS923-1 per RP002.
AS923-3 915–921 (Brunei/Cambodia/Laos-style carve-out; overlaps many AU915-adjacent countries per Table 1) +16 dBm default <1% No 400 ms AS923_FREQ_OFFSET = −6.6 MHz from AS923-1.
AS923-4 917–920 (Israel) +16 dBm default <1% UNVERIFIED whether Israel mandates LBT — sources describe the band as “opened for LoRaWAN” without repeating the LBT clause 400 ms Added in RP002-1.0.3 specifically for Israel. Conflict: some 2021-era secondary sources (LoRa Alliance blog) describe the pilot band as 915–917 MHz; the finalized commercial allocation is 917–920 MHz. Use 917–920.
KR920 (KR920-923) 920.9–923.3 (13 defined 125 kHz centers) +10 dBm end-device on 920.9–921.9 MHz sub-range, +14 dBm end-device on 922.1–923.3 MHz sub-range; +23 dBm gateway LoRaWAN spec exclusively uses LBT, not duty cycle, to comply with Korean regulation (RP002 §2.11.2: “the current LoRaWAN spec… exclusively uses LBT channel access”) Yes, mandatory None specified beyond LBT channel access Korean regulation permits either duty-cycle or LBT+AFA; LoRaWAN standardized on LBT only for this region.
IN865 (IN865-867) 865.0–867.0 (3 mandatory: 865.0625/865.4025/865.985) +30 dBm RP002: “no dwell time or duty-cycle limitation for the INDIA 865-867 PHY layer” — but see Conflict below No LBT requirement documented in RP002 None Conflict: RP002’s own Table 2 (§1.3.1) lists IN865’s “Duty Cycle” column value as “LBT” while §2.12.3 body text says there is no dwell/duty-cycle limit at all. Separately, some secondary sources (not the regulator) claim India’s WPC applies a 1% duty cycle “similar to ETSI.” The Indian DoT/WPC exemption notice (cited below) does not itself impose a numeric duty cycle. Treat IN865 duty cycle as UNVERIFIED against primary Indian regulatory text; RP002 body text (no limit) is the more authoritative of the two LoRa Alliance statements since section 2 is stated to override section 1 tables in case of conflict (RP002 §1.3, explicit rubric).
RU864 (RU864-870) 864.0–870.0, mandatory 868.9/869.1 +20 dBm (Meshtastic); 25 mW ERP ≈ +14 dBm cited for the 868.7–869.2 MHz sub-band by Russian GKRCh decision 18-46-03-1 — see Conflict 0.1% (3.6 s/hour) in the 868.7–869.2 MHz sub-band, or LBT as an explicit regulatory alternative Optional (alternative to the 0.1% duty cap) None specified in RP002 Conflict: Meshtastic’s own regions[] comment says “We do LBT, so 100% is allowed” and encodes RU at +20 dBm/100% duty, while the underlying GKRCh Annex 12 text (secondary-source paraphrase) states 25 mW ERP (~+14 dBm) and 0.1% duty or LBT for the 868.7–869.2 MHz sub-band specifically — the wider 864–868 MHz range may carry different limits. Full text of Annex 12 (Russian-language PDF) was not fetched; treat exact EIRP ceiling as UNVERIFIED.
CN470 (CN470-510) 470.0–510.0 (fixed 20/26 MHz-antenna channel plans, 32+32 channel groups) ≤50 mW ERP (≈+17 dBm) for LBT-compliant civil-metering devices; generic non-LBT SRD ceiling is 5 mW ERP No simple duty-cycle percentage — regulatory mechanism is LBT AFA + max 1 second single-channel transmission (RP002 §2.9.2: “transmission time shall not exceed one second and is limited to one channel at a time”) Yes, effectively mandatory (“LBT AFA… or other similar mechanisms like channel blacklisting”) 1 s max continuous transmission per channel RP002 marks this plan “experimental” as of 1.0.3, “heavily modified… not backward compatible” with pre-1.0 CN470; scoped to “small scale networks” (buildings/residential/villages) after Nov 2021; channels overlapping China Broadcasting Services must be disabled. Meshtastic’s CN region (470–510, 100% duty, 19 dBm) does not implement the LBT/1s/small-scale constraints RP002 documents — a known simplification worth flagging if chIRpChat ever targets China.
CN779 (CN779-787, legacy) 779.5–786.5 +12 dBm <1% No None RP002: devices “may not be produced, imported or installed after 2021-01-01”; deployed units may continue operating. Effectively deprecated — omit from a new implementation unless legacy-device support is required.
NZ865 (Meshtastic NZ_865) 864.0–868.0 +36 dBm No limit (Meshtastic 100%) No None New Zealand’s RSM 864–868 MHz allocation is notably permissive (36 dBm is the highest EIRP ceiling in this table). RP002 itself files NZ under IN865-867 (per Table 1’s country cross-reference — NZ has both AS923-1/AU915-928 at 915–928 and IN865-867 at 864–868). Meshtastic instead ships a bespoke NZ_865 profile at 36 dBm, i.e. Meshtastic diverges from the LoRaWAN regional-parameter mapping to better match the actual RSM ceiling.
ANZ (Meshtastic-only umbrella, “Australia + NZ + Brazil”) 915.0–928.0 +30 dBm 100% (no limit) No None Not an RP002 plan name; Meshtastic-specific umbrella for AU915-equivalent deployments including Brazil (902–907.5 in Brazil’s own carve-out, folded in loosely). See ANZ_433 below for the companion sub-GHz ISM slice.
ANZ_433 (Meshtastic-only) 433.05–434.79 25 mW EIRP (+14 dBm) 100% (no restriction per ACMA LIPD / NZ GURL) No None Australia ACMA “Low Interference Potential Devices” class license and New Zealand General User Radio Licence both permit unrestricted duty cycle at this power on 433 MHz.
UA433 / UA868 (Meshtastic-only Ukraine split) 433.0–434.7 / 868.0–868.6 +10 dBm (433) / +14 dBm (868) 10% (433) / 1% (868) No None Ukraine’s NKRZI decision splits the two ISM slices with different power and different duty-cycle ceilings — not a simple EU433/EU868 re-use, hence Meshtastic keeps them as distinct regions.
IL / Israel see AS923-4 UNVERIFIED Israel does not get a distinct plan; it is realized via AS923-4 (917–920 MHz). No separate “IL” RP002 plan exists.
MY_919 (Malaysia) 919.0–924.0 +27 dBm 100% below 923 MHz, but the 923–924 MHz sub-slice requires 1% duty cycle OR frequency hopping (Meshtastic comment) Only via the FHSS alternative in 923–924 None documented Malaysia MCMC Short-Range Devices spec splits 919–923 (unrestricted, 500 mW) from 923–924 (500 mW but 1% duty or hopping). Meshtastic’s single MY_919 region flags frequency_switching=true to capture this.
SG_923 (Singapore) 917.0–925.0 +20 dBm 100% No None IMDA “Band 30d” TSSRD spec; no duty-cycle restriction stated.
TH_923 (Thailand, Meshtastic TH) 920.0–925.0 +27 dBm 10% No None Meshtastic’s comment cites NBTC standard directly: 27 dBm + 10% duty, aligned to but distinct from AS923-1’s ARIB/LBT profile — Thailand does not require LBT despite sharing the 920–925 window with Japan’s AS923-1 carve-out.
PH_915 (Philippines) 915.0–918.0 +24 dBm EIRP, no external antenna permitted 100% No None NTC device-approval required; companion PH_433 (433–434.7, 10 dBm ERP) and PH_868 (868–869.4, 14 dBm ERP) cover the Philippines’ other two ISM slices.
JP (Japan, as modeled by Meshtastic) 920.5–923.5 +13 dBm Meshtastic encodes 100% (unlimited) Meshtastic does not model LBT for JP None modeled This is a known gap, not a regulatory fact: Japan’s actual ARIB STD-T108 rules require LBT (see §1.3) as the mechanism that makes 100% duty legal. Meshtastic’s flat 100%-duty/no-LBT JP region relies on the radio chip (e.g. certified SX126x + region-locked firmware) to perform LBT at a layer this table doesn’t reach — i.e. Meshtastic’s software region table alone is not sufficient evidence that JP transmissions are ARIB-compliant on uncertified hardware. Any chIRpChat implementation targeting Japan MUST implement LBT explicitly (§3), not copy Meshtastic’s numbers verbatim.

Numbers not independently re-derived above (taken as-is from RP002 or Meshtastic and flagged UNVERIFIED against a primary national-regulator text): RU864 GKRCh Annex 12 exact EIRP ceiling for the 864–868.7 MHz sub-range (only the 868.7–869.2 MHz slice was independently confirmed); AS923-4 Israeli LBT requirement; India WPC numeric duty-cycle (if any) beyond the “license-exempt, no explicit duty limit” characterization.

1.1a EU863-870 ETSI sub-band duty-cycle breakdown

Section titled “1.1a EU863-870 ETSI sub-band duty-cycle breakdown”

RP002’s “EU868” plan is only the LoRaWAN-default 3-channel slice (868.1/868.3/868.5, <1%) plus the 869.4–869.65 MHz “g3” high-power channel Meshtastic and most non-LoRaWAN mesh firmwares actually use for its 27 dBm ceiling. The full ETSI EN 300 220-2 863–870 MHz allocation that any EU-targeting firmware must gate against is sub-divided as follows (letter labels vary by source revision — both letter schemes are shown since ETSI EN 300 220-2 V3.2.1/V3.3.1 relabeled the sub-bands between versions):

Sub-band (V3.2.1 label) Alt. label seen in some secondary sources Freq range (MHz) Max ERP Duty cycle Notes
g K/L combined region 863.0–868.0 25 mW (+14 dBm ERP) 1% (868.0 lower edge sometimes split at 865.0 as a separate 0.1% “h” sub-band in older ETSI revisions — see Conflict) Widest slice, general SRD use
g1 M 868.0–868.6 25 mW (+14 dBm ERP) 1% Classic “868 MHz ISM” — the slice most non-LoRaWAN sub-GHz radios historically default to
g2 N 868.7–869.2 25 mW (+14 dBm ERP) 0.1% Also used by alarms; RU864’s mandatory channels (868.9/869.1) sit inside this ETSI-equivalent slice, though Russia is not ETSI-governed
g3 P 869.4–869.65 500 mW (+27 dBm ERP) 10% The high-power “network relay / social alarm” channel — this is the slice EU868 LoRa mesh networks (Meshtastic, MeshCore, LoRaWAN’s optional high-power mode) actually use for range, at 10% duty in exchange for 27 dBm
g4 Q 869.7–870.0 25 mW (+14 dBm ERP) 1%

Conflict: one 2026 web summary reported six labeled sub-bands (K/L/M/N/P/Q, 863–865 MHz split out as its own 0.1% “K” band) while the ETSI EN 300 220-2 V3.2.1 g/g1/g2/g3/g4 scheme (as cited by Meshtastic’s own source comment pointing at “EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21]”) folds 863–868 MHz into one 1% “g” band without an internal 0.1% split at 865 MHz. The two schemes may reflect different ETSI revisions (the K/L/M/N/P/Q letters appear associated with older or draft tables) — use the g/g1–g4 scheme as authoritative since it is the one Meshtastic’s firmware comment directly cites against the 3.2.1 revision, but note the source PDF itself (en_30022002v030201p.pdf) could not be parsed as text by the tools used in this research pass (binary/FlateDecode PDF, no OCR attempted) — the exact table should be re-verified by a human against the ETSI PDF directly before being hard-coded, particularly the 863.0 MHz lower edge and whether an intermediate 0.1% split exists.

ETSI additionally permits LBT + Adaptive Frequency Agility (LBT+AFA) as a full substitute for duty-cycle limiting in any of these sub-bands — this is the “polite spectrum access” alternative mainline LoRaWAN does not use but that a from-scratch mesh firmware is free to implement instead of (or in addition to) the token-bucket duty limiter.

1.1b 433 MHz ISM family (non-EU433-LoRaWAN slices)

Section titled “1.1b 433 MHz ISM family (non-EU433-LoRaWAN slices)”

Distinct national/regional carve-outs of the 433.05–434.79 MHz ITU Region 1 ISM band, each with its own power ceiling — do not assume EU433’s 12 dBm applies uniformly:

Region Freq (MHz) Max power Duty cycle Source
EU433 (ETSI, RP002) 433.05–434.79 +12 dBm EIRP <10% RP002 §2.7.2
ANZ_433 (AU/NZ) 433.05–434.79 +14 dBm EIRP (25 mW) 100% ACMA LIPD / NZ GURL, via Meshtastic comment
UA_433 (Ukraine) 433.0–434.7 +10 dBm 10% NKRZI, via Meshtastic comment
MY_433 (Malaysia) 433–435 +20 dBm (100 mW) 100% MCMC SRD spec
PH_433 (Philippines) 433–434.7 +10 dBm ERP 100%, NTC device approval required Meshtastic comment
KZ_433 (Kazakhstan) 433.075–434.775 +10 dBm EIRP 100% Meshtastic comment

The authoritative source is RP002-1.0.3 §1.2 “Country Cross Reference Table” (a 190+ country table). Reproducing it in full here would duplicate ~700 lines of primary-source text; instead this section gives (a) the general mapping rule, (b) every country/territory called out with more than one applicable band (multi-band overlays), and (c) the specific “unusual overlay” cases the task calls out.

2.1 General mapping (single-band countries — representative sample)

Section titled “2.1 General mapping (single-band countries — representative sample)”

Most countries map to exactly one plan based on their national SRD/ISM allocation:

  • US902-928 (US915): United States, Canada, Mexico, Bermuda, Guam, Puerto Rico, US Virgin Islands, Bahamas, Northern Mariana Islands, American Samoa (also has AU915 available), United States Minor Outlying Islands.
  • EU863-870 (EU868): essentially all of the EU/EEA plus UK, most of the Balkans, Scandinavia+Svalbard, Andorra, most Middle-East/North-Africa states that adopted ETSI-aligned rules (Israel is the notable exception — see below), most Sub-Saharan African states, most former- Soviet states outside Russia (Armenia, Azerbaijan splits 433/868, Georgia, Moldova, Ukraine — though Ukraine has its own split power profile per §1.1b).
  • EU433: near-universal fallback/companion band — almost every country in the EU863-870 list also lists 433.05–434.79 MHz EU433 as available, since the 433 MHz ISM allocation is close to globally harmonized at ITU Region 1/3 level.
  • AU915-928: Australia, and (via the “915–928 MHz available, though regulation nominally implies 902–928” footnote in RP002 Table 1) most of Latin America — Argentina, Bolivia, Brazil (also has its own 902–907.5 slice), Chile, Colombia, Ecuador, Guatemala, Honduras, Jamaica, Panama, Paraguay, Peru, Suriname, Uruguay — plus most Caribbean nations (Anguilla, Barbados, Belize, Dominica, Grenada, Saint Kitts and Nevis, Saint Lucia, Trinidad and Tobago) via the footnoted “AU915-928 also applies to this [US902-928] band” relationship.
  • AS923-1: Japan (+ mandatory LBT overlay), Brunei, Cambodia, Hong Kong (nominally IN865-867 but also AS923-1 listed), Macao, Malaysia, New Caledonia, Singapore, Sri Lanka, Taiwan, Thailand, Vanuatu (via AS923-3), Venezuela.
  • AS923-2: Indonesia, Vietnam (also has its own 918–923/920–922.5 MHz in-flux allocation flagged as “regulations in flux” by RP002 footnote).
  • AS923-3: a long tail of countries whose 915–921 MHz slice is explicitly cross-referenced to AS923-3 in RP002 Table 1 — Albania, Algeria, Bulgaria (868), Cuba, Denmark (915–918, secondary to its primary EU868), most EU868 countries in fact also list a 915–918 MHz AS923-3 entry (Austria excepted in the extract above — actually most EU states show both EU863-870 and 915-918 AS923-3 rows in the table, meaning the two bands coexist in-country as separate legal allocations), Iran, Ireland, Jordan, Kuwait, Philippines, Qatar, Russia (as a licensed 916–921 MHz overlay, distinct from RU864’s unlicensed 864–870 MHz slice), Saudi Arabia, UAE, UK.
  • AS923-4: Israel only.
  • KR920-923: Republic of Korea only.
  • IN865-867: India, and (per RP002’s country table) Cook Islands, Egypt, Hong Kong, Jordan (also AS923-3), Niger, Niue, Pakistan, Tokelau, Uganda, Vanuatu.
  • RU864-870: Russian Federation only (licensed 916–921 MHz AS923-3-style overlay also listed for Russia — meaning Russia has both an unlicensed 864–870 MHz slice and a licensed 916–921 MHz slice, per Table 1’s own entry for “Russian Federation (RU)”).
  • CN470-510 / CN779-787 (legacy): China only, alongside China-specific 314–316, 430–432, 840–845 MHz allocations RP002 lists but does not detail (out of scope — not LoRa-relevant sub-GHz ISM in the usual sense).

2.2 Multi-band / unusual-overlay countries called out explicitly

Section titled “2.2 Multi-band / unusual-overlay countries called out explicitly”
  • Japan (JP): single band (920.6–928.0 MHz, stepped 200/600 kHz, mapped to AS923-1) but with a mandatory ARIB STD-T108 LBT overlay not present for any other AS923-1 country — see §1.1 JP row and §1.3. This is the single most consequential overlay in the whole table for firmware design: the frequency plan is shared with e.g. Thailand or Singapore, but the channel-access rule is completely different (LBT vs plain duty-cycle-free or duty-cycled access).
  • United Kingdom (GB): listed by RP002 simply as EU863-870 (863–873 MHz)
    • AS923-3 (915–918 MHz), same as most EU states — RP002 does not encode UK-specific Ofcom detail. However Ofcom’s own IR 2030 document (the UK’s domestic implementation of the ETSI SRD rules, now diverging post-Brexit from EU harmonized standards on its own update cadence) is the actual controlling text for UK deployments and specifies, for the 868 MHz non-specific SRD band: 25 mW e.r.p. with duty cycle ≤10% (network access points) or ≤2.5% (other devices) as the default access technique, with an alternative ≤1% duty-cycle-only option, and a separate ≤0.1% alternative for the 868.7–869.2 MHz sub-band. These numbers are consistent with (a slight variant of) the ETSI g/g1/g2 duty cycle values in §1.1a — the UK is not a numeric outlier post-Brexit, but firmware SHOULD treat “GB” as its own row sourced from IR 2030 directly rather than silently assuming “EU868 == UK868”, since Ofcom’s document is the enforceable instrument in UK law, and future IR 2030 revisions could diverge further from ETSI EN 300 220 (a draft 2025-12-11 IR 2030 revision exists — see Citations — implying the UK document is actively being updated independent of ETSI’s schedule).
  • Russia (RU): two separate legal regimes in the same country — an unlicensed 864–870 MHz allocation (RU864-870, the plan in §1.1) and a licensed 916–921 MHz AS923-3-style band (per RP002 Table 1’s own “Russian Federation (RU)” row, third line: “916 - 921 MHz (Licensed) AS923-3”). A region table that only encodes RU864 misses the fact that Russian operators with a license may legally use a completely different band and duty-cycle regime. Additionally the unlicensed RU864 968.7–869.2 MHz sub-slice’s exact EIRP figure is a Conflict flagged in §1.1 (20 dBm per Meshtastic vs ~14 dBm/25mW per the secondary GKRCh paraphrase) — treat as UNVERIFIED pending the primary Annex 12 PDF.
  • Israel (IL): has no distinct RP002 plan name; realized entirely through AS923-4. Historically significant because the pilot-phase allocation (915–917 MHz, some sources say 915–917.9 MHz) is different from the finalized commercial allocation (917–920 MHz) — any hardware built to the pilot numbers is out of compliance with the current regulation. A country→region table entry for Israel must point at the current 917–920 MHz AS923-4 definition, not the historical pilot band.
  • New Zealand (NZ): per RP002 Table 1, NZ has three simultaneously available allocations — AS923-1 and AU915-928 (both 915–928 MHz) plus IN865-867 (864–868 MHz, listed as “864 868 MHz” in the extracted table, almost certainly a typo for “864–868 MHz”). Meshtastic instead papers over this with a bespoke NZ_865 region at a distinct 36 dBm EIRP ceiling not matching any RP002 plan number, because RSM’s actual New Zealand allocation is more permissive than the LoRaWAN-standardized numbers for any of the three overlapping RP002 plans.
  • Thailand (TH): shares Japan’s/AS923-1’s 920–925 MHz nominal window but with no LBT requirement (10% duty cycle instead, per NBTC — see Meshtastic’s own source comment). A firmware author copying “Japan’s frequency table” for Thailand and assuming the same access rule would be non-compliant in one direction or the other.
  • Brazil (BR): the only country in the table with a bespoke 902–907.5 MHz slice (1 W / +30 dBm, no duty-cycle restriction) in addition to its general AU915-928 eligibility (915–928 MHz) — Brazil’s ANATEL carved out additional low-end spectrum not available to other AU915-928 countries.
  • South Africa (ZA): uses the full ETSI-style four-sub-band split (865–868.6 / 868.7–869.2 / 869.4–869.65 / 869.7–870, each separately cross-referenced to EU863-870 in RP002 Table 1) rather than a single merged 863–870 entry — functionally equivalent to full ETSI EN 300 220-2 sub-band adoption even though South Africa is not in ETSI’s jurisdiction.

3. Enforcement primitives a mesh firmware must implement

Section titled “3. Enforcement primitives a mesh firmware must implement”

Ranked by how many regions make each one a hard legal requirement vs. a best-practice/interoperability nicety.

3.1 Duty-cycle token bucket per sub-band — REQUIRED in most regions

Section titled “3.1 Duty-cycle token bucket per sub-band — REQUIRED in most regions”

Every ETSI-governed region (EU868/EU433/UA868/UA433 and their many country-specific derivatives) and several non-ETSI regions (CN779 legacy, AS923-1/-2/-3/-4 default channels, RU864’s 868.7–869.2 MHz sub-slice) make this a legal requirement, not a courtesy. It must be:

  • Per sub-band, not per device or per channel: ETSI’s g/g1/g2/g3/g4 split means a single antenna can be legally in-budget on 869.4–869.65 MHz (10%) while simultaneously over-budget on 868.0–868.6 MHz (1%) — a single global duty counter is non-compliant the moment a firmware uses more than one sub-band.
  • A rolling window, not a fixed-epoch counter: naive “reset every wall-clock hour” counters allow a burst at 23:59/00:01 to exceed the true 1-hour rolling duty cycle. Meshtastic’s AirTime class (reference/meshtastic/src/airtime.cpp) implements this as a 60-slot, 1-minute-bucket rolling array (utilizationTX[MINUTES_IN_HOUR]), summed for utilizationTXPercent() — effectively a rolling-hour sliding window at 1-minute granularity. chIRpChat’s existing lrc::AirtimeLedger (core/include/lrc/airtime.h, referenced from docs/RADIO.md “Air discipline summary”) is already this shape — a sliding-window budget consulted via admit()/charge() before every TX — and is the right primitive to extend per-sub-band rather than replace.
  • Charged on actual TX, not on attempt: airtime is sf/bw/cr-derived from the LoRa symbol-time formula, not a flat per-packet cost — a SF12/BW125 packet costs vastly more duty-cycle budget than the same payload at SF7/BW500. Both Meshtastic (getPacketTime) and chIRpChat’s existing airtime estimate (core/include/lrc/presets.h “LoRa airtime estimate (ms) for bytes at a preset”) already do this correctly and should feed the same computed airtime into both the duty-cycle ledger and the dwell-time cap (§3.2), so observed and enforced numbers cannot drift apart — matching the existing invariant chIRpChat’s TESTING.md / RADIO.md already commit to (“Every TX decision consults the airtime ledger… the same numbers users see”).
  • Compliance mode SHOULD be legally-required-only, not best-practice padding: Meshtastic additionally applies a polite_duty_cycle_percent (default 50%) of the regulatory duty cycle as a self-imposed courtesy ceiling, and a separate polite_channel_util_percent (25%) / max_channel_util_percent (40%) channel-busy gate that is not a regulatory requirement anywhere — it exists purely so a single busy node doesn’t starve the shared channel. A chIRpChat implementation should keep the legal duty-cycle number and the politeness multiplier as two clearly separate constants, so the legal one can never accidentally be loosened by a “just being nice” tuning change.

3.2 Dwell-time cap per transmission — REQUIRED in US915/AU915 (FCC-style) and AS923 family

Section titled “3.2 Dwell-time cap per transmission — REQUIRED in US915/AU915 (FCC-style) and AS923 family”
  • US915/AU915: FCC 47 CFR §15.247(f) frequency-hopping mode caps average per-channel occupancy at 400 ms within a 20 s window (for hop channels with 20 dB bandwidth <250 kHz — the 64×125 kHz lane group) or 400 ms within a 10 s window (≥250 kHz channels, i.e. the 8×500 kHz group uses a shorter window even though RP002’s own summary table claims “no dwell time” for the 500 kHz group — this is worth double-checking against the primary CFR text before hard-coding “no cap” for the fast lanes; RP002 Table 3 says “[64:71] No” but this reads as “the FCC dwell obligation is satisfied structurally by the DTS mode’s power-spectral-density limit instead,” not “there is no cap of any kind”). Enforcement primitive: hard per-TX ceiling on airtime, independent of and in addition to the duty-cycle ledger — a single packet that would take longer than 400 ms to transmit at the chosen SF/BW must be rejected or fragmented before it ever reaches the radio, regardless of how much duty-cycle budget remains.
  • AS923-1/-2/-3/-4: identical 400 ms mandatory dwell (RP002 explicit: “Dwell time limitation: Yes (400ms)” in the dynamic-plan summary table), enforced the same way — this is a legal cap, and TxParamSetupReq (a LoRaWAN MAC command chIRpChat has no equivalent of) is how a LoRaWAN network server can relax it once it confirms local regulation permits longer transmissions; without an equivalent signaling mechanism, chIRpChat should treat 400 ms as the permanent ceiling for any AS923-family region rather than a negotiable default.
  • All other regions in this survey (EU868/EU433/CN779/IN865/KR920/RU864) are explicitly “no dwell time limitation” per RP002 body text — dwell capping is a US915/AU915/AS923-family-specific primitive, not universal.

3.3 LBT / CAD-before-TX — REQUIRED in Japan, Korea, China; optional-alternative elsewhere

Section titled “3.3 LBT / CAD-before-TX — REQUIRED in Japan, Korea, China; optional-alternative elsewhere”
  • Japan (via AS923-1): ARIB STD-T108 mandates Listen-Before-Talk with precise, verifiable parameters (Silicon Labs AN647, cross-checked against multiple ARIB-standard secondary sources): carrier-sense duration ≥128 µs, carrier-sense threshold −80 dBm at the antenna input (do not transmit if received power at any channel to be used is ≥−80 dBm), and a maximum continuous transmission time <4 s for center frequencies 920.6–922.2 MHz specifically (other ARIB sub-bands may have different continuous-TX caps — not independently verified for this survey; UNVERIFIED beyond the 920.6–922.2 MHz figure). Devices that can show ≤1% duty cycle (≤36 s/hour) may be exempted from LBT per the “conditions for LBT omission” clause found in secondary ARIB guidance — i.e. Japan actually offers the same “duty-cycle vs LBT” choice ETSI does, just with a much stricter fallback duty-cycle number (1%, not 10%) than ETSI’s high-power sub-band.
  • Korea (KR920): LoRaWAN’s regional-parameters spec states plainly that it “exclusively uses LBT channel access” for this region rather than duty-cycling, “to maximize MACPayload size length and comply with the South Korea regulations” (RP002 §2.11.2) — Korean regulation itself permits either mechanism, but any interoperable implementation should assume LBT is the expected/only mechanism actually deployed for KR920. Precise KR920 carrier-sense timing parameters (equivalent to ARIB’s 128 µs/−80 dBm) were not found in the sources consulted for this survey — UNVERIFIED, needs the Korean MSIT/RRA regulation text directly.
  • China (CN470): LBT AFA (or an equivalent channel-blacklisting scheme) plus a hard 1-second max transmission per channel is the regulatory mechanism (RP002 §2.9.2) — there is no duty-cycle-only fallback documented for CN470 the way there is for Japan/Russia/ETSI.
  • Europe (ETSI-governed regions, all): LBT+AFA (“polite spectrum access”) is an explicit legal alternative to duty-cycle limiting (ETSI EN 300 220-2, cited by both RP002 and Meshtastic’s own EU868 source comment) but is not required — duty-cycle-only compliance remains fully legal, which is why mainline LoRaWAN and Meshtastic both skip implementing LBT for EU868/EU433 entirely.
  • US915/AU915: no LBT requirement of any kind; FCC’s frequency-hopping and duty-cycle-free model is the entire compliance story.
  • CAD (Channel Activity Detection, a LoRa-chip hardware primitive distinct from a regulatory “LBT” carrier-sense-and-threshold procedure) is what chIRpChat’s docs/RADIO.md “Air discipline summary” already documents as a universal collision-avoidance mechanism (“CAD before every TX, randomized backoff on busy (120–480 ms)”) — this is good practice everywhere and happens to satisfy Japan/Korea/China’s LBT mechanism if its timing/threshold parameters are tightened to match ARIB’s specific 128 µs / −80 dBm numbers for JP, and a to-be-researched KR-specific figure for KR920. The gap: chIRpChat’s current CAD-before-TX is tuned for collision avoidance, not proven to meet ARIB’s exact carrier-sense threshold — closing that gap (or documenting that the region gate simply refuses to enable JP/KR920/CN470 until it does) is the concrete design implication here.

3.4 EIRP caps including antenna-gain accounting — REQUIRED everywhere, mechanically different by unit

Section titled “3.4 EIRP caps including antenna-gain accounting — REQUIRED everywhere, mechanically different by unit”
  • Every region in §1.1 states its ceiling as either EIRP (isotropic reference) or ERP (half-wave-dipole reference); ERP = EIRP − 2.15 dB (RP002 §2.4.3 footnote, and consistent with standard antenna-theory conversion). A region table that mixes EIRP-denominated regions (US915, AU915, most of AS923, KR920, IN865, NZ865) with ERP-denominated regions (most of the ETSI EU868 sub-bands, as ETSI’s “25 mW ERP” phrasing shows) without converting to a single common unit internally will silently under- or over-report by ~2 dB depending on which way the mismatch goes — worth a single enum class PowerUnit { Eirp, Erp } tag per row rather than assuming one unit throughout.
  • Conducted power ≠ EIRP: firmware only controls conducted (PA output) power; EIRP is conducted power plus antenna gain minus cable/connector loss. RP002’s own US915 text makes the antenna-gain adjustment explicit: “devices which use an antenna system with a directional gain greater than +6 dBi [SHALL] reduce the specified conducted output power by the amount in dB of directional gain over +6 dBi” — i.e. the regulatory ceiling is on radiated, not conducted, power, and a device with a high-gain antenna must clamp its conducted TX power down to stay under the same EIRP ceiling a stock-antenna device reaches at full conducted power. A correct enforcement primitive needs an antenna-gain field (dBi) as a per-node config value, feeding a conducted_power_cap = eirp_ceiling - antenna_gain_dbi + cable_loss_dbi computation — not a flat “set the chip’s PA register to X” constant. This is a REQUIRED primitive, not best-practice, anywhere a removable or user-selectable antenna is supported (which, per docs/HARDWARE.md variant list, chIRpChat already does across its XIAO/Heltec/RAK targets).
  • KR920’s two-tier EIRP-by-sub-band (10 dBm on 920.9–921.9, 14 dBm on 922.1–923.3) is the sharpest example of a region where the cap is not even uniform across the region’s own channel list — a single max_dbm field per BandPlan row (as chIRpChat’s current core/include/lrc/presets.h::BandPlan struct has) is insufficient for KR920 without a sub-band breakdown analogous to what EU868 already needs for its g/g1–g4 split.

3.5 Summary: legally required vs. best practice, by primitive

Section titled “3.5 Summary: legally required vs. best practice, by primitive”
Primitive Legally required in Best-practice-only in
Duty-cycle token bucket (per sub-band) EU868, EU433, UA433/UA868, CN779 (legacy), AS923-1/-2/-3/-4 default channels, RU864 (868.7–869.2 sub-band, or LBT alternative), TH_923, MY_919 (923–924 sub-slice) US915, AU915, IN865 (per RP002 body text), KR920 (LBT-only region), NZ865, ANZ, SG_923, PH_915
Dwell-time cap (400 ms/1 s) US915/AU915 (FCC 15.247 FHSS, 400 ms), AS923-1/-2/-3/-4 (400 ms), CN470 (1 s, via LBT rule not dwell rule per se) Everywhere else
LBT / carrier sense Japan (via AS923-1, ARIB STD-T108), Korea (KR920), China (CN470) EU868/EU433 (legal alternative to duty cycle, not required), everywhere else as collision-avoidance CAD (good practice, not regulatory)
EIRP cap + antenna-gain accounting Every region, universally N/A — always required; the only variance is EIRP vs ERP unit and whether the cap is uniform or sub-band-split

4.1 Recommendation for chIRpChat (C++17, no heap on radio path)

Section titled “4.1 Recommendation for chIRpChat (C++17, no heap on radio path)”

chIRpChat already has the right shape for the simple case in core/include/lrc/presets.h:

struct BandPlan {
const char* name;
uint32_t start_khz;
uint32_t end_khz;
uint8_t max_dbm;
uint8_t duty_pct; // regulatory duty cycle (100 = unlimited)
};

This is sufficient for single-sub-band regions (US915, AU915, KR-as-flat, NZ865, ANZ, SG_923, TH_923, PH_915, MY_433, UA_433) but cannot express: regions with more than one sub-band at different duty cycles/power caps in the same allocation (EU868’s g/g1–g4 split, KR920’s two-tier EIRP, RU864’s “0.1% or LBT” alternative, MY_919’s split 919–923-unrestricted / 923–924-restricted); dwell-time caps (US915/AU915/AS923 400 ms, CN470 1 s); or LBT parameters (Japan’s 128 µs/−80 dBm/4 s).

Recommended extension, staying within the existing “flat constexpr array, no heap, no virtual dispatch” style already used by kPresets and kBands:

namespace lrc {
// One contiguous sub-band inside a region's overall spectrum allocation.
// Most regions have exactly one; EU868/RU864/KR920/MY_919 need several.
struct SubBand {
uint32_t start_khz;
uint32_t end_khz;
uint8_t max_dbm; // EIRP by convention; see eirp_is_erp below
uint8_t duty_pct; // 100 = unlimited; regulatory (legal) cap only
bool eirp_is_erp; // true => max_dbm is ERP; caller applies -2.15dB… wait,
// ERP = EIRP - 2.15dB, so an ERP-denominated cap is
// ALREADY the lower number; store as given, tag the unit
};
// LBT parameters. All-zero means "no LBT requirement" (the common case).
struct LbtParams {
uint16_t cs_time_us; // minimum carrier-sense duration before TX, e.g. 128 (JP)
int8_t cs_threshold_dbm;// do-not-transmit-above threshold, e.g. -80 (JP)
uint16_t max_tx_ms; // hard cap on one continuous transmission, e.g. 4000 (JP), 1000 (CN470)
};
struct RegionPlan {
const char* name; // "EU868", "US915", "KR920", ...
const SubBand* sub_bands; // pointer into a static constexpr array
uint8_t sub_band_count;
uint16_t dwell_ms; // 0 = no cap; 400 for US915/AU915/AS923 fast lanes
LbtParams lbt; // zeroed if not required
bool duty_or_lbt_alternative; // true for RU864/EU-style "either mechanism is legal"
};
// Example: EU868 as 5 ETSI sub-bands (see doc §1.1a; VERIFY against ETSI PDF
// directly before shipping — this table is transcribed from a secondary
// source, not OCR'd from the primary ETSI EN 300 220-2 document).
constexpr SubBand kEu868SubBands[] = {
{863'000, 868'000, 14, 1, /*erp*/true}, // g
{868'000, 868'600, 14, 1, /*erp*/true}, // g1
{868'700, 869'200, 14, 0 /*0.1% - needs finer than uint8 pct, see below*/, true}, // g2
{869'400, 869'650, 27, 10, /*erp*/true}, // g3 - the one chIRpChat actually TXs on today
{869'700, 870'000, 14, 1, /*erp*/true}, // g4
};
constexpr RegionPlan kEu868 = {"EU868", kEu868SubBands, 5, 0, {}, false};

Two representational problems surface immediately and are worth deciding explicitly rather than discovering mid-implementation:

  1. 0.1% duty cycle does not fit in a uint8_t percent. g2/KR920’s finer-grained bands need either a uint16_t in units of 0.01% (so 0.1% = 10, 1% = 100, 10% = 1000), or a separate duty_ppm (parts-per-million) field. Given chIRpChat’s existing AirtimeLedger::capacity_ms() computes (window_ms * duty_pct) / 100, switching the unit to basis-points (1/100 of a percent, uint16_t, range 0–10000) is a minimal, mechanical change: capacity_ms = (window_ms * duty_bp) / 10000. This preserves integer-only arithmetic (no float on the radio path, consistent with the “no hidden alloc on the radio path” / portable-core/ constraint in this repo’s AGENTS.md).
  2. sub_bands as a raw pointer into a separate constexpr array keeps RegionPlan itself small and sizeof-stable for a constexpr RegionPlan kRegions[] master table, matching the existing kPresets/kBands pattern — no std::vector, no heap, and each region’s sub-band array can be sized exactly (1 entry for US915, 5 for EU868, 2 for KR920, etc.) without wasting space on a fixed max-sub-bands array. This is the same pattern Meshtastic uses for presets inside RegionProfile (const meshtastic_Config_LoRaConfig_ModemPreset *presets, sentinel-terminated) and for regions[] itself (a flat constexpr-style const RegionInfo regions[] walked linearly by getRegion()).

A Region selector at TX time becomes: pick RegionPlan (a config-time choice, likely persisted like today’s BandPlan choice), then at TX time find which SubBand the chosen frequency falls in (linear scan over ≤5 entries — no hash table needed at this scale), then check AirtimeLedger per sub-band index (extend the ledger to be keyed by (lane_index, sub_band_index) rather than just lane_index), then check dwell_ms against the packet’s precomputed airtime, then — only for regions with non-zero LbtParams — run a CAD/RSSI-threshold check with cs_time_us/cs_threshold_dbm before releasing the TX gate, and finally cap continuing transmission at lbt.max_tx_ms (relevant mainly for CN470’s 1 s cap and JP’s 4 s cap, not for typical chIRpChat packet sizes which are almost certainly under both thresholds already — worth confirming with an explicit unit test once implemented, per this repo’s lrc-add-test skill guidance).

4.2 How Meshtastic encodes theirs, for comparison

Section titled “4.2 How Meshtastic encodes theirs, for comparison”

Meshtastic’s actual on-device representation (reference/meshtastic/src/mesh/MeshRadio.h + RadioInterface.cpp, read directly in this repo’s reference/ checkout) is flatter than the recommendation above, and — as documented throughout §1 and §3 — this flatness is exactly why it cannot express sub-band duty-cycle splits or LBT parameters:

struct RegionInfo {
meshtastic_Config_LoRaConfig_RegionCode code;
float freqStart;
float freqEnd;
float dutyCycle; // single float, one number for the WHOLE region
uint8_t powerLimit; // single number, one EIRP ceiling for the WHOLE region
bool freqSwitching;
bool wideLora;
const RegionProfile *profile; // shared preset-list + spacing/padding,
// NOT a duty-cycle or LBT payload
meshtastic_Config_LoRaConfig_ModemPreset defaultPreset;
int16_t overrideSlot;
const char *name;
};
extern const RegionInfo regions[]; // flat C array, linear-scanned by code in getRegion()

Consequences of this flatness, all directly observed in the source:

  • EU868 vs EU_866 are two separate RegionInfo rows (not one row with two sub-bands) — Meshtastic works around the lack of a sub-band array by duplicating the entire region entry once per ETSI sub-band it cares about (EU_868 at 869.4–869.65/10%/27dBm, EU_866 at 865.6–867.6/2.5%/27dBm, EU_N_868 a third narrowband variant of the same 869.4–869.65 slice). This is a workable pattern at Meshtastic’s current scale (3 EU entries) but scales linearly with sub-band count and duplicates freqStart/freqEnd-adjacent logic across rows rather than nesting it — the SubBand-array approach in §4.1 avoids this duplication.
  • No LBT fields at all — confirmed by grep across RadioInterface.h/.cpp and MeshRadio.h: no cs_time, no threshold, no lbt identifiers anywhere in the region-table code. Japan’s JP region is just another flat 100, 13 (dutyCycle=100, powerLimit=13) row with a comment citing ARIB but no ARIB parameters encoded — this independently confirms the gap flagged in the JP row of §1.1 is real and not an artifact of this survey’s source selection.
  • getEffectiveDutyCycle() is the one place Meshtastic does do role-conditional logic (EU_866: 10% for router-role devices, 2.5% for everyone else) — this lives as an if on config.device.role in airtime.cpp, not as data in the region table itself; i.e. Meshtastic’s region table is data-only for the region, and per-node-role modifiers are bolted on separately at the call site. This is a reasonable split chIRpChat’s AirtimePolicy (which already separates the regional cap from the ledger/policy object per docs/RADIO.md) can keep doing rather than pushing role logic into the region table itself.
  • Enforcement is entirely separate from the table: AirTime (airtime.h/.cpp) is a completely independent class from RegionInfo — the region table supplies dutyCycle/powerLimit as plain data, and AirTime::isTxAllowedAirUtil() / isTxAllowedChannelUtil() do the actual gating using those numbers plus its own rolling-minute-bucket state. This mirrors chIRpChat’s existing split between presets.h (data) and airtime.h’s AirtimeLedger (enforcement) almost exactly — the recommendation in §4.1 is additive to that existing split, not a redesign of it.

Grouped by topic. All URLs verified reachable during this research pass (2026-07-01) except where noted.

LoRa Alliance / RP002 (primary regional-parameters source)

ETSI (Europe)

FCC (United States)

ARIB STD-T108 (Japan)

Ofcom / UK

Russia

Israel

India

China

  • SRRC / civil-metering LBT + 1s + 50mW ERP characterization (secondary source; cross-checked against RP002 §2.9.2’s own “transmission time shall not exceed one second… LBT AFA” text, which is treated as the more authoritative of the two since it is the LoRa Alliance’s own regulatory-compliance section): https://ib-lenhardt.com/kb/srrc-requirements

Meshtastic (primary source for on-device representation comparison, all read directly from this repo’s local checkout, not fetched over the network)

  • reference/meshtastic/src/mesh/RadioInterface.cpp — the regions[] table (lines 73–294) and getEffectiveDutyCycle() (line 609)
  • reference/meshtastic/src/mesh/MeshRadio.hRegionProfile (line 24) and RegionInfo (line 51) struct definitions
  • reference/meshtastic/src/airtime.h / src/airtime.cppAirTime class, isTxAllowedChannelUtil()/isTxAllowedAirUtil(), CWmin/CWmax/max_channel_util_percent/polite_channel_util_percent/ polite_duty_cycle_percent constants
  • Meshtastic docs, “LoRa Configuration” (public region-table summary, cross-checked against the source read above and found consistent): https://meshtastic.org/docs/configuration/radio/lora/

chIRpChat internal references (this repo, for context — not external citations, listed for completeness since this document’s recommendations build directly on them)

  • core/include/lrc/presets.h — existing BandPlan/Preset structs
  • core/include/lrc/airtime.h — existing AirtimeLedger/AirtimePolicy
  • docs/RADIO.md — “Air discipline summary” section describing the current CAD/backoff/duty-cycle-ledger design this survey extends