| @@ -13,10 +13,11 @@ import ( | |||||
| "sdr-wideband-suite/internal/config" | "sdr-wideband-suite/internal/config" | ||||
| "sdr-wideband-suite/internal/detector" | "sdr-wideband-suite/internal/detector" | ||||
| "sdr-wideband-suite/internal/dsp" | "sdr-wideband-suite/internal/dsp" | ||||
| "sdr-wideband-suite/internal/pipeline" | |||||
| "sdr-wideband-suite/internal/recorder" | "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() { | defer func() { | ||||
| if r := recover(); r != nil { | if r := recover(); r != nil { | ||||
| log.Printf("FATAL: runDSP goroutine panic: %v\n%s", r, debug.Stack()) | 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) | rt.maintenance(displaySignals, rec) | ||||
| } else { | } else { | ||||
| state.refinement = pipeline.RefinementResult{} | |||||
| displaySignals = rt.det.StableSignals() | displaySignals = rt.det.StableSignals() | ||||
| } | } | ||||
| if phaseSnap != nil { | |||||
| phaseSnap.Set(*state) | |||||
| } | |||||
| if sigSnap != nil { | if sigSnap != nil { | ||||
| sigSnap.set(displaySignals) | sigSnap.set(displaySignals) | ||||
| @@ -21,7 +21,7 @@ import ( | |||||
| "sdr-wideband-suite/internal/runtime" | "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) { | mux.HandleFunc("/api/config", func(w http.ResponseWriter, r *http.Request) { | ||||
| w.Header().Set("Content-Type", "application/json") | w.Header().Set("Content-Type", "application/json") | ||||
| switch r.Method { | 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() | mux := http.NewServeMux() | ||||
| registerWSHandlers(mux, h, recMgr) | 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))) | mux.Handle("/", http.FileServer(http.Dir(webRoot))) | ||||
| return &http.Server{Addr: addr, Handler: mux} | return &http.Server{Addr: addr, Handler: mux} | ||||
| } | } | ||||
| @@ -97,18 +97,18 @@ func main() { | |||||
| decodeMap := buildDecoderMap(cfg) | decodeMap := buildDecoderMap(cfg) | ||||
| recMgr := recorder.New(cfg.SampleRate, cfg.FFTSize, recorder.Policy{ | 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, | RingSeconds: cfg.Recorder.RingSeconds, | ||||
| DeemphasisUs: cfg.Recorder.DeemphasisUs, | DeemphasisUs: cfg.Recorder.DeemphasisUs, | ||||
| ExtractionTaps: cfg.Recorder.ExtractionTaps, | ExtractionTaps: cfg.Recorder.ExtractionTaps, | ||||
| @@ -120,9 +120,10 @@ func main() { | |||||
| extractMgr := &extractionManager{} | extractMgr := &extractionManager{} | ||||
| defer extractMgr.reset() | 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() { | go func() { | ||||
| log.Printf("web listening on %s", cfg.WebAddr) | log.Printf("web listening on %s", cfg.WebAddr) | ||||
| if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { | ||||
| @@ -135,7 +136,3 @@ func main() { | |||||
| <-stop | <-stop | ||||
| shutdownServer(server) | shutdownServer(server) | ||||
| } | } | ||||
| @@ -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 | |||||
| } | |||||