Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

248 Zeilen
7.4KB

  1. package config
  2. import (
  3. "math"
  4. "os"
  5. "time"
  6. "gopkg.in/yaml.v3"
  7. )
  8. type Band struct {
  9. Name string `yaml:"name" json:"name"`
  10. StartHz float64 `yaml:"start_hz" json:"start_hz"`
  11. EndHz float64 `yaml:"end_hz" json:"end_hz"`
  12. }
  13. type DetectorConfig struct {
  14. ThresholdDb float64 `yaml:"threshold_db" json:"threshold_db"`
  15. MinDurationMs int `yaml:"min_duration_ms" json:"min_duration_ms"`
  16. HoldMs int `yaml:"hold_ms" json:"hold_ms"`
  17. EmaAlpha float64 `yaml:"ema_alpha" json:"ema_alpha"`
  18. HysteresisDb float64 `yaml:"hysteresis_db" json:"hysteresis_db"`
  19. MinStableFrames int `yaml:"min_stable_frames" json:"min_stable_frames"`
  20. GapToleranceMs int `yaml:"gap_tolerance_ms" json:"gap_tolerance_ms"`
  21. CFARMode string `yaml:"cfar_mode" json:"cfar_mode"`
  22. CFARGuardCells int `yaml:"cfar_guard_cells" json:"cfar_guard_cells"`
  23. CFARTrainCells int `yaml:"cfar_train_cells" json:"cfar_train_cells"`
  24. CFARRank int `yaml:"cfar_rank" json:"cfar_rank"`
  25. CFARScaleDb float64 `yaml:"cfar_scale_db" json:"cfar_scale_db"`
  26. CFARWrapAround bool `yaml:"cfar_wrap_around" json:"cfar_wrap_around"`
  27. EdgeMarginDb float64 `yaml:"edge_margin_db" json:"edge_margin_db"`
  28. MaxSignalBwHz float64 `yaml:"max_signal_bw_hz" json:"max_signal_bw_hz"`
  29. MergeGapHz float64 `yaml:"merge_gap_hz" json:"merge_gap_hz"`
  30. // Deprecated (backward compatibility)
  31. CFAREnabled *bool `yaml:"cfar_enabled,omitempty" json:"cfar_enabled,omitempty"`
  32. }
  33. type RecorderConfig struct {
  34. Enabled bool `yaml:"enabled" json:"enabled"`
  35. MinSNRDb float64 `yaml:"min_snr_db" json:"min_snr_db"`
  36. MinDuration string `yaml:"min_duration" json:"min_duration"`
  37. MaxDuration string `yaml:"max_duration" json:"max_duration"`
  38. PrerollMs int `yaml:"preroll_ms" json:"preroll_ms"`
  39. RecordIQ bool `yaml:"record_iq" json:"record_iq"`
  40. RecordAudio bool `yaml:"record_audio" json:"record_audio"`
  41. AutoDemod bool `yaml:"auto_demod" json:"auto_demod"`
  42. AutoDecode bool `yaml:"auto_decode" json:"auto_decode"`
  43. MaxDiskMB int `yaml:"max_disk_mb" json:"max_disk_mb"`
  44. OutputDir string `yaml:"output_dir" json:"output_dir"`
  45. ClassFilter []string `yaml:"class_filter" json:"class_filter"`
  46. RingSeconds int `yaml:"ring_seconds" json:"ring_seconds"`
  47. }
  48. type DecoderConfig struct {
  49. FT8Cmd string `yaml:"ft8_cmd" json:"ft8_cmd"`
  50. WSPRCmd string `yaml:"wspr_cmd" json:"wspr_cmd"`
  51. DMRCmd string `yaml:"dmr_cmd" json:"dmr_cmd"`
  52. DStarCmd string `yaml:"dstar_cmd" json:"dstar_cmd"`
  53. FSKCmd string `yaml:"fsk_cmd" json:"fsk_cmd"`
  54. PSKCmd string `yaml:"psk_cmd" json:"psk_cmd"`
  55. }
  56. type Config struct {
  57. Bands []Band `yaml:"bands" json:"bands"`
  58. CenterHz float64 `yaml:"center_hz" json:"center_hz"`
  59. SampleRate int `yaml:"sample_rate" json:"sample_rate"`
  60. FFTSize int `yaml:"fft_size" json:"fft_size"`
  61. GainDb float64 `yaml:"gain_db" json:"gain_db"`
  62. TunerBwKHz int `yaml:"tuner_bw_khz" json:"tuner_bw_khz"`
  63. UseGPUFFT bool `yaml:"use_gpu_fft" json:"use_gpu_fft"`
  64. AGC bool `yaml:"agc" json:"agc"`
  65. DCBlock bool `yaml:"dc_block" json:"dc_block"`
  66. IQBalance bool `yaml:"iq_balance" json:"iq_balance"`
  67. Detector DetectorConfig `yaml:"detector" json:"detector"`
  68. Recorder RecorderConfig `yaml:"recorder" json:"recorder"`
  69. Decoder DecoderConfig `yaml:"decoder" json:"decoder"`
  70. WebAddr string `yaml:"web_addr" json:"web_addr"`
  71. EventPath string `yaml:"event_path" json:"event_path"`
  72. FrameRate int `yaml:"frame_rate" json:"frame_rate"`
  73. WaterfallLines int `yaml:"waterfall_lines" json:"waterfall_lines"`
  74. WebRoot string `yaml:"web_root" json:"web_root"`
  75. }
  76. func Default() Config {
  77. return Config{
  78. Bands: []Band{
  79. {Name: "example", StartHz: 99.5e6, EndHz: 100.5e6},
  80. },
  81. CenterHz: 100.0e6,
  82. SampleRate: 2_048_000,
  83. FFTSize: 2048,
  84. GainDb: 30,
  85. TunerBwKHz: 1536,
  86. UseGPUFFT: false,
  87. AGC: false,
  88. DCBlock: false,
  89. IQBalance: false,
  90. Detector: DetectorConfig{
  91. ThresholdDb: -20,
  92. MinDurationMs: 250,
  93. HoldMs: 500,
  94. EmaAlpha: 0.2,
  95. HysteresisDb: 3,
  96. MinStableFrames: 3,
  97. GapToleranceMs: 500,
  98. CFARMode: "GOSCA",
  99. CFARGuardCells: 3,
  100. CFARTrainCells: 24,
  101. CFARRank: 36,
  102. CFARScaleDb: 6,
  103. CFARWrapAround: true,
  104. EdgeMarginDb: 3.0,
  105. MaxSignalBwHz: 150000,
  106. MergeGapHz: 5000,
  107. },
  108. Recorder: RecorderConfig{
  109. Enabled: false,
  110. MinSNRDb: 10,
  111. MinDuration: "1s",
  112. MaxDuration: "300s",
  113. PrerollMs: 500,
  114. RecordIQ: true,
  115. RecordAudio: false,
  116. AutoDemod: true,
  117. AutoDecode: false,
  118. MaxDiskMB: 0,
  119. OutputDir: "data/recordings",
  120. RingSeconds: 8,
  121. },
  122. Decoder: DecoderConfig{},
  123. WebAddr: ":8080",
  124. EventPath: "data/events.jsonl",
  125. FrameRate: 15,
  126. WaterfallLines: 200,
  127. WebRoot: "web",
  128. }
  129. }
  130. func Load(path string) (Config, error) {
  131. cfg := Default()
  132. if b, err := os.ReadFile(autosavePath(path)); err == nil {
  133. if err := yaml.Unmarshal(b, &cfg); err == nil {
  134. return applyDefaults(cfg), nil
  135. }
  136. }
  137. b, err := os.ReadFile(path)
  138. if err != nil {
  139. return cfg, err
  140. }
  141. if err := yaml.Unmarshal(b, &cfg); err != nil {
  142. return cfg, err
  143. }
  144. return applyDefaults(cfg), nil
  145. }
  146. func applyDefaults(cfg Config) Config {
  147. if cfg.Detector.MinDurationMs <= 0 {
  148. cfg.Detector.MinDurationMs = 250
  149. }
  150. if cfg.Detector.HoldMs <= 0 {
  151. cfg.Detector.HoldMs = 500
  152. }
  153. if cfg.Detector.MinStableFrames <= 0 {
  154. cfg.Detector.MinStableFrames = 3
  155. }
  156. if cfg.Detector.GapToleranceMs <= 0 {
  157. cfg.Detector.GapToleranceMs = cfg.Detector.HoldMs
  158. }
  159. if cfg.Detector.CFARMode == "" {
  160. if cfg.Detector.CFAREnabled != nil {
  161. if *cfg.Detector.CFAREnabled {
  162. cfg.Detector.CFARMode = "OS"
  163. } else {
  164. cfg.Detector.CFARMode = "OFF"
  165. }
  166. } else {
  167. cfg.Detector.CFARMode = "GOSCA"
  168. }
  169. }
  170. if cfg.Detector.CFARGuardCells <= 0 {
  171. cfg.Detector.CFARGuardCells = 3
  172. }
  173. if cfg.Detector.CFARTrainCells <= 0 {
  174. cfg.Detector.CFARTrainCells = 24
  175. }
  176. if cfg.Detector.CFARRank <= 0 || cfg.Detector.CFARRank > 2*cfg.Detector.CFARTrainCells {
  177. cfg.Detector.CFARRank = int(math.Round(0.75 * float64(2*cfg.Detector.CFARTrainCells)))
  178. if cfg.Detector.CFARRank <= 0 {
  179. cfg.Detector.CFARRank = 1
  180. }
  181. }
  182. if cfg.Detector.CFARScaleDb <= 0 {
  183. cfg.Detector.CFARScaleDb = 6
  184. }
  185. if cfg.Detector.EdgeMarginDb <= 0 {
  186. cfg.Detector.EdgeMarginDb = 3.0
  187. }
  188. if cfg.Detector.MaxSignalBwHz <= 0 {
  189. cfg.Detector.MaxSignalBwHz = 150000
  190. }
  191. if cfg.Detector.MergeGapHz <= 0 {
  192. cfg.Detector.MergeGapHz = 5000
  193. }
  194. if cfg.FrameRate <= 0 {
  195. cfg.FrameRate = 15
  196. }
  197. if cfg.WaterfallLines <= 0 {
  198. cfg.WaterfallLines = 200
  199. }
  200. if cfg.WebRoot == "" {
  201. cfg.WebRoot = "web"
  202. }
  203. if cfg.WebAddr == "" {
  204. cfg.WebAddr = ":8080"
  205. }
  206. if cfg.EventPath == "" {
  207. cfg.EventPath = "data/events.jsonl"
  208. }
  209. if cfg.SampleRate <= 0 {
  210. cfg.SampleRate = 2_048_000
  211. }
  212. if cfg.FFTSize <= 0 {
  213. cfg.FFTSize = 2048
  214. }
  215. if cfg.TunerBwKHz <= 0 {
  216. cfg.TunerBwKHz = 1536
  217. }
  218. if cfg.CenterHz == 0 {
  219. cfg.CenterHz = 100.0e6
  220. }
  221. if cfg.Recorder.OutputDir == "" {
  222. cfg.Recorder.OutputDir = "data/recordings"
  223. }
  224. if cfg.Recorder.RingSeconds <= 0 {
  225. cfg.Recorder.RingSeconds = 8
  226. }
  227. return cfg
  228. }
  229. func (c Config) FrameInterval() time.Duration {
  230. fps := c.FrameRate
  231. if fps <= 0 {
  232. fps = 15
  233. }
  234. return time.Second / time.Duration(fps)
  235. }