diff --git a/app/routers/radio.py b/app/routers/radio.py index aad08f7..ff0a5b4 100644 --- a/app/routers/radio.py +++ b/app/routers/radio.py @@ -101,6 +101,18 @@ class RadioConfigResponse(BaseModel): default=False, description="Whether the radio sends an extra direct ACK transmission", ) + telemetry_mode_base: int = Field( + default=0, + description="Base telemetry sharing mode (0=deny, 1=per-contact, 2=allow-all)", + ) + telemetry_mode_loc: int = Field( + default=0, + description="Location telemetry sharing mode (0=deny, 1=per-contact, 2=allow-all)", + ) + telemetry_mode_env: int = Field( + default=0, + description="Environment sensor sharing mode (0=deny, 1=per-contact, 2=allow-all)", + ) class RadioConfigUpdate(BaseModel): @@ -123,6 +135,15 @@ class RadioConfigUpdate(BaseModel): default=None, description="Whether the radio sends an extra direct ACK transmission", ) + telemetry_mode_base: int | None = Field( + default=None, ge=0, le=2, description="Base telemetry sharing mode" + ) + telemetry_mode_loc: int | None = Field( + default=None, ge=0, le=2, description="Location telemetry sharing mode" + ) + telemetry_mode_env: int | None = Field( + default=None, ge=0, le=2, description="Environment sensor sharing mode" + ) class PrivateKeyUpdate(BaseModel): @@ -360,6 +381,9 @@ async def get_radio_config() -> RadioConfigResponse: path_hash_mode_supported=radio_manager.path_hash_mode_supported, advert_location_source=advert_location_source, multi_acks_enabled=bool(info.get("multi_acks", 0)), + telemetry_mode_base=info.get("telemetry_mode_base", 0), + telemetry_mode_loc=info.get("telemetry_mode_loc", 0), + telemetry_mode_env=info.get("telemetry_mode_env", 0), ) diff --git a/app/services/radio_commands.py b/app/services/radio_commands.py index 9718a37..8f7a1c5 100644 --- a/app/services/radio_commands.py +++ b/app/services/radio_commands.py @@ -51,6 +51,30 @@ async def apply_radio_config_update( if result is not None and result.type == EventType.ERROR: raise RadioCommandRejectedError(f"Failed to set multi ACKs: {result.payload}") + if update.telemetry_mode_base is not None: + logger.info("Setting telemetry_mode_base to %d", update.telemetry_mode_base) + result = await mc.commands.set_telemetry_mode_base(update.telemetry_mode_base) + if result is not None and result.type == EventType.ERROR: + raise RadioCommandRejectedError( + f"Failed to set telemetry mode (base): {result.payload}" + ) + + if update.telemetry_mode_loc is not None: + logger.info("Setting telemetry_mode_loc to %d", update.telemetry_mode_loc) + result = await mc.commands.set_telemetry_mode_loc(update.telemetry_mode_loc) + if result is not None and result.type == EventType.ERROR: + raise RadioCommandRejectedError( + f"Failed to set telemetry mode (location): {result.payload}" + ) + + if update.telemetry_mode_env is not None: + logger.info("Setting telemetry_mode_env to %d", update.telemetry_mode_env) + result = await mc.commands.set_telemetry_mode_env(update.telemetry_mode_env) + if result is not None and result.type == EventType.ERROR: + raise RadioCommandRejectedError( + f"Failed to set telemetry mode (environment): {result.payload}" + ) + if update.name is not None: logger.info("Setting radio name to %s", update.name) await mc.commands.set_name(update.name) diff --git a/frontend/src/components/settings/SettingsRadioSection.tsx b/frontend/src/components/settings/SettingsRadioSection.tsx index 1278a6a..25d64e4 100644 --- a/frontend/src/components/settings/SettingsRadioSection.tsx +++ b/frontend/src/components/settings/SettingsRadioSection.tsx @@ -183,6 +183,9 @@ export function SettingsRadioSection({ const [pathHashMode, setPathHashMode] = useState('0'); const [advertLocationSource, setAdvertLocationSource] = useState<'off' | 'current'>('current'); const [multiAcksEnabled, setMultiAcksEnabled] = useState(false); + const [telemetryModeBase, setTelemetryModeBase] = useState(0); + const [telemetryModeLoc, setTelemetryModeLoc] = useState(0); + const [telemetryModeEnv, setTelemetryModeEnv] = useState(0); const [gettingLocation, setGettingLocation] = useState(false); const [busy, setBusy] = useState(false); const [rebooting, setRebooting] = useState(false); @@ -218,6 +221,9 @@ export function SettingsRadioSection({ setPathHashMode(String(config.path_hash_mode)); setAdvertLocationSource(config.advert_location_source ?? 'current'); setMultiAcksEnabled(config.multi_acks_enabled ?? false); + setTelemetryModeBase(config.telemetry_mode_base ?? 0); + setTelemetryModeLoc(config.telemetry_mode_loc ?? 0); + setTelemetryModeEnv(config.telemetry_mode_env ?? 0); }, [config]); useEffect(() => { @@ -313,6 +319,15 @@ export function SettingsRadioSection({ ...(multiAcksEnabled !== (config.multi_acks_enabled ?? false) ? { multi_acks_enabled: multiAcksEnabled } : {}), + ...(telemetryModeBase !== (config.telemetry_mode_base ?? 0) + ? { telemetry_mode_base: telemetryModeBase } + : {}), + ...(telemetryModeLoc !== (config.telemetry_mode_loc ?? 0) + ? { telemetry_mode_loc: telemetryModeLoc } + : {}), + ...(telemetryModeEnv !== (config.telemetry_mode_env ?? 0) + ? { telemetry_mode_env: telemetryModeEnv } + : {}), radio: { freq: parsedFreq, bw: parsedBw, @@ -468,6 +483,9 @@ export function SettingsRadioSection({ path_hash_mode: config.path_hash_mode, advert_location_source: config.advert_location_source ?? 'current', multi_acks_enabled: config.multi_acks_enabled ?? false, + telemetry_mode_base: config.telemetry_mode_base ?? 0, + telemetry_mode_loc: config.telemetry_mode_loc ?? 0, + telemetry_mode_env: config.telemetry_mode_env ?? 0, }); const downloadJson = (profile: object, suffix: string) => { @@ -539,6 +557,10 @@ export function SettingsRadioSection({ if (data.advert_location_source === 'off' || data.advert_location_source === 'current') setAdvertLocationSource(data.advert_location_source); if (typeof data.multi_acks_enabled === 'boolean') setMultiAcksEnabled(data.multi_acks_enabled); + if (typeof data.telemetry_mode_base === 'number') + setTelemetryModeBase(data.telemetry_mode_base); + if (typeof data.telemetry_mode_loc === 'number') setTelemetryModeLoc(data.telemetry_mode_loc); + if (typeof data.telemetry_mode_env === 'number') setTelemetryModeEnv(data.telemetry_mode_env); }; const buildUpdateFromImport = (data: Record): RadioConfigUpdate => { @@ -554,6 +576,12 @@ export function SettingsRadioSection({ update.advert_location_source = data.advert_location_source; if (typeof data.multi_acks_enabled === 'boolean') update.multi_acks_enabled = data.multi_acks_enabled; + if (typeof data.telemetry_mode_base === 'number') + update.telemetry_mode_base = data.telemetry_mode_base as number; + if (typeof data.telemetry_mode_loc === 'number') + update.telemetry_mode_loc = data.telemetry_mode_loc as number; + if (typeof data.telemetry_mode_env === 'number') + update.telemetry_mode_env = data.telemetry_mode_env as number; if (config.path_hash_mode_supported && typeof data.path_hash_mode === 'number') update.path_hash_mode = data.path_hash_mode as number; return update; @@ -954,6 +982,66 @@ export function SettingsRadioSection({ + + + {/* ── Telemetry Sharing ── */} +
+

Telemetry Sharing

+

+ Controls what this radio shares when other nodes request its telemetry. “Deny” + blocks all requests, “Per-Contact” uses per-contact permission flags on the + radio, and “Allow All” shares with any requester. +

+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ {error && (
{error} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 4bb4342..4ab7bbc 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -17,6 +17,9 @@ export interface RadioConfig { path_hash_mode_supported: boolean; advert_location_source?: 'off' | 'current'; multi_acks_enabled?: boolean; + telemetry_mode_base?: number; + telemetry_mode_loc?: number; + telemetry_mode_env?: number; } export interface RadioConfigUpdate { @@ -28,6 +31,9 @@ export interface RadioConfigUpdate { path_hash_mode?: number; advert_location_source?: 'off' | 'current'; multi_acks_enabled?: boolean; + telemetry_mode_base?: number; + telemetry_mode_loc?: number; + telemetry_mode_env?: number; } export type RadioDiscoveryTarget = 'repeaters' | 'sensors' | 'all';