diff --git a/drb-c2-core/app/routers/nodes.py b/drb-c2-core/app/routers/nodes.py index cbacbe1..297b3e5 100644 --- a/drb-c2-core/app/routers/nodes.py +++ b/drb-c2-core/app/routers/nodes.py @@ -1,5 +1,5 @@ import secrets -from fastapi import APIRouter, HTTPException, Depends +from fastapi import APIRouter, HTTPException, Depends, Query from app.models import CommandPayload from app.internal import firestore as fstore from app.internal.mqtt_handler import mqtt_handler @@ -93,7 +93,12 @@ async def reissue_node_key(node_id: str, _: dict = Depends(require_admin_token)) @router.post("/{node_id}/config/{system_id}") -async def assign_system(node_id: str, system_id: str): +async def assign_system( + node_id: str, + system_id: str, + hardware_preset: str = Query("rtl-sdr-v3"), + ppm_override: float = Query(None), +): """ Assign a system to a node. Fetches the system config from Firestore and pushes it to the node via MQTT, then marks the node as configured. @@ -106,13 +111,22 @@ async def assign_system(node_id: str, system_id: str): if not system: raise HTTPException(404, f"System '{system_id}' not found.") - # Push config to the node via MQTT - mqtt_handler.push_config(node_id, system) + # Include hardware preset in the push so the edge node applies it when + # generating the OP25 config. Strip it from the system doc first so it + # doesn't collide with SystemConfig field validation on the node side. + push_payload = {**system, "hardware_preset": hardware_preset} + if ppm_override is not None: + push_payload["ppm_override"] = ppm_override + mqtt_handler.push_config(node_id, push_payload) # Update Firestore - await fstore.doc_update("nodes", node_id, { + node_updates = { "assigned_system_id": system_id, "configured": True, - }) + "hardware_preset": hardware_preset, + } + if ppm_override is not None: + node_updates["ppm_override"] = ppm_override + await fstore.doc_update("nodes", node_id, node_updates) return {"ok": True} diff --git a/drb-frontend/components/NodeConfigModal.tsx b/drb-frontend/components/NodeConfigModal.tsx index f9c8340..7a5cba6 100644 --- a/drb-frontend/components/NodeConfigModal.tsx +++ b/drb-frontend/components/NodeConfigModal.tsx @@ -10,18 +10,29 @@ interface Props { onClose: () => void; } +const PRESETS = [ + { value: "rtl-sdr-v3", label: "RTL-SDR v3", hint: "TCXO — LNA:34, tracking off" }, + { value: "nesdr-smart-v4", label: "NESDR Smart v4", hint: "TCXO — LNA:32, tracking off" }, + { value: "other", label: "Other", hint: "LNA:32, tracking on" }, +]; + export function NodeConfigModal({ node, systems, onClose }: Props) { const [systemId, setSystemId] = useState(""); + const [preset, setPreset] = useState("rtl-sdr-v3"); + const [ppm, setPpm] = useState("0"); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); + const ppmVal = parseFloat(ppm); + const ppmOverride = !isNaN(ppmVal) && ppmVal !== 0 ? ppmVal : undefined; + async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!systemId) return; setSaving(true); setError(null); try { - await c2api.assignSystem(node.node_id, systemId); + await c2api.assignSystem(node.node_id, systemId, preset, ppmOverride); onClose(); } catch (err) { setError(err instanceof Error ? err.message : "Failed to assign system."); @@ -30,6 +41,8 @@ export function NodeConfigModal({ node, systems, onClose }: Props) { } } + const selectedPreset = PRESETS.find((p) => p.value === preset); + return (
@@ -57,6 +70,36 @@ export function NodeConfigModal({ node, systems, onClose }: Props) {
+
+ + + {selectedPreset && ( +

{selectedPreset.hint}

+ )} +
+ +
+ + setPpm(e.target.value)} + step="0.1" + className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm font-mono focus:outline-none focus:border-indigo-500" + placeholder="0" + /> +
+ {error &&

{error}

}
diff --git a/drb-frontend/lib/c2api.ts b/drb-frontend/lib/c2api.ts index 6b12dd2..dc87c93 100644 --- a/drb-frontend/lib/c2api.ts +++ b/drb-frontend/lib/c2api.ts @@ -25,8 +25,11 @@ export const c2api = { getNode: (id: string) => request(`/nodes/${id}`), sendCommand: (nodeId: string, payload: object) => request(`/nodes/${nodeId}/command`, { method: "POST", body: JSON.stringify(payload) }), - assignSystem: (nodeId: string, systemId: string) => - request(`/nodes/${nodeId}/config/${systemId}`, { method: "POST" }), + assignSystem: (nodeId: string, systemId: string, hardwarePreset: string, ppmOverride?: number) => { + const params = new URLSearchParams({ hardware_preset: hardwarePreset }); + if (ppmOverride !== undefined) params.set("ppm_override", String(ppmOverride)); + return request(`/nodes/${nodeId}/config/${systemId}?${params}`, { method: "POST" }); + }, // Systems getSystems: () => request("/systems"), diff --git a/drb-frontend/lib/types.ts b/drb-frontend/lib/types.ts index 7b38a8b..e6d6561 100644 --- a/drb-frontend/lib/types.ts +++ b/drb-frontend/lib/types.ts @@ -11,6 +11,8 @@ export interface NodeRecord { last_seen: string | null; assigned_system_id: string | null; approval_status: ApprovalStatus | null; + hardware_preset?: string; + ppm_override?: number | null; } export interface VocabularyPendingTerm {