diff --git a/public/locales/en.json b/public/locales/en.json index 4caf4d9f7..113490e76 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -3189,6 +3189,7 @@ "lora_config.override_frequency_description": "HAM/Advanced: Override channel calculation and use this frequency. Frequency offset still applies. Value of 0 = disabled", "lora_config.region": "Region Code", "lora_config.region_description": "Select your regulatory region for frequency/power limits", + "lora_config.amateur_band_warning": "This region operates in a licensed amateur (ham) radio band. A valid amateur radio license is required, and you are responsible for complying with local band-plan, power, and station identification rules.", "lora_config.hop_limit": "Hop Limit", "lora_config.hop_limit_description": "Maximum number of hops for mesh packets. Range: 1-7 (default: 3)", "lora_config.tx_power": "Transmit Power (dBm)", diff --git a/src/components/admin-commands/RadioConfigurationSection.tsx b/src/components/admin-commands/RadioConfigurationSection.tsx index 2a86b164f..33428a5d7 100644 --- a/src/components/admin-commands/RadioConfigurationSection.tsx +++ b/src/components/admin-commands/RadioConfigurationSection.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { MODEM_PRESET_OPTIONS, REGION_OPTIONS } from '../configuration/constants'; +import { MODEM_PRESET_OPTIONS, REGION_OPTIONS, isAmateurRadioRegion } from '../configuration/constants'; import type { Channel } from '../../types/device'; interface RadioConfigurationSectionProps { @@ -214,6 +214,23 @@ export const RadioConfigurationSection: React.FC ))} + {isAmateurRadioRegion(region) && ( +
+ ⚠️ {t('lora_config.amateur_band_warning')} +
+ )}
diff --git a/src/components/configuration/LoRaConfigSection.test.tsx b/src/components/configuration/LoRaConfigSection.test.tsx index a4f6e8218..ae9b71c68 100644 --- a/src/components/configuration/LoRaConfigSection.test.tsx +++ b/src/components/configuration/LoRaConfigSection.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { MODEM_PRESET_OPTIONS, FEM_LNA_MODE_OPTIONS } from './constants'; +import { MODEM_PRESET_OPTIONS, FEM_LNA_MODE_OPTIONS, AMATEUR_RADIO_REGIONS, isAmateurRadioRegion, REGION_MAP } from './constants'; /** * LoRaConfigSection Tests @@ -137,6 +137,40 @@ describe('LoRaConfigSection', () => { }); }); + describe('Amateur Radio Region Warning (#3924)', () => { + it('flags the ITU 2m amateur regions (27, 28) as amateur bands', () => { + expect(isAmateurRadioRegion(27)).toBe(true); // ITU1_2M + expect(isAmateurRadioRegion(28)).toBe(true); // ITU23_2M + }); + + it('keeps the amateur region set in sync with REGION_MAP values', () => { + expect(AMATEUR_RADIO_REGIONS[27]).toBe('ITU1_2M'); + expect(AMATEUR_RADIO_REGIONS[28]).toBe('ITU23_2M'); + expect(REGION_MAP['ITU1_2M']).toBe(27); + expect(REGION_MAP['ITU23_2M']).toBe(28); + }); + + it('does not flag standard ISM/SRD regions as amateur bands', () => { + expect(isAmateurRadioRegion(1)).toBe(false); // US + expect(isAmateurRadioRegion(3)).toBe(false); // EU_868 + expect(isAmateurRadioRegion(6)).toBe(false); // ANZ + expect(isAmateurRadioRegion(32)).toBe(false); // EU_N_868 + }); + + it('does not flag UNSET (0) as an amateur band', () => { + expect(isAmateurRadioRegion(0)).toBe(false); + }); + + it('handles null/undefined region without throwing', () => { + expect(isAmateurRadioRegion(null)).toBe(false); + expect(isAmateurRadioRegion(undefined)).toBe(false); + }); + + it('does not flag an out-of-range region value', () => { + expect(isAmateurRadioRegion(999)).toBe(false); + }); + }); + describe('Save Configuration Logic', () => { it('includes femLnaMode in the LoRa save payload', () => { // Mirrors the Device Configuration save (ConfigurationTab) and Remote Admin diff --git a/src/components/configuration/LoRaConfigSection.tsx b/src/components/configuration/LoRaConfigSection.tsx index 47dd2ea1b..f47fadcb8 100644 --- a/src/components/configuration/LoRaConfigSection.tsx +++ b/src/components/configuration/LoRaConfigSection.tsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useMemo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { MODEM_PRESET_OPTIONS, REGION_OPTIONS, FEM_LNA_MODE_OPTIONS } from './constants'; +import { MODEM_PRESET_OPTIONS, REGION_OPTIONS, FEM_LNA_MODE_OPTIONS, isAmateurRadioRegion } from './constants'; import { useSaveBar } from '../../hooks/useSaveBar'; interface LoRaConfigSectionProps { @@ -387,6 +387,23 @@ const LoRaConfigSection: React.FC = ({ ))} + {isAmateurRadioRegion(region) && ( +
+ ⚠️ {t('lora_config.amateur_band_warning')} +
+ )}