Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

319 wiersze
9.7KB

  1. package runtime
  2. import (
  3. "errors"
  4. "math"
  5. "strings"
  6. "sync"
  7. "sdr-visual-suite/internal/config"
  8. )
  9. type ConfigUpdate struct {
  10. CenterHz *float64 `json:"center_hz"`
  11. SampleRate *int `json:"sample_rate"`
  12. FFTSize *int `json:"fft_size"`
  13. GainDb *float64 `json:"gain_db"`
  14. TunerBwKHz *int `json:"tuner_bw_khz"`
  15. UseGPUFFT *bool `json:"use_gpu_fft"`
  16. ClassifierMode *string `json:"classifier_mode"`
  17. Detector *DetectorUpdate `json:"detector"`
  18. Recorder *RecorderUpdate `json:"recorder"`
  19. }
  20. type DetectorUpdate struct {
  21. ThresholdDb *float64 `json:"threshold_db"`
  22. MinDuration *int `json:"min_duration_ms"`
  23. HoldMs *int `json:"hold_ms"`
  24. EmaAlpha *float64 `json:"ema_alpha"`
  25. HysteresisDb *float64 `json:"hysteresis_db"`
  26. MinStableFrames *int `json:"min_stable_frames"`
  27. GapToleranceMs *int `json:"gap_tolerance_ms"`
  28. CFARMode *string `json:"cfar_mode"`
  29. CFARGuardHz *float64 `json:"cfar_guard_hz"`
  30. CFARTrainHz *float64 `json:"cfar_train_hz"`
  31. CFARGuardCells *int `json:"cfar_guard_cells"`
  32. CFARTrainCells *int `json:"cfar_train_cells"`
  33. CFARRank *int `json:"cfar_rank"`
  34. CFARScaleDb *float64 `json:"cfar_scale_db"`
  35. CFARWrapAround *bool `json:"cfar_wrap_around"`
  36. EdgeMarginDb *float64 `json:"edge_margin_db"`
  37. MergeGapHz *float64 `json:"merge_gap_hz"`
  38. ClassHistorySize *int `json:"class_history_size"`
  39. ClassSwitchRatio *float64 `json:"class_switch_ratio"`
  40. }
  41. type SettingsUpdate struct {
  42. AGC *bool `json:"agc"`
  43. DCBlock *bool `json:"dc_block"`
  44. IQBalance *bool `json:"iq_balance"`
  45. }
  46. type RecorderUpdate struct {
  47. Enabled *bool `json:"enabled"`
  48. MinSNRDb *float64 `json:"min_snr_db"`
  49. MinDuration *string `json:"min_duration"`
  50. MaxDuration *string `json:"max_duration"`
  51. PrerollMs *int `json:"preroll_ms"`
  52. RecordIQ *bool `json:"record_iq"`
  53. RecordAudio *bool `json:"record_audio"`
  54. AutoDemod *bool `json:"auto_demod"`
  55. AutoDecode *bool `json:"auto_decode"`
  56. MaxDiskMB *int `json:"max_disk_mb"`
  57. OutputDir *string `json:"output_dir"`
  58. ClassFilter *[]string `json:"class_filter"`
  59. RingSeconds *int `json:"ring_seconds"`
  60. }
  61. type Manager struct {
  62. mu sync.RWMutex
  63. cfg config.Config
  64. }
  65. func New(cfg config.Config) *Manager {
  66. return &Manager{cfg: cfg}
  67. }
  68. func (m *Manager) Snapshot() config.Config {
  69. m.mu.RLock()
  70. defer m.mu.RUnlock()
  71. return m.cfg
  72. }
  73. func (m *Manager) Replace(cfg config.Config) {
  74. m.mu.Lock()
  75. defer m.mu.Unlock()
  76. m.cfg = cfg
  77. }
  78. func (m *Manager) ApplyConfig(update ConfigUpdate) (config.Config, error) {
  79. m.mu.Lock()
  80. defer m.mu.Unlock()
  81. next := m.cfg
  82. if update.CenterHz != nil {
  83. if *update.CenterHz < 1e3 || *update.CenterHz > 2e9 {
  84. return m.cfg, errors.New("center_hz out of range")
  85. }
  86. next.CenterHz = *update.CenterHz
  87. }
  88. if update.SampleRate != nil {
  89. if *update.SampleRate <= 0 {
  90. return m.cfg, errors.New("sample_rate must be > 0")
  91. }
  92. next.SampleRate = *update.SampleRate
  93. }
  94. if update.FFTSize != nil {
  95. if *update.FFTSize <= 0 {
  96. return m.cfg, errors.New("fft_size must be > 0")
  97. }
  98. if *update.FFTSize&(*update.FFTSize-1) != 0 {
  99. return m.cfg, errors.New("fft_size must be a power of 2")
  100. }
  101. next.FFTSize = *update.FFTSize
  102. next.Surveillance.AnalysisFFTSize = *update.FFTSize
  103. }
  104. if update.GainDb != nil {
  105. next.GainDb = *update.GainDb
  106. }
  107. if update.TunerBwKHz != nil {
  108. if *update.TunerBwKHz <= 0 {
  109. return m.cfg, errors.New("tuner_bw_khz must be > 0")
  110. }
  111. next.TunerBwKHz = *update.TunerBwKHz
  112. }
  113. if update.UseGPUFFT != nil {
  114. next.UseGPUFFT = *update.UseGPUFFT
  115. }
  116. if update.ClassifierMode != nil {
  117. mode := *update.ClassifierMode
  118. switch mode {
  119. case "rule", "math", "combined":
  120. next.ClassifierMode = mode
  121. default:
  122. return m.cfg, errors.New("classifier_mode must be rule, math, or combined")
  123. }
  124. }
  125. if update.Detector != nil {
  126. if update.Detector.ThresholdDb != nil {
  127. next.Detector.ThresholdDb = *update.Detector.ThresholdDb
  128. }
  129. if update.Detector.MinDuration != nil {
  130. if *update.Detector.MinDuration <= 0 {
  131. return m.cfg, errors.New("min_duration_ms must be > 0")
  132. }
  133. next.Detector.MinDurationMs = *update.Detector.MinDuration
  134. }
  135. if update.Detector.HoldMs != nil {
  136. if *update.Detector.HoldMs <= 0 {
  137. return m.cfg, errors.New("hold_ms must be > 0")
  138. }
  139. next.Detector.HoldMs = *update.Detector.HoldMs
  140. }
  141. if update.Detector.EmaAlpha != nil {
  142. v := *update.Detector.EmaAlpha
  143. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 || v > 1 {
  144. return m.cfg, errors.New("ema_alpha must be between 0 and 1")
  145. }
  146. next.Detector.EmaAlpha = v
  147. }
  148. if update.Detector.HysteresisDb != nil {
  149. v := *update.Detector.HysteresisDb
  150. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 {
  151. return m.cfg, errors.New("hysteresis_db must be >= 0")
  152. }
  153. next.Detector.HysteresisDb = v
  154. }
  155. if update.Detector.MinStableFrames != nil {
  156. if *update.Detector.MinStableFrames < 1 {
  157. return m.cfg, errors.New("min_stable_frames must be >= 1")
  158. }
  159. next.Detector.MinStableFrames = *update.Detector.MinStableFrames
  160. }
  161. if update.Detector.GapToleranceMs != nil {
  162. if *update.Detector.GapToleranceMs < 0 {
  163. return m.cfg, errors.New("gap_tolerance_ms must be >= 0")
  164. }
  165. next.Detector.GapToleranceMs = *update.Detector.GapToleranceMs
  166. }
  167. if update.Detector.CFARMode != nil {
  168. mode := strings.ToUpper(strings.TrimSpace(*update.Detector.CFARMode))
  169. switch mode {
  170. case "OFF", "CA", "OS", "GOSCA", "CASO":
  171. next.Detector.CFARMode = mode
  172. default:
  173. return m.cfg, errors.New("cfar_mode must be OFF, CA, OS, GOSCA, or CASO")
  174. }
  175. }
  176. if update.Detector.CFARWrapAround != nil {
  177. next.Detector.CFARWrapAround = *update.Detector.CFARWrapAround
  178. }
  179. if update.Detector.CFARGuardHz != nil {
  180. if *update.Detector.CFARGuardHz < 0 {
  181. return m.cfg, errors.New("cfar_guard_hz must be >= 0")
  182. }
  183. next.Detector.CFARGuardHz = *update.Detector.CFARGuardHz
  184. }
  185. if update.Detector.CFARTrainHz != nil {
  186. if *update.Detector.CFARTrainHz <= 0 {
  187. return m.cfg, errors.New("cfar_train_hz must be > 0")
  188. }
  189. next.Detector.CFARTrainHz = *update.Detector.CFARTrainHz
  190. }
  191. if update.Detector.CFARGuardCells != nil {
  192. if *update.Detector.CFARGuardCells < 0 {
  193. return m.cfg, errors.New("cfar_guard_cells must be >= 0")
  194. }
  195. next.Detector.CFARGuardCells = *update.Detector.CFARGuardCells
  196. }
  197. if update.Detector.CFARTrainCells != nil {
  198. if *update.Detector.CFARTrainCells <= 0 {
  199. return m.cfg, errors.New("cfar_train_cells must be > 0")
  200. }
  201. next.Detector.CFARTrainCells = *update.Detector.CFARTrainCells
  202. }
  203. if update.Detector.CFARRank != nil {
  204. if *update.Detector.CFARRank <= 0 {
  205. return m.cfg, errors.New("cfar_rank must be > 0")
  206. }
  207. if next.Detector.CFARTrainCells > 0 && *update.Detector.CFARRank > 2*next.Detector.CFARTrainCells {
  208. return m.cfg, errors.New("cfar_rank must be <= 2 * cfar_train_cells")
  209. }
  210. next.Detector.CFARRank = *update.Detector.CFARRank
  211. }
  212. if update.Detector.CFARScaleDb != nil {
  213. next.Detector.CFARScaleDb = *update.Detector.CFARScaleDb
  214. }
  215. if update.Detector.EdgeMarginDb != nil {
  216. v := *update.Detector.EdgeMarginDb
  217. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 {
  218. return m.cfg, errors.New("edge_margin_db must be >= 0")
  219. }
  220. next.Detector.EdgeMarginDb = v
  221. }
  222. if update.Detector.MergeGapHz != nil {
  223. v := *update.Detector.MergeGapHz
  224. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 {
  225. return m.cfg, errors.New("merge_gap_hz must be >= 0")
  226. }
  227. next.Detector.MergeGapHz = v
  228. }
  229. if update.Detector.ClassHistorySize != nil {
  230. if *update.Detector.ClassHistorySize < 1 {
  231. return m.cfg, errors.New("class_history_size must be >= 1")
  232. }
  233. next.Detector.ClassHistorySize = *update.Detector.ClassHistorySize
  234. }
  235. if update.Detector.ClassSwitchRatio != nil {
  236. v := *update.Detector.ClassSwitchRatio
  237. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0.1 || v > 1.0 {
  238. return m.cfg, errors.New("class_switch_ratio must be between 0.1 and 1.0")
  239. }
  240. next.Detector.ClassSwitchRatio = v
  241. }
  242. }
  243. if update.Recorder != nil {
  244. if update.Recorder.Enabled != nil {
  245. next.Recorder.Enabled = *update.Recorder.Enabled
  246. }
  247. if update.Recorder.MinSNRDb != nil {
  248. next.Recorder.MinSNRDb = *update.Recorder.MinSNRDb
  249. }
  250. if update.Recorder.MinDuration != nil {
  251. next.Recorder.MinDuration = *update.Recorder.MinDuration
  252. }
  253. if update.Recorder.MaxDuration != nil {
  254. next.Recorder.MaxDuration = *update.Recorder.MaxDuration
  255. }
  256. if update.Recorder.PrerollMs != nil {
  257. next.Recorder.PrerollMs = *update.Recorder.PrerollMs
  258. }
  259. if update.Recorder.RecordIQ != nil {
  260. next.Recorder.RecordIQ = *update.Recorder.RecordIQ
  261. }
  262. if update.Recorder.RecordAudio != nil {
  263. next.Recorder.RecordAudio = *update.Recorder.RecordAudio
  264. }
  265. if update.Recorder.AutoDemod != nil {
  266. next.Recorder.AutoDemod = *update.Recorder.AutoDemod
  267. }
  268. if update.Recorder.AutoDecode != nil {
  269. next.Recorder.AutoDecode = *update.Recorder.AutoDecode
  270. }
  271. if update.Recorder.MaxDiskMB != nil {
  272. next.Recorder.MaxDiskMB = *update.Recorder.MaxDiskMB
  273. }
  274. if update.Recorder.OutputDir != nil {
  275. next.Recorder.OutputDir = *update.Recorder.OutputDir
  276. }
  277. if update.Recorder.ClassFilter != nil {
  278. next.Recorder.ClassFilter = *update.Recorder.ClassFilter
  279. }
  280. if update.Recorder.RingSeconds != nil {
  281. next.Recorder.RingSeconds = *update.Recorder.RingSeconds
  282. }
  283. }
  284. m.cfg = next
  285. return m.cfg, nil
  286. }
  287. func (m *Manager) ApplySettings(update SettingsUpdate) (config.Config, error) {
  288. m.mu.Lock()
  289. defer m.mu.Unlock()
  290. next := m.cfg
  291. if update.AGC != nil {
  292. next.AGC = *update.AGC
  293. }
  294. if update.DCBlock != nil {
  295. next.DCBlock = *update.DCBlock
  296. }
  297. if update.IQBalance != nil {
  298. next.IQBalance = *update.IQBalance
  299. }
  300. m.cfg = next
  301. return m.cfg, nil
  302. }