Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

164 строки
5.4KB

  1. package main
  2. import (
  3. "context"
  4. "flag"
  5. "log"
  6. "net/http"
  7. "os"
  8. "os/signal"
  9. "path/filepath"
  10. "runtime/debug"
  11. "sync"
  12. "syscall"
  13. "time"
  14. "sdr-wideband-suite/internal/config"
  15. "sdr-wideband-suite/internal/detector"
  16. fftutil "sdr-wideband-suite/internal/fft"
  17. "sdr-wideband-suite/internal/fft/gpufft"
  18. "sdr-wideband-suite/internal/logging"
  19. "sdr-wideband-suite/internal/mock"
  20. "sdr-wideband-suite/internal/recorder"
  21. "sdr-wideband-suite/internal/runtime"
  22. "sdr-wideband-suite/internal/sdr"
  23. "sdr-wideband-suite/internal/sdrplay"
  24. "sdr-wideband-suite/internal/telemetry"
  25. )
  26. func main() {
  27. // Reduce GC target to limit peak memory. Default GOGC=100 lets heap
  28. // grow to 2× live set before collecting. GOGC=50 triggers GC at 1.5×,
  29. // halving the memory swings at a small CPU cost.
  30. debug.SetGCPercent(50)
  31. // Soft memory limit — GC will be more aggressive near this limit.
  32. // 1 GB is generous for 5 WFM-stereo signals + FFT + recordings.
  33. debug.SetMemoryLimit(1024 * 1024 * 1024)
  34. var cfgPath string
  35. var mockFlag bool
  36. flag.StringVar(&cfgPath, "config", "config.yaml", "path to config YAML")
  37. flag.BoolVar(&mockFlag, "mock", false, "use synthetic IQ source")
  38. flag.Parse()
  39. cfg, err := config.Load(cfgPath)
  40. if err != nil {
  41. log.Fatalf("load config: %v", err)
  42. }
  43. if err := logging.Init(logging.Config(cfg.Logging)); err != nil {
  44. log.Fatalf("logging init: %v", err)
  45. }
  46. defer logging.Close()
  47. cfgManager := runtime.New(cfg)
  48. gpuState := &gpuStatus{Available: gpufft.Available()}
  49. telemetryCfg := telemetry.Config{
  50. Enabled: cfg.Debug.Telemetry.Enabled,
  51. HeavyEnabled: cfg.Debug.Telemetry.HeavyEnabled,
  52. HeavySampleEvery: cfg.Debug.Telemetry.HeavySampleEvery,
  53. MetricSampleEvery: cfg.Debug.Telemetry.MetricSampleEvery,
  54. MetricHistoryMax: cfg.Debug.Telemetry.MetricHistoryMax,
  55. EventHistoryMax: cfg.Debug.Telemetry.EventHistoryMax,
  56. Retention: time.Duration(cfg.Debug.Telemetry.RetentionSeconds) * time.Second,
  57. PersistEnabled: cfg.Debug.Telemetry.PersistEnabled,
  58. PersistDir: cfg.Debug.Telemetry.PersistDir,
  59. RotateMB: cfg.Debug.Telemetry.RotateMB,
  60. KeepFiles: cfg.Debug.Telemetry.KeepFiles,
  61. }
  62. telemetryCollector, err := telemetry.New(telemetryCfg)
  63. if err != nil {
  64. log.Fatalf("telemetry init failed: %v", err)
  65. }
  66. defer telemetryCollector.Close()
  67. telemetryCollector.SetStatus("build", "sdrd")
  68. newSource := func(cfg config.Config) (sdr.Source, error) {
  69. if mockFlag {
  70. src := mock.New(cfg.SampleRate)
  71. if updatable, ok := interface{}(src).(sdr.ConfigurableSource); ok {
  72. _ = updatable.UpdateConfig(cfg.SampleRate, cfg.CenterHz, cfg.GainDb, cfg.AGC, cfg.TunerBwKHz)
  73. }
  74. return src, nil
  75. }
  76. src, err := sdrplay.New(cfg.SampleRate, cfg.CenterHz, cfg.GainDb, cfg.TunerBwKHz)
  77. if err != nil {
  78. return nil, err
  79. }
  80. if updatable, ok := src.(sdr.ConfigurableSource); ok {
  81. _ = updatable.UpdateConfig(cfg.SampleRate, cfg.CenterHz, cfg.GainDb, cfg.AGC, cfg.TunerBwKHz)
  82. }
  83. return src, nil
  84. }
  85. src, err := newSource(cfg)
  86. if err != nil {
  87. log.Fatalf("sdrplay init failed: %v (try --mock or build with -tags sdrplay)", err)
  88. }
  89. srcMgr := newSourceManagerWithTelemetry(src, newSource, telemetryCollector)
  90. if err := srcMgr.Start(); err != nil {
  91. log.Fatalf("source start: %v", err)
  92. }
  93. defer srcMgr.Stop()
  94. if err := os.MkdirAll(filepath.Dir(cfg.EventPath), 0o755); err != nil {
  95. log.Fatalf("event path: %v", err)
  96. }
  97. eventFile, err := os.OpenFile(cfg.EventPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
  98. if err != nil {
  99. log.Fatalf("open events: %v", err)
  100. }
  101. defer eventFile.Close()
  102. eventMu := &sync.RWMutex{}
  103. det := detector.New(cfg.Detector, cfg.SampleRate, cfg.FFTSize)
  104. window := fftutil.Hann(cfg.FFTSize)
  105. h := newHub()
  106. dspUpdates := make(chan dspUpdate, 1)
  107. ctx, cancel := context.WithCancel(context.Background())
  108. defer cancel()
  109. decodeMap := buildDecoderMap(cfg)
  110. recMgr := recorder.New(cfg.SampleRate, cfg.FFTSize, recorder.Policy{
  111. Enabled: cfg.Recorder.Enabled,
  112. MinSNRDb: cfg.Recorder.MinSNRDb,
  113. MinDuration: mustParseDuration(cfg.Recorder.MinDuration, 1*time.Second),
  114. MaxDuration: mustParseDuration(cfg.Recorder.MaxDuration, 300*time.Second),
  115. PrerollMs: cfg.Recorder.PrerollMs,
  116. RecordIQ: cfg.Recorder.RecordIQ,
  117. RecordAudio: cfg.Recorder.RecordAudio,
  118. AutoDemod: cfg.Recorder.AutoDemod,
  119. AutoDecode: cfg.Recorder.AutoDecode,
  120. MaxDiskMB: cfg.Recorder.MaxDiskMB,
  121. OutputDir: cfg.Recorder.OutputDir,
  122. ClassFilter: cfg.Recorder.ClassFilter,
  123. RingSeconds: cfg.Recorder.RingSeconds,
  124. DeemphasisUs: cfg.Recorder.DeemphasisUs,
  125. ExtractionTaps: cfg.Recorder.ExtractionTaps,
  126. ExtractionBwMult: cfg.Recorder.ExtractionBwMult,
  127. }, cfg.CenterHz, decodeMap, telemetryCollector)
  128. defer recMgr.Close()
  129. sigSnap := &signalSnapshot{}
  130. extractMgr := &extractionManager{}
  131. defer extractMgr.reset()
  132. phaseSnap := &phaseSnapshot{}
  133. go runDSP(ctx, srcMgr, cfg, det, window, h, eventFile, eventMu, dspUpdates, gpuState, recMgr, sigSnap, extractMgr, phaseSnap, telemetryCollector)
  134. server := newHTTPServer(cfg.WebAddr, cfg.WebRoot, h, cfgPath, cfgManager, srcMgr, dspUpdates, gpuState, recMgr, sigSnap, eventMu, phaseSnap, telemetryCollector)
  135. go func() {
  136. log.Printf("web listening on %s", cfg.WebAddr)
  137. if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  138. log.Fatalf("server: %v", err)
  139. }
  140. }()
  141. stop := make(chan os.Signal, 1)
  142. signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
  143. <-stop
  144. shutdownServer(server)
  145. }