audio fixes attempt
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user