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
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}
+44 -1
View File
@@ -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<string | null>(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 (
<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">
@@ -57,6 +70,36 @@ export function NodeConfigModal({ node, systems, onClose }: Props) {
</select>
</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>}
<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}`),
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<unknown[]>("/systems"),
+2
View File
@@ -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 {