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.

308 wiersze
9.3KB

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