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.

518 rindas
17KB

  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. Intent *string `json:"intent"`
  13. MonitorStartHz *float64 `json:"monitor_start_hz"`
  14. MonitorEndHz *float64 `json:"monitor_end_hz"`
  15. MonitorSpanHz *float64 `json:"monitor_span_hz"`
  16. SignalPriorities *[]string `json:"signal_priorities"`
  17. AutoRecordClasses *[]string `json:"auto_record_classes"`
  18. AutoDecodeClasses *[]string `json:"auto_decode_classes"`
  19. }
  20. type SurveillanceUpdate struct {
  21. AnalysisFFTSize *int `json:"analysis_fft_size"`
  22. FrameRate *int `json:"frame_rate"`
  23. Strategy *string `json:"strategy"`
  24. DisplayBins *int `json:"display_bins"`
  25. DisplayFPS *int `json:"display_fps"`
  26. DerivedDetection *string `json:"derived_detection"`
  27. }
  28. type RefinementUpdate struct {
  29. Enabled *bool `json:"enabled"`
  30. MaxConcurrent *int `json:"max_concurrent"`
  31. DetailFFTSize *int `json:"detail_fft_size"`
  32. MinCandidateSNRDb *float64 `json:"min_candidate_snr_db"`
  33. MinSpanHz *float64 `json:"min_span_hz"`
  34. MaxSpanHz *float64 `json:"max_span_hz"`
  35. AutoSpan *bool `json:"auto_span"`
  36. }
  37. type ResourcesUpdate struct {
  38. PreferGPU *bool `json:"prefer_gpu"`
  39. MaxRefinementJobs *int `json:"max_refinement_jobs"`
  40. MaxRecordingStreams *int `json:"max_recording_streams"`
  41. MaxDecodeJobs *int `json:"max_decode_jobs"`
  42. DecisionHoldMs *int `json:"decision_hold_ms"`
  43. }
  44. type ConfigUpdate struct {
  45. CenterHz *float64 `json:"center_hz"`
  46. SampleRate *int `json:"sample_rate"`
  47. FFTSize *int `json:"fft_size"`
  48. GainDb *float64 `json:"gain_db"`
  49. TunerBwKHz *int `json:"tuner_bw_khz"`
  50. UseGPUFFT *bool `json:"use_gpu_fft"`
  51. ClassifierMode *string `json:"classifier_mode"`
  52. Pipeline *PipelineUpdate `json:"pipeline"`
  53. Surveillance *SurveillanceUpdate `json:"surveillance"`
  54. Refinement *RefinementUpdate `json:"refinement"`
  55. Resources *ResourcesUpdate `json:"resources"`
  56. Detector *DetectorUpdate `json:"detector"`
  57. Recorder *RecorderUpdate `json:"recorder"`
  58. }
  59. type DetectorUpdate struct {
  60. ThresholdDb *float64 `json:"threshold_db"`
  61. MinDuration *int `json:"min_duration_ms"`
  62. HoldMs *int `json:"hold_ms"`
  63. EmaAlpha *float64 `json:"ema_alpha"`
  64. HysteresisDb *float64 `json:"hysteresis_db"`
  65. MinStableFrames *int `json:"min_stable_frames"`
  66. GapToleranceMs *int `json:"gap_tolerance_ms"`
  67. CFARMode *string `json:"cfar_mode"`
  68. CFARGuardHz *float64 `json:"cfar_guard_hz"`
  69. CFARTrainHz *float64 `json:"cfar_train_hz"`
  70. CFARGuardCells *int `json:"cfar_guard_cells"`
  71. CFARTrainCells *int `json:"cfar_train_cells"`
  72. CFARRank *int `json:"cfar_rank"`
  73. CFARScaleDb *float64 `json:"cfar_scale_db"`
  74. CFARWrapAround *bool `json:"cfar_wrap_around"`
  75. EdgeMarginDb *float64 `json:"edge_margin_db"`
  76. MergeGapHz *float64 `json:"merge_gap_hz"`
  77. ClassHistorySize *int `json:"class_history_size"`
  78. ClassSwitchRatio *float64 `json:"class_switch_ratio"`
  79. }
  80. type SettingsUpdate struct {
  81. AGC *bool `json:"agc"`
  82. DCBlock *bool `json:"dc_block"`
  83. IQBalance *bool `json:"iq_balance"`
  84. }
  85. type RecorderUpdate struct {
  86. Enabled *bool `json:"enabled"`
  87. MinSNRDb *float64 `json:"min_snr_db"`
  88. MinDuration *string `json:"min_duration"`
  89. MaxDuration *string `json:"max_duration"`
  90. PrerollMs *int `json:"preroll_ms"`
  91. RecordIQ *bool `json:"record_iq"`
  92. RecordAudio *bool `json:"record_audio"`
  93. AutoDemod *bool `json:"auto_demod"`
  94. AutoDecode *bool `json:"auto_decode"`
  95. MaxDiskMB *int `json:"max_disk_mb"`
  96. OutputDir *string `json:"output_dir"`
  97. ClassFilter *[]string `json:"class_filter"`
  98. RingSeconds *int `json:"ring_seconds"`
  99. }
  100. type Manager struct {
  101. mu sync.RWMutex
  102. cfg config.Config
  103. }
  104. func New(cfg config.Config) *Manager {
  105. return &Manager{cfg: cfg}
  106. }
  107. func (m *Manager) Snapshot() config.Config {
  108. m.mu.RLock()
  109. defer m.mu.RUnlock()
  110. return m.cfg
  111. }
  112. func (m *Manager) Replace(cfg config.Config) {
  113. m.mu.Lock()
  114. defer m.mu.Unlock()
  115. m.cfg = cfg
  116. }
  117. func (m *Manager) ApplyConfig(update ConfigUpdate) (config.Config, error) {
  118. m.mu.Lock()
  119. defer m.mu.Unlock()
  120. next := m.cfg
  121. if update.CenterHz != nil {
  122. if *update.CenterHz < 1e3 || *update.CenterHz > 2e9 {
  123. return m.cfg, errors.New("center_hz out of range")
  124. }
  125. next.CenterHz = *update.CenterHz
  126. }
  127. if update.SampleRate != nil {
  128. if *update.SampleRate <= 0 {
  129. return m.cfg, errors.New("sample_rate must be > 0")
  130. }
  131. next.SampleRate = *update.SampleRate
  132. }
  133. if update.FFTSize != nil {
  134. if *update.FFTSize <= 0 {
  135. return m.cfg, errors.New("fft_size must be > 0")
  136. }
  137. if *update.FFTSize&(*update.FFTSize-1) != 0 {
  138. return m.cfg, errors.New("fft_size must be a power of 2")
  139. }
  140. next.FFTSize = *update.FFTSize
  141. next.Surveillance.AnalysisFFTSize = *update.FFTSize
  142. }
  143. if update.GainDb != nil {
  144. next.GainDb = *update.GainDb
  145. }
  146. if update.TunerBwKHz != nil {
  147. if *update.TunerBwKHz <= 0 {
  148. return m.cfg, errors.New("tuner_bw_khz must be > 0")
  149. }
  150. next.TunerBwKHz = *update.TunerBwKHz
  151. }
  152. if update.UseGPUFFT != nil {
  153. next.UseGPUFFT = *update.UseGPUFFT
  154. }
  155. if update.ClassifierMode != nil {
  156. mode := *update.ClassifierMode
  157. switch mode {
  158. case "rule", "math", "combined":
  159. next.ClassifierMode = mode
  160. default:
  161. return m.cfg, errors.New("classifier_mode must be rule, math, or combined")
  162. }
  163. }
  164. if update.Pipeline != nil {
  165. if update.Pipeline.Mode != nil {
  166. next.Pipeline.Mode = *update.Pipeline.Mode
  167. }
  168. if update.Pipeline.Profile != nil {
  169. next.Pipeline.Profile = *update.Pipeline.Profile
  170. }
  171. if update.Pipeline.Intent != nil {
  172. next.Pipeline.Goals.Intent = *update.Pipeline.Intent
  173. }
  174. if update.Pipeline.MonitorStartHz != nil {
  175. next.Pipeline.Goals.MonitorStartHz = *update.Pipeline.MonitorStartHz
  176. }
  177. if update.Pipeline.MonitorEndHz != nil {
  178. next.Pipeline.Goals.MonitorEndHz = *update.Pipeline.MonitorEndHz
  179. }
  180. if update.Pipeline.MonitorSpanHz != nil {
  181. if *update.Pipeline.MonitorSpanHz <= 0 {
  182. return m.cfg, errors.New("monitor_span_hz must be > 0")
  183. }
  184. next.Pipeline.Goals.MonitorSpanHz = *update.Pipeline.MonitorSpanHz
  185. }
  186. if update.Pipeline.SignalPriorities != nil {
  187. next.Pipeline.Goals.SignalPriorities = append([]string(nil), (*update.Pipeline.SignalPriorities)...)
  188. }
  189. if update.Pipeline.AutoRecordClasses != nil {
  190. next.Pipeline.Goals.AutoRecordClasses = append([]string(nil), (*update.Pipeline.AutoRecordClasses)...)
  191. }
  192. if update.Pipeline.AutoDecodeClasses != nil {
  193. next.Pipeline.Goals.AutoDecodeClasses = append([]string(nil), (*update.Pipeline.AutoDecodeClasses)...)
  194. }
  195. if next.Pipeline.Goals.MonitorStartHz != 0 && next.Pipeline.Goals.MonitorEndHz != 0 && next.Pipeline.Goals.MonitorEndHz <= next.Pipeline.Goals.MonitorStartHz {
  196. return m.cfg, errors.New("monitor_end_hz must be > monitor_start_hz")
  197. }
  198. if next.Pipeline.Goals.MonitorSpanHz <= 0 && next.Pipeline.Goals.MonitorStartHz != 0 && next.Pipeline.Goals.MonitorEndHz != 0 && next.Pipeline.Goals.MonitorEndHz > next.Pipeline.Goals.MonitorStartHz {
  199. next.Pipeline.Goals.MonitorSpanHz = next.Pipeline.Goals.MonitorEndHz - next.Pipeline.Goals.MonitorStartHz
  200. }
  201. }
  202. if update.Surveillance != nil {
  203. if update.Surveillance.AnalysisFFTSize != nil {
  204. v := *update.Surveillance.AnalysisFFTSize
  205. if v <= 0 {
  206. return m.cfg, errors.New("surveillance.analysis_fft_size must be > 0")
  207. }
  208. if v&(v-1) != 0 {
  209. return m.cfg, errors.New("surveillance.analysis_fft_size must be a power of 2")
  210. }
  211. next.Surveillance.AnalysisFFTSize = v
  212. next.FFTSize = v
  213. }
  214. if update.Surveillance.FrameRate != nil {
  215. v := *update.Surveillance.FrameRate
  216. if v <= 0 {
  217. return m.cfg, errors.New("surveillance.frame_rate must be > 0")
  218. }
  219. next.Surveillance.FrameRate = v
  220. next.FrameRate = v
  221. }
  222. if update.Surveillance.Strategy != nil {
  223. next.Surveillance.Strategy = *update.Surveillance.Strategy
  224. }
  225. if update.Surveillance.DisplayBins != nil {
  226. v := *update.Surveillance.DisplayBins
  227. if v <= 0 {
  228. return m.cfg, errors.New("surveillance.display_bins must be > 0")
  229. }
  230. next.Surveillance.DisplayBins = v
  231. }
  232. if update.Surveillance.DisplayFPS != nil {
  233. v := *update.Surveillance.DisplayFPS
  234. if v <= 0 {
  235. return m.cfg, errors.New("surveillance.display_fps must be > 0")
  236. }
  237. next.Surveillance.DisplayFPS = v
  238. }
  239. if update.Surveillance.DerivedDetection != nil {
  240. mode := strings.ToLower(strings.TrimSpace(*update.Surveillance.DerivedDetection))
  241. switch mode {
  242. case "auto", "on", "off", "true", "false", "enabled", "disabled", "enable", "disable":
  243. next.Surveillance.DerivedDetection = mode
  244. default:
  245. return m.cfg, errors.New("surveillance.derived_detection must be auto, on, or off")
  246. }
  247. }
  248. }
  249. if update.Refinement != nil {
  250. if update.Refinement.Enabled != nil {
  251. next.Refinement.Enabled = *update.Refinement.Enabled
  252. }
  253. if update.Refinement.MaxConcurrent != nil {
  254. if *update.Refinement.MaxConcurrent <= 0 {
  255. return m.cfg, errors.New("refinement.max_concurrent must be > 0")
  256. }
  257. next.Refinement.MaxConcurrent = *update.Refinement.MaxConcurrent
  258. }
  259. if update.Refinement.DetailFFTSize != nil {
  260. v := *update.Refinement.DetailFFTSize
  261. if v <= 0 {
  262. return m.cfg, errors.New("refinement.detail_fft_size must be > 0")
  263. }
  264. if v&(v-1) != 0 {
  265. return m.cfg, errors.New("refinement.detail_fft_size must be a power of 2")
  266. }
  267. next.Refinement.DetailFFTSize = v
  268. }
  269. if update.Refinement.MinCandidateSNRDb != nil {
  270. next.Refinement.MinCandidateSNRDb = *update.Refinement.MinCandidateSNRDb
  271. }
  272. if update.Refinement.MinSpanHz != nil {
  273. if *update.Refinement.MinSpanHz < 0 {
  274. return m.cfg, errors.New("refinement.min_span_hz must be >= 0")
  275. }
  276. next.Refinement.MinSpanHz = *update.Refinement.MinSpanHz
  277. }
  278. if update.Refinement.MaxSpanHz != nil {
  279. if *update.Refinement.MaxSpanHz < 0 {
  280. return m.cfg, errors.New("refinement.max_span_hz must be >= 0")
  281. }
  282. next.Refinement.MaxSpanHz = *update.Refinement.MaxSpanHz
  283. }
  284. if update.Refinement.AutoSpan != nil {
  285. next.Refinement.AutoSpan = update.Refinement.AutoSpan
  286. }
  287. if next.Refinement.MaxSpanHz > 0 && next.Refinement.MinSpanHz > next.Refinement.MaxSpanHz {
  288. return m.cfg, errors.New("refinement.min_span_hz must be <= refinement.max_span_hz")
  289. }
  290. }
  291. if update.Resources != nil {
  292. if update.Resources.PreferGPU != nil {
  293. next.Resources.PreferGPU = *update.Resources.PreferGPU
  294. }
  295. if update.Resources.MaxRefinementJobs != nil {
  296. if *update.Resources.MaxRefinementJobs <= 0 {
  297. return m.cfg, errors.New("resources.max_refinement_jobs must be > 0")
  298. }
  299. next.Resources.MaxRefinementJobs = *update.Resources.MaxRefinementJobs
  300. }
  301. if update.Resources.MaxRecordingStreams != nil {
  302. if *update.Resources.MaxRecordingStreams <= 0 {
  303. return m.cfg, errors.New("resources.max_recording_streams must be > 0")
  304. }
  305. next.Resources.MaxRecordingStreams = *update.Resources.MaxRecordingStreams
  306. }
  307. if update.Resources.MaxDecodeJobs != nil {
  308. if *update.Resources.MaxDecodeJobs <= 0 {
  309. return m.cfg, errors.New("resources.max_decode_jobs must be > 0")
  310. }
  311. next.Resources.MaxDecodeJobs = *update.Resources.MaxDecodeJobs
  312. }
  313. if update.Resources.DecisionHoldMs != nil {
  314. if *update.Resources.DecisionHoldMs < 0 {
  315. return m.cfg, errors.New("resources.decision_hold_ms must be >= 0")
  316. }
  317. next.Resources.DecisionHoldMs = *update.Resources.DecisionHoldMs
  318. }
  319. }
  320. if update.Detector != nil {
  321. if update.Detector.ThresholdDb != nil {
  322. next.Detector.ThresholdDb = *update.Detector.ThresholdDb
  323. }
  324. if update.Detector.MinDuration != nil {
  325. if *update.Detector.MinDuration <= 0 {
  326. return m.cfg, errors.New("min_duration_ms must be > 0")
  327. }
  328. next.Detector.MinDurationMs = *update.Detector.MinDuration
  329. }
  330. if update.Detector.HoldMs != nil {
  331. if *update.Detector.HoldMs <= 0 {
  332. return m.cfg, errors.New("hold_ms must be > 0")
  333. }
  334. next.Detector.HoldMs = *update.Detector.HoldMs
  335. }
  336. if update.Detector.EmaAlpha != nil {
  337. v := *update.Detector.EmaAlpha
  338. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 || v > 1 {
  339. return m.cfg, errors.New("ema_alpha must be between 0 and 1")
  340. }
  341. next.Detector.EmaAlpha = v
  342. }
  343. if update.Detector.HysteresisDb != nil {
  344. v := *update.Detector.HysteresisDb
  345. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 {
  346. return m.cfg, errors.New("hysteresis_db must be >= 0")
  347. }
  348. next.Detector.HysteresisDb = v
  349. }
  350. if update.Detector.MinStableFrames != nil {
  351. if *update.Detector.MinStableFrames < 1 {
  352. return m.cfg, errors.New("min_stable_frames must be >= 1")
  353. }
  354. next.Detector.MinStableFrames = *update.Detector.MinStableFrames
  355. }
  356. if update.Detector.GapToleranceMs != nil {
  357. if *update.Detector.GapToleranceMs < 0 {
  358. return m.cfg, errors.New("gap_tolerance_ms must be >= 0")
  359. }
  360. next.Detector.GapToleranceMs = *update.Detector.GapToleranceMs
  361. }
  362. if update.Detector.CFARMode != nil {
  363. mode := strings.ToUpper(strings.TrimSpace(*update.Detector.CFARMode))
  364. switch mode {
  365. case "OFF", "CA", "OS", "GOSCA", "CASO":
  366. next.Detector.CFARMode = mode
  367. default:
  368. return m.cfg, errors.New("cfar_mode must be OFF, CA, OS, GOSCA, or CASO")
  369. }
  370. }
  371. if update.Detector.CFARWrapAround != nil {
  372. next.Detector.CFARWrapAround = *update.Detector.CFARWrapAround
  373. }
  374. if update.Detector.CFARGuardHz != nil {
  375. if *update.Detector.CFARGuardHz < 0 {
  376. return m.cfg, errors.New("cfar_guard_hz must be >= 0")
  377. }
  378. next.Detector.CFARGuardHz = *update.Detector.CFARGuardHz
  379. }
  380. if update.Detector.CFARTrainHz != nil {
  381. if *update.Detector.CFARTrainHz <= 0 {
  382. return m.cfg, errors.New("cfar_train_hz must be > 0")
  383. }
  384. next.Detector.CFARTrainHz = *update.Detector.CFARTrainHz
  385. }
  386. if update.Detector.CFARGuardCells != nil {
  387. if *update.Detector.CFARGuardCells < 0 {
  388. return m.cfg, errors.New("cfar_guard_cells must be >= 0")
  389. }
  390. next.Detector.CFARGuardCells = *update.Detector.CFARGuardCells
  391. }
  392. if update.Detector.CFARTrainCells != nil {
  393. if *update.Detector.CFARTrainCells <= 0 {
  394. return m.cfg, errors.New("cfar_train_cells must be > 0")
  395. }
  396. next.Detector.CFARTrainCells = *update.Detector.CFARTrainCells
  397. }
  398. if update.Detector.CFARRank != nil {
  399. if *update.Detector.CFARRank <= 0 {
  400. return m.cfg, errors.New("cfar_rank must be > 0")
  401. }
  402. if next.Detector.CFARTrainCells > 0 && *update.Detector.CFARRank > 2*next.Detector.CFARTrainCells {
  403. return m.cfg, errors.New("cfar_rank must be <= 2 * cfar_train_cells")
  404. }
  405. next.Detector.CFARRank = *update.Detector.CFARRank
  406. }
  407. if update.Detector.CFARScaleDb != nil {
  408. next.Detector.CFARScaleDb = *update.Detector.CFARScaleDb
  409. }
  410. if update.Detector.EdgeMarginDb != nil {
  411. v := *update.Detector.EdgeMarginDb
  412. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 {
  413. return m.cfg, errors.New("edge_margin_db must be >= 0")
  414. }
  415. next.Detector.EdgeMarginDb = v
  416. }
  417. if update.Detector.MergeGapHz != nil {
  418. v := *update.Detector.MergeGapHz
  419. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 {
  420. return m.cfg, errors.New("merge_gap_hz must be >= 0")
  421. }
  422. next.Detector.MergeGapHz = v
  423. }
  424. if update.Detector.ClassHistorySize != nil {
  425. if *update.Detector.ClassHistorySize < 1 {
  426. return m.cfg, errors.New("class_history_size must be >= 1")
  427. }
  428. next.Detector.ClassHistorySize = *update.Detector.ClassHistorySize
  429. }
  430. if update.Detector.ClassSwitchRatio != nil {
  431. v := *update.Detector.ClassSwitchRatio
  432. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0.1 || v > 1.0 {
  433. return m.cfg, errors.New("class_switch_ratio must be between 0.1 and 1.0")
  434. }
  435. next.Detector.ClassSwitchRatio = v
  436. }
  437. }
  438. if update.Recorder != nil {
  439. if update.Recorder.Enabled != nil {
  440. next.Recorder.Enabled = *update.Recorder.Enabled
  441. }
  442. if update.Recorder.MinSNRDb != nil {
  443. next.Recorder.MinSNRDb = *update.Recorder.MinSNRDb
  444. }
  445. if update.Recorder.MinDuration != nil {
  446. next.Recorder.MinDuration = *update.Recorder.MinDuration
  447. }
  448. if update.Recorder.MaxDuration != nil {
  449. next.Recorder.MaxDuration = *update.Recorder.MaxDuration
  450. }
  451. if update.Recorder.PrerollMs != nil {
  452. next.Recorder.PrerollMs = *update.Recorder.PrerollMs
  453. }
  454. if update.Recorder.RecordIQ != nil {
  455. next.Recorder.RecordIQ = *update.Recorder.RecordIQ
  456. }
  457. if update.Recorder.RecordAudio != nil {
  458. next.Recorder.RecordAudio = *update.Recorder.RecordAudio
  459. }
  460. if update.Recorder.AutoDemod != nil {
  461. next.Recorder.AutoDemod = *update.Recorder.AutoDemod
  462. }
  463. if update.Recorder.AutoDecode != nil {
  464. next.Recorder.AutoDecode = *update.Recorder.AutoDecode
  465. }
  466. if update.Recorder.MaxDiskMB != nil {
  467. next.Recorder.MaxDiskMB = *update.Recorder.MaxDiskMB
  468. }
  469. if update.Recorder.OutputDir != nil {
  470. next.Recorder.OutputDir = *update.Recorder.OutputDir
  471. }
  472. if update.Recorder.ClassFilter != nil {
  473. next.Recorder.ClassFilter = *update.Recorder.ClassFilter
  474. }
  475. if update.Recorder.RingSeconds != nil {
  476. next.Recorder.RingSeconds = *update.Recorder.RingSeconds
  477. }
  478. }
  479. m.cfg = next
  480. return m.cfg, nil
  481. }
  482. func (m *Manager) ApplySettings(update SettingsUpdate) (config.Config, error) {
  483. m.mu.Lock()
  484. defer m.mu.Unlock()
  485. next := m.cfg
  486. if update.AGC != nil {
  487. next.AGC = *update.AGC
  488. }
  489. if update.DCBlock != nil {
  490. next.DCBlock = *update.DCBlock
  491. }
  492. if update.IQBalance != nil {
  493. next.IQBalance = *update.IQBalance
  494. }
  495. m.cfg = next
  496. return m.cfg, nil
  497. }