Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

195 строки
4.8KB

  1. package detector
  2. import (
  3. "testing"
  4. "time"
  5. "sdr-visual-suite/internal/config"
  6. )
  7. func TestDetectorCreatesEvent(t *testing.T) {
  8. d := New(config.DetectorConfig{
  9. ThresholdDb: -10,
  10. MinDurationMs: 1,
  11. HoldMs: 10,
  12. EmaAlpha: 0.2,
  13. HysteresisDb: 3,
  14. MinStableFrames: 1,
  15. GapToleranceMs: 10,
  16. CFARMode: "OFF",
  17. CFARGuardCells: 2,
  18. CFARTrainCells: 16,
  19. CFARRank: 24,
  20. CFARScaleDb: 6,
  21. CFARWrapAround: true,
  22. }, 1000, 10)
  23. center := 0.0
  24. spectrum := []float64{-30, -30, -30, -5, -5, -30, -30, -30, -30, -30}
  25. now := time.Now()
  26. finished, signals := d.Process(now, spectrum, center)
  27. if len(finished) != 0 {
  28. t.Fatalf("expected no finished events yet")
  29. }
  30. if len(signals) != 1 {
  31. t.Fatalf("expected 1 signal, got %d", len(signals))
  32. }
  33. if signals[0].BWHz <= 0 {
  34. t.Fatalf("expected bandwidth > 0")
  35. }
  36. _, _ = d.Process(now.Add(5*time.Millisecond), spectrum, center)
  37. now2 := now.Add(30 * time.Millisecond)
  38. noSignal := make([]float64, len(spectrum))
  39. for i := range noSignal {
  40. noSignal[i] = -100
  41. }
  42. finished, _ = d.Process(now2, noSignal, center)
  43. if len(finished) != 1 {
  44. t.Fatalf("expected 1 finished event, got %d", len(finished))
  45. }
  46. if finished[0].Bandwidth <= 0 {
  47. t.Fatalf("event bandwidth not set")
  48. }
  49. }
  50. func TestSignalBandwidthExpansion(t *testing.T) {
  51. sampleRate := 2048000
  52. fftSize := 2048
  53. cfg := config.DetectorConfig{
  54. ThresholdDb: -20,
  55. MinDurationMs: 1,
  56. HoldMs: 10,
  57. EmaAlpha: 1.0,
  58. HysteresisDb: 3,
  59. MinStableFrames: 1,
  60. GapToleranceMs: 10,
  61. CFARMode: "OFF",
  62. CFARGuardCells: 2,
  63. CFARTrainCells: 8,
  64. CFARRank: 12,
  65. CFARScaleDb: 6,
  66. CFARWrapAround: true,
  67. EdgeMarginDb: 3.0,
  68. MaxSignalBwHz: 150000,
  69. MergeGapHz: 5000,
  70. }
  71. d := New(cfg, sampleRate, fftSize)
  72. spectrum := make([]float64, fftSize)
  73. for i := range spectrum {
  74. spectrum[i] = -100
  75. }
  76. for i := 1000; i <= 1012; i++ {
  77. spectrum[i] = -20
  78. }
  79. for j := 1; j <= 5; j++ {
  80. level := -20 - float64(j)*3
  81. if 1000-j >= 0 {
  82. spectrum[1000-j] = level
  83. }
  84. if 1012+j < fftSize {
  85. spectrum[1012+j] = level
  86. }
  87. }
  88. now := time.Now()
  89. _, signals := d.Process(now, spectrum, 434e6)
  90. if len(signals) == 0 {
  91. t.Fatal("no signals detected")
  92. }
  93. sig := signals[0]
  94. expectedMinBW := 18.0 * 1000
  95. if sig.BWHz < expectedMinBW {
  96. t.Errorf("BW too narrow: got %.0f Hz, want >= %.0f Hz (FirstBin=%d LastBin=%d)", sig.BWHz, expectedMinBW, sig.FirstBin, sig.LastBin)
  97. }
  98. }
  99. func TestMergeGap(t *testing.T) {
  100. sampleRate := 2048000
  101. fftSize := 2048
  102. cfg := config.DetectorConfig{
  103. ThresholdDb: -20,
  104. MinDurationMs: 1,
  105. HoldMs: 10,
  106. EmaAlpha: 1.0,
  107. HysteresisDb: 3,
  108. MinStableFrames: 1,
  109. GapToleranceMs: 10,
  110. CFARMode: "OFF",
  111. MergeGapHz: 5000,
  112. }
  113. d := New(cfg, sampleRate, fftSize)
  114. spectrum := make([]float64, fftSize)
  115. for i := range spectrum {
  116. spectrum[i] = -100
  117. }
  118. for i := 500; i <= 505; i++ {
  119. spectrum[i] = -20
  120. }
  121. for i := 509; i <= 514; i++ {
  122. spectrum[i] = -20
  123. }
  124. now := time.Now()
  125. _, signals := d.Process(now, spectrum, 434e6)
  126. if len(signals) != 1 {
  127. t.Errorf("expected 1 merged signal, got %d", len(signals))
  128. }
  129. if len(signals) == 1 && signals[0].BWHz < 14000 {
  130. t.Errorf("merged BW too narrow: %.0f Hz", signals[0].BWHz)
  131. }
  132. }
  133. func TestGuardTrainHzScaling(t *testing.T) {
  134. sampleRate := 2048000
  135. guardHz := 500.0
  136. trainHz := 5000.0
  137. cfg := config.DetectorConfig{
  138. ThresholdDb: -20,
  139. MinDurationMs: 1,
  140. HoldMs: 10,
  141. EmaAlpha: 1.0,
  142. HysteresisDb: 3,
  143. MinStableFrames: 1,
  144. GapToleranceMs: 10,
  145. CFARMode: "OFF",
  146. CFARGuardHz: guardHz,
  147. CFARTrainHz: trainHz,
  148. CFARScaleDb: 6,
  149. CFARWrapAround: true,
  150. }
  151. d1 := New(cfg, sampleRate, 2048)
  152. d2 := New(cfg, sampleRate, 65536)
  153. spec2048 := makeSignalSpectrum(2048, sampleRate, 500e3, 12e3, -20, -100)
  154. spec65536 := makeSignalSpectrum(65536, sampleRate, 500e3, 12e3, -20, -100)
  155. now := time.Now()
  156. _, sig1 := d1.Process(now, spec2048, 434e6)
  157. _, sig2 := d2.Process(now, spec65536, 434e6)
  158. if len(sig1) == 0 || len(sig2) == 0 {
  159. t.Fatalf("detection failed: fft2048=%d signals, fft65536=%d signals", len(sig1), len(sig2))
  160. }
  161. bw1 := sig1[0].BWHz
  162. bw2 := sig2[0].BWHz
  163. ratio := bw1 / bw2
  164. if ratio < 0.8 || ratio > 1.2 {
  165. t.Errorf("BW mismatch across FFT sizes: fft2048=%.0f Hz, fft65536=%.0f Hz", bw1, bw2)
  166. }
  167. }
  168. func makeSignalSpectrum(fftSize int, sampleRate int, offsetHz float64, bwHz float64, signalDb float64, noiseDb float64) []float64 {
  169. spectrum := make([]float64, fftSize)
  170. for i := range spectrum {
  171. spectrum[i] = noiseDb
  172. }
  173. binWidth := float64(sampleRate) / float64(fftSize)
  174. centerBin := int(float64(fftSize)/2 + offsetHz/binWidth)
  175. halfBins := int((bwHz / 2) / binWidth)
  176. if halfBins < 1 {
  177. halfBins = 1
  178. }
  179. for i := centerBin - halfBins; i <= centerBin+halfBins; i++ {
  180. if i >= 0 && i < fftSize {
  181. spectrum[i] = signalDb
  182. }
  183. }
  184. return spectrum
  185. }