audio fixes attempt

This commit is contained in:
Logan
2026-05-23 14:59:51 -04:00
parent 9cf8fd4221
commit 35ce8e911e
4 changed files with 71 additions and 9 deletions
+20 -6
View File
@@ -1,5 +1,5 @@
import secrets import secrets
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends, Query
from app.models import CommandPayload from app.models import CommandPayload
from app.internal import firestore as fstore from app.internal import firestore as fstore
from app.internal.mqtt_handler import mqtt_handler 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}") @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 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. 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: if not system:
raise HTTPException(404, f"System '{system_id}' not found.") raise HTTPException(404, f"System '{system_id}' not found.")
# Push config to the node via MQTT # Include hardware preset in the push so the edge node applies it when
mqtt_handler.push_config(node_id, system) # 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 # Update Firestore
await fstore.doc_update("nodes", node_id, { node_updates = {
"assigned_system_id": system_id, "assigned_system_id": system_id,
"configured": True, "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} return {"ok": True}
+44 -1
View File
@@ -10,18 +10,29 @@ interface Props {
onClose: () => void; 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) { export function NodeConfigModal({ node, systems, onClose }: Props) {
const [systemId, setSystemId] = useState(""); const [systemId, setSystemId] = useState("");
const [preset, setPreset] = useState("rtl-sdr-v3");
const [ppm, setPpm] = useState("0");
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const ppmVal = parseFloat(ppm);
const ppmOverride = !isNaN(ppmVal) && ppmVal !== 0 ? ppmVal : undefined;
async function handleSubmit(e: React.FormEvent) { async function handleSubmit(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
if (!systemId) return; if (!systemId) return;
setSaving(true); setSaving(true);
setError(null); setError(null);
try { try {
await c2api.assignSystem(node.node_id, systemId); await c2api.assignSystem(node.node_id, systemId, preset, ppmOverride);
onClose(); onClose();
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : "Failed to assign system."); 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 ( return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50">
<div className="bg-gray-900 border border-gray-700 rounded-xl p-6 w-full max-w-md font-mono"> <div className="bg-gray-900 border border-gray-700 rounded-xl p-6 w-full max-w-md font-mono">
@@ -57,6 +70,36 @@ export function NodeConfigModal({ node, systems, onClose }: Props) {
</select> </select>
</div> </div>
<div>
<label className="block text-xs text-gray-400 mb-1">SDR Hardware</label>
<select
value={preset}
onChange={(e) => setPreset(e.target.value)}
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500"
>
{PRESETS.map((p) => (
<option key={p.value} value={p.value}>{p.label}</option>
))}
</select>
{selectedPreset && (
<p className="text-xs text-gray-600 mt-1">{selectedPreset.hint}</p>
)}
</div>
<div>
<label className="block text-xs text-gray-400 mb-1">
PPM offset <span className="text-gray-600">(0 = use preset default; calibrate via OP25 web UI)</span>
</label>
<input
type="number"
value={ppm}
onChange={(e) => 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"
/>
</div>
{error && <p className="text-red-400 text-xs">{error}</p>} {error && <p className="text-red-400 text-xs">{error}</p>}
<div className="flex gap-3 pt-1"> <div className="flex gap-3 pt-1">
+5 -2
View File
@@ -25,8 +25,11 @@ export const c2api = {
getNode: (id: string) => request<unknown>(`/nodes/${id}`), getNode: (id: string) => request<unknown>(`/nodes/${id}`),
sendCommand: (nodeId: string, payload: object) => sendCommand: (nodeId: string, payload: object) =>
request(`/nodes/${nodeId}/command`, { method: "POST", body: JSON.stringify(payload) }), request(`/nodes/${nodeId}/command`, { method: "POST", body: JSON.stringify(payload) }),
assignSystem: (nodeId: string, systemId: string) => assignSystem: (nodeId: string, systemId: string, hardwarePreset: string, ppmOverride?: number) => {
request(`/nodes/${nodeId}/config/${systemId}`, { method: "POST" }), 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 // Systems
getSystems: () => request<unknown[]>("/systems"), getSystems: () => request<unknown[]>("/systems"),
+2
View File
@@ -11,6 +11,8 @@ export interface NodeRecord {
last_seen: string | null; last_seen: string | null;
assigned_system_id: string | null; assigned_system_id: string | null;
approval_status: ApprovalStatus | null; approval_status: ApprovalStatus | null;
hardware_preset?: string;
ppm_override?: number | null;
} }
export interface VocabularyPendingTerm { export interface VocabularyPendingTerm {