From 831119c3693f1b9c3d7e40d4b9bb8f53b2c9f35c Mon Sep 17 00:00:00 2001 From: Jan Svabenik Date: Sat, 21 Mar 2026 19:03:39 +0100 Subject: [PATCH] feat: capture phase snapshots for API --- cmd/sdrd/dsp_loop.go | 7 ++++++- cmd/sdrd/http_handlers.go | 6 +++--- cmd/sdrd/main.go | 33 +++++++++++++++------------------ cmd/sdrd/phase_snapshot.go | 27 +++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 cmd/sdrd/phase_snapshot.go diff --git a/cmd/sdrd/dsp_loop.go b/cmd/sdrd/dsp_loop.go index 6af5789..10eb856 100644 --- a/cmd/sdrd/dsp_loop.go +++ b/cmd/sdrd/dsp_loop.go @@ -13,10 +13,11 @@ import ( "sdr-wideband-suite/internal/config" "sdr-wideband-suite/internal/detector" "sdr-wideband-suite/internal/dsp" + "sdr-wideband-suite/internal/pipeline" "sdr-wideband-suite/internal/recorder" ) -func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *detector.Detector, window []float64, h *hub, eventFile *os.File, eventMu *sync.RWMutex, updates <-chan dspUpdate, gpuState *gpuStatus, rec *recorder.Manager, sigSnap *signalSnapshot, extractMgr *extractionManager) { +func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *detector.Detector, window []float64, h *hub, eventFile *os.File, eventMu *sync.RWMutex, updates <-chan dspUpdate, gpuState *gpuStatus, rec *recorder.Manager, sigSnap *signalSnapshot, extractMgr *extractionManager, phaseSnap *phaseSnapshot) { defer func() { if r := recover(); r != nil { log.Printf("FATAL: runDSP goroutine panic: %v\n%s", r, debug.Stack()) @@ -88,8 +89,12 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * } rt.maintenance(displaySignals, rec) } else { + state.refinement = pipeline.RefinementResult{} displaySignals = rt.det.StableSignals() } + if phaseSnap != nil { + phaseSnap.Set(*state) + } if sigSnap != nil { sigSnap.set(displaySignals) diff --git a/cmd/sdrd/http_handlers.go b/cmd/sdrd/http_handlers.go index 31f9b20..2224489 100644 --- a/cmd/sdrd/http_handlers.go +++ b/cmd/sdrd/http_handlers.go @@ -21,7 +21,7 @@ import ( "sdr-wideband-suite/internal/runtime" ) -func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime.Manager, srcMgr *sourceManager, dspUpdates chan dspUpdate, gpuState *gpuStatus, recMgr *recorder.Manager, sigSnap *signalSnapshot, eventMu *sync.RWMutex) { +func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime.Manager, srcMgr *sourceManager, dspUpdates chan dspUpdate, gpuState *gpuStatus, recMgr *recorder.Manager, sigSnap *signalSnapshot, eventMu *sync.RWMutex, phaseSnap *phaseSnapshot) { mux.HandleFunc("/api/config", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch r.Method { @@ -291,10 +291,10 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime }) } -func newHTTPServer(addr string, webRoot string, h *hub, cfgPath string, cfgManager *runtime.Manager, srcMgr *sourceManager, dspUpdates chan dspUpdate, gpuState *gpuStatus, recMgr *recorder.Manager, sigSnap *signalSnapshot, eventMu *sync.RWMutex) *http.Server { +func newHTTPServer(addr string, webRoot string, h *hub, cfgPath string, cfgManager *runtime.Manager, srcMgr *sourceManager, dspUpdates chan dspUpdate, gpuState *gpuStatus, recMgr *recorder.Manager, sigSnap *signalSnapshot, eventMu *sync.RWMutex, phaseSnap *phaseSnapshot) *http.Server { mux := http.NewServeMux() registerWSHandlers(mux, h, recMgr) - registerAPIHandlers(mux, cfgPath, cfgManager, srcMgr, dspUpdates, gpuState, recMgr, sigSnap, eventMu) + registerAPIHandlers(mux, cfgPath, cfgManager, srcMgr, dspUpdates, gpuState, recMgr, sigSnap, eventMu, phaseSnap) mux.Handle("/", http.FileServer(http.Dir(webRoot))) return &http.Server{Addr: addr, Handler: mux} } diff --git a/cmd/sdrd/main.go b/cmd/sdrd/main.go index a9416e1..624c752 100644 --- a/cmd/sdrd/main.go +++ b/cmd/sdrd/main.go @@ -97,18 +97,18 @@ func main() { decodeMap := buildDecoderMap(cfg) recMgr := recorder.New(cfg.SampleRate, cfg.FFTSize, recorder.Policy{ - Enabled: cfg.Recorder.Enabled, - MinSNRDb: cfg.Recorder.MinSNRDb, - MinDuration: mustParseDuration(cfg.Recorder.MinDuration, 1*time.Second), - MaxDuration: mustParseDuration(cfg.Recorder.MaxDuration, 300*time.Second), - PrerollMs: cfg.Recorder.PrerollMs, - RecordIQ: cfg.Recorder.RecordIQ, - RecordAudio: cfg.Recorder.RecordAudio, - AutoDemod: cfg.Recorder.AutoDemod, - AutoDecode: cfg.Recorder.AutoDecode, - MaxDiskMB: cfg.Recorder.MaxDiskMB, - OutputDir: cfg.Recorder.OutputDir, - ClassFilter: cfg.Recorder.ClassFilter, + Enabled: cfg.Recorder.Enabled, + MinSNRDb: cfg.Recorder.MinSNRDb, + MinDuration: mustParseDuration(cfg.Recorder.MinDuration, 1*time.Second), + MaxDuration: mustParseDuration(cfg.Recorder.MaxDuration, 300*time.Second), + PrerollMs: cfg.Recorder.PrerollMs, + RecordIQ: cfg.Recorder.RecordIQ, + RecordAudio: cfg.Recorder.RecordAudio, + AutoDemod: cfg.Recorder.AutoDemod, + AutoDecode: cfg.Recorder.AutoDecode, + MaxDiskMB: cfg.Recorder.MaxDiskMB, + OutputDir: cfg.Recorder.OutputDir, + ClassFilter: cfg.Recorder.ClassFilter, RingSeconds: cfg.Recorder.RingSeconds, DeemphasisUs: cfg.Recorder.DeemphasisUs, ExtractionTaps: cfg.Recorder.ExtractionTaps, @@ -120,9 +120,10 @@ func main() { extractMgr := &extractionManager{} defer extractMgr.reset() - go runDSP(ctx, srcMgr, cfg, det, window, h, eventFile, eventMu, dspUpdates, gpuState, recMgr, sigSnap, extractMgr) + phaseSnap := &phaseSnapshot{} + go runDSP(ctx, srcMgr, cfg, det, window, h, eventFile, eventMu, dspUpdates, gpuState, recMgr, sigSnap, extractMgr, phaseSnap) - server := newHTTPServer(cfg.WebAddr, cfg.WebRoot, h, cfgPath, cfgManager, srcMgr, dspUpdates, gpuState, recMgr, sigSnap, eventMu) + server := newHTTPServer(cfg.WebAddr, cfg.WebRoot, h, cfgPath, cfgManager, srcMgr, dspUpdates, gpuState, recMgr, sigSnap, eventMu, phaseSnap) go func() { log.Printf("web listening on %s", cfg.WebAddr) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { @@ -135,7 +136,3 @@ func main() { <-stop shutdownServer(server) } - - - - diff --git a/cmd/sdrd/phase_snapshot.go b/cmd/sdrd/phase_snapshot.go new file mode 100644 index 0000000..44b5259 --- /dev/null +++ b/cmd/sdrd/phase_snapshot.go @@ -0,0 +1,27 @@ +package main + +import "sync" + +type phaseSnapshot struct { + mu sync.RWMutex + state phaseState +} + +func (p *phaseSnapshot) Set(state phaseState) { + if p == nil { + return + } + p.mu.Lock() + p.state = state + p.mu.Unlock() +} + +func (p *phaseSnapshot) Snapshot() phaseState { + if p == nil { + return phaseState{} + } + p.mu.RLock() + state := p.state + p.mu.RUnlock() + return state +}