Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

404 rindas
13KB

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