All checks were successful
Demo images / Build & push demo images (push) Successful in 2m47s
The RF section shipped as four bare fields; restore a prepopulated plan list (dropdown) with Custom unlocking free entry, per operator direction. Preset labels carry the full values, so the number fields render only in Custom mode — no duplicated display. Presets limited to verifiable sources: the repo's MeshCore community plans (EU 868/433), the MeshCore firmware's own build defaults (platformio.ini arduino_base 869.618/62.5/ SF8 CR5; companion example 915.0/250/SF10/CR5), and the operator-attested Portugal plan (869.618/62.5/SF8/CR8). Stored params are matched back to a named preset on load; hand-editing flips the dropdown to Custom. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
129 lines
6.9 KiB
TypeScript
129 lines
6.9 KiB
TypeScript
/**
|
||
* LoRa regional frequency plans — the canonical region list the backend's
|
||
* Meshtastic driver can program (core/.../mesh/meshtastic.rs region table),
|
||
* annotated with band / duty-cycle / power constraints from the Meshtastic
|
||
* firmware `regions[]` table so the UI can surface legality hints.
|
||
*
|
||
* Also used to derive suggested radio parameters for MeshCore and RNode
|
||
* presets (those firmwares take raw frequency/BW/SF/CR, not a region enum).
|
||
*/
|
||
|
||
export interface LoraRegion {
|
||
/** Wire value for mesh.configure { lora_region } */
|
||
code: string
|
||
label: string
|
||
/** Frequency range, MHz (display) */
|
||
band: string
|
||
/** Regional duty-cycle cap, percent (100 = none) */
|
||
dutyCyclePct: number
|
||
/** Max TX power, dBm */
|
||
maxPowerDbm: number
|
||
}
|
||
|
||
export const LORA_REGIONS: LoraRegion[] = [
|
||
{ code: 'US', label: 'United States / Americas (915 MHz)', band: '902–928', dutyCyclePct: 100, maxPowerDbm: 30 },
|
||
{ code: 'EU_868', label: 'Europe (868 MHz)', band: '869.4–869.65', dutyCyclePct: 10, maxPowerDbm: 27 },
|
||
{ code: 'EU_433', label: 'Europe (433 MHz)', band: '433–434', dutyCyclePct: 10, maxPowerDbm: 10 },
|
||
{ code: 'ANZ', label: 'Australia / New Zealand (915 MHz)', band: '915–928', dutyCyclePct: 100, maxPowerDbm: 30 },
|
||
{ code: 'ANZ_433', label: 'Australia / New Zealand (433 MHz)', band: '433.05–434.79', dutyCyclePct: 100, maxPowerDbm: 14 },
|
||
{ code: 'NZ_865', label: 'New Zealand (865 MHz)', band: '864–868', dutyCyclePct: 100, maxPowerDbm: 36 },
|
||
{ code: 'CN', label: 'China (470 MHz)', band: '470–510', dutyCyclePct: 100, maxPowerDbm: 19 },
|
||
{ code: 'JP', label: 'Japan (920 MHz)', band: '920.5–923.5', dutyCyclePct: 100, maxPowerDbm: 13 },
|
||
{ code: 'KR', label: 'South Korea (920 MHz)', band: '920–923', dutyCyclePct: 100, maxPowerDbm: 23 },
|
||
{ code: 'TW', label: 'Taiwan (920 MHz)', band: '920–925', dutyCyclePct: 100, maxPowerDbm: 27 },
|
||
{ code: 'RU', label: 'Russia (868 MHz)', band: '868.7–869.2', dutyCyclePct: 100, maxPowerDbm: 20 },
|
||
{ code: 'IN', label: 'India (865 MHz)', band: '865–867', dutyCyclePct: 100, maxPowerDbm: 30 },
|
||
{ code: 'TH', label: 'Thailand (920 MHz)', band: '920–925', dutyCyclePct: 10, maxPowerDbm: 27 },
|
||
{ code: 'UA_868', label: 'Ukraine (868 MHz)', band: '868–868.6', dutyCyclePct: 1, maxPowerDbm: 14 },
|
||
{ code: 'UA_433', label: 'Ukraine (433 MHz)', band: '433–434.7', dutyCyclePct: 10, maxPowerDbm: 10 },
|
||
{ code: 'MY_919', label: 'Malaysia (919 MHz)', band: '919–924', dutyCyclePct: 100, maxPowerDbm: 27 },
|
||
{ code: 'MY_433', label: 'Malaysia (433 MHz)', band: '433–435', dutyCyclePct: 100, maxPowerDbm: 20 },
|
||
{ code: 'SG_923', label: 'Singapore (923 MHz)', band: '917–925', dutyCyclePct: 100, maxPowerDbm: 20 },
|
||
{ code: 'PH_915', label: 'Philippines (915 MHz)', band: '915–918', dutyCyclePct: 100, maxPowerDbm: 24 },
|
||
{ code: 'PH_868', label: 'Philippines (868 MHz)', band: '868–869.4', dutyCyclePct: 100, maxPowerDbm: 14 },
|
||
{ code: 'PH_433', label: 'Philippines (433 MHz)', band: '433–434.7', dutyCyclePct: 100, maxPowerDbm: 10 },
|
||
{ code: 'LORA_24', label: 'Worldwide (2.4 GHz, SX1280 only)', band: '2400–2483.5', dutyCyclePct: 100, maxPowerDbm: 10 },
|
||
]
|
||
|
||
export function regionByCode(code: string | null | undefined): LoraRegion | undefined {
|
||
if (!code) return undefined
|
||
return LORA_REGIONS.find(r => r.code === code.toUpperCase())
|
||
}
|
||
|
||
/**
|
||
* Coarse lat/lon → LoRa region suggestion. Bounding boxes, most-specific
|
||
* first — good enough to preselect the dropdown; the user confirms.
|
||
*/
|
||
export function suggestRegionFromLatLon(lat: number, lon: number): LoraRegion | undefined {
|
||
const boxes: Array<{ code: string; latMin: number; latMax: number; lonMin: number; lonMax: number }> = [
|
||
{ code: 'JP', latMin: 24, latMax: 46, lonMin: 123, lonMax: 146 },
|
||
{ code: 'KR', latMin: 33, latMax: 39, lonMin: 124, lonMax: 132 },
|
||
{ code: 'TW', latMin: 21.5, latMax: 25.5, lonMin: 119.5, lonMax: 122.5 },
|
||
{ code: 'PH_915', latMin: 4, latMax: 21, lonMin: 116, lonMax: 127 },
|
||
{ code: 'SG_923', latMin: 1, latMax: 1.6, lonMin: 103.5, lonMax: 104.2 },
|
||
{ code: 'MY_919', latMin: 0.8, latMax: 7.5, lonMin: 99, lonMax: 119.5 },
|
||
{ code: 'TH', latMin: 5.5, latMax: 20.5, lonMin: 97, lonMax: 106 },
|
||
{ code: 'IN', latMin: 6, latMax: 36, lonMin: 68, lonMax: 97.5 },
|
||
{ code: 'CN', latMin: 18, latMax: 54, lonMin: 73, lonMax: 135 },
|
||
{ code: 'UA_868', latMin: 44, latMax: 52.5, lonMin: 22, lonMax: 40.5 },
|
||
{ code: 'RU', latMin: 41, latMax: 82, lonMin: 27, lonMax: 180 },
|
||
{ code: 'ANZ', latMin: -48, latMax: -9, lonMin: 112, lonMax: 180 },
|
||
{ code: 'EU_868', latMin: 34, latMax: 72, lonMin: -25, lonMax: 45 },
|
||
// Americas
|
||
{ code: 'US', latMin: -56, latMax: 72, lonMin: -170, lonMax: -30 },
|
||
]
|
||
for (const b of boxes) {
|
||
if (lat >= b.latMin && lat <= b.latMax && lon >= b.lonMin && lon <= b.lonMax) {
|
||
return regionByCode(b.code)
|
||
}
|
||
}
|
||
return undefined
|
||
}
|
||
|
||
/** Community MeshCore radio plan for a region, where one is well established.
|
||
* MeshCore has no region enum — the firmware takes raw freq/BW/SF/CR — so
|
||
* these are display-level suggestions (EU is the verified community default;
|
||
* other regions show the legal band and defer to the local community plan). */
|
||
export interface MeshcorePlan {
|
||
freqMhz: number
|
||
bwKhz: number
|
||
sf: number
|
||
cr: number
|
||
}
|
||
|
||
export const MESHCORE_PLANS: Record<string, MeshcorePlan> = {
|
||
EU_868: { freqMhz: 869.525, bwKhz: 250, sf: 11, cr: 5 },
|
||
EU_433: { freqMhz: 433.65, bwKhz: 250, sf: 11, cr: 5 },
|
||
}
|
||
|
||
export function meshcorePlanFor(code: string | null | undefined): MeshcorePlan | undefined {
|
||
if (!code) return undefined
|
||
return MESHCORE_PLANS[code.toUpperCase()]
|
||
}
|
||
|
||
/** A named MeshCore RF preset for the Device-panel dropdown. */
|
||
export interface MeshcoreRfPreset {
|
||
id: string
|
||
label: string
|
||
freqMhz: number
|
||
bwKhz: number
|
||
sf: number
|
||
cr: number
|
||
}
|
||
|
||
/**
|
||
* Prepopulated MeshCore RF plans by country/region for the Device panel's
|
||
* preset dropdown ("Custom" in the UI unlocks free entry). Values are only
|
||
* added here when verifiable: the MeshCore community plans above, the
|
||
* MeshCore firmware's own build defaults (platformio.ini `arduino_base`:
|
||
* 869.618/62.5/SF8, CR default 5; companion example: 915.0/250/SF10/CR5),
|
||
* and operator-attested deployment plans.
|
||
*/
|
||
export const MESHCORE_RF_PRESETS: MeshcoreRfPreset[] = [
|
||
{ id: 'eu_868', label: 'Europe / UK — 869.525 MHz, 250 kHz, SF11, CR4/5', freqMhz: 869.525, bwKhz: 250, sf: 11, cr: 5 },
|
||
{ id: 'pt', label: 'Portugal — 869.618 MHz, 62.5 kHz, SF8, CR4/8', freqMhz: 869.618, bwKhz: 62.5, sf: 8, cr: 8 },
|
||
{ id: 'fw_default', label: 'MeshCore firmware default — 869.618 MHz, 62.5 kHz, SF8, CR4/5', freqMhz: 869.618, bwKhz: 62.5, sf: 8, cr: 5 },
|
||
{ id: 'us_anz_915', label: 'US / Canada / ANZ — 915.0 MHz, 250 kHz, SF10, CR4/5', freqMhz: 915.0, bwKhz: 250, sf: 10, cr: 5 },
|
||
{ id: 'eu_433', label: 'Europe (433 MHz) — 433.65 MHz, 250 kHz, SF11, CR4/5', freqMhz: 433.65, bwKhz: 250, sf: 11, cr: 5 },
|
||
]
|