You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 line
7.3KB

  1. package runtime
  2. import (
  3. "errors"
  4. "math"
  5. "sync"
  6. "sdr-visual-suite/internal/config"
  7. )
  8. type ConfigUpdate struct {
  9. CenterHz *float64 `json:"center_hz"`
  10. SampleRate *int `json:"sample_rate"`
  11. FFTSize *int `json:"fft_size"`
  12. GainDb *float64 `json:"gain_db"`
  13. TunerBwKHz *int `json:"tuner_bw_khz"`
  14. UseGPUFFT *bool `json:"use_gpu_fft"`
  15. Detector *DetectorUpdate `json:"detector"`
  16. Recorder *RecorderUpdate `json:"recorder"`
  17. }
  18. type DetectorUpdate struct {
  19. ThresholdDb *float64 `json:"threshold_db"`
  20. MinDuration *int `json:"min_duration_ms"`
  21. HoldMs *int `json:"hold_ms"`
  22. EmaAlpha *float64 `json:"ema_alpha"`
  23. HysteresisDb *float64 `json:"hysteresis_db"`
  24. MinStableFrames *int `json:"min_stable_frames"`
  25. GapToleranceMs *int `json:"gap_tolerance_ms"`
  26. CFAREnabled *bool `json:"cfar_enabled"`
  27. CFARGuardCells *int `json:"cfar_guard_cells"`
  28. CFARTrainCells *int `json:"cfar_train_cells"`
  29. CFARRank *int `json:"cfar_rank"`
  30. CFARScaleDb *float64 `json:"cfar_scale_db"`
  31. }
  32. type SettingsUpdate struct {
  33. AGC *bool `json:"agc"`
  34. DCBlock *bool `json:"dc_block"`
  35. IQBalance *bool `json:"iq_balance"`
  36. }
  37. type RecorderUpdate struct {
  38. Enabled *bool `json:"enabled"`
  39. MinSNRDb *float64 `json:"min_snr_db"`
  40. MinDuration *string `json:"min_duration"`
  41. MaxDuration *string `json:"max_duration"`
  42. PrerollMs *int `json:"preroll_ms"`
  43. RecordIQ *bool `json:"record_iq"`
  44. RecordAudio *bool `json:"record_audio"`
  45. AutoDemod *bool `json:"auto_demod"`
  46. AutoDecode *bool `json:"auto_decode"`
  47. MaxDiskMB *int `json:"max_disk_mb"`
  48. OutputDir *string `json:"output_dir"`
  49. ClassFilter *[]string `json:"class_filter"`
  50. RingSeconds *int `json:"ring_seconds"`
  51. }
  52. type Manager struct {
  53. mu sync.RWMutex
  54. cfg config.Config
  55. }
  56. func New(cfg config.Config) *Manager {
  57. return &Manager{cfg: cfg}
  58. }
  59. func (m *Manager) Snapshot() config.Config {
  60. m.mu.RLock()
  61. defer m.mu.RUnlock()
  62. return m.cfg
  63. }
  64. func (m *Manager) Replace(cfg config.Config) {
  65. m.mu.Lock()
  66. defer m.mu.Unlock()
  67. m.cfg = cfg
  68. }
  69. func (m *Manager) ApplyConfig(update ConfigUpdate) (config.Config, error) {
  70. m.mu.Lock()
  71. defer m.mu.Unlock()
  72. next := m.cfg
  73. if update.CenterHz != nil {
  74. if *update.CenterHz < 1e3 || *update.CenterHz > 2e9 {
  75. return m.cfg, errors.New("center_hz out of range")
  76. }
  77. next.CenterHz = *update.CenterHz
  78. }
  79. if update.SampleRate != nil {
  80. if *update.SampleRate <= 0 {
  81. return m.cfg, errors.New("sample_rate must be > 0")
  82. }
  83. next.SampleRate = *update.SampleRate
  84. }
  85. if update.FFTSize != nil {
  86. if *update.FFTSize <= 0 {
  87. return m.cfg, errors.New("fft_size must be > 0")
  88. }
  89. if *update.FFTSize&(*update.FFTSize-1) != 0 {
  90. return m.cfg, errors.New("fft_size must be a power of 2")
  91. }
  92. next.FFTSize = *update.FFTSize
  93. }
  94. if update.GainDb != nil {
  95. next.GainDb = *update.GainDb
  96. }
  97. if update.TunerBwKHz != nil {
  98. if *update.TunerBwKHz <= 0 {
  99. return m.cfg, errors.New("tuner_bw_khz must be > 0")
  100. }
  101. next.TunerBwKHz = *update.TunerBwKHz
  102. }
  103. if update.UseGPUFFT != nil {
  104. next.UseGPUFFT = *update.UseGPUFFT
  105. }
  106. if update.Detector != nil {
  107. if update.Detector.ThresholdDb != nil {
  108. next.Detector.ThresholdDb = *update.Detector.ThresholdDb
  109. }
  110. if update.Detector.MinDuration != nil {
  111. if *update.Detector.MinDuration <= 0 {
  112. return m.cfg, errors.New("min_duration_ms must be > 0")
  113. }
  114. next.Detector.MinDurationMs = *update.Detector.MinDuration
  115. }
  116. if update.Detector.HoldMs != nil {
  117. if *update.Detector.HoldMs <= 0 {
  118. return m.cfg, errors.New("hold_ms must be > 0")
  119. }
  120. next.Detector.HoldMs = *update.Detector.HoldMs
  121. }
  122. if update.Detector.EmaAlpha != nil {
  123. v := *update.Detector.EmaAlpha
  124. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 || v > 1 {
  125. return m.cfg, errors.New("ema_alpha must be between 0 and 1")
  126. }
  127. next.Detector.EmaAlpha = v
  128. }
  129. if update.Detector.HysteresisDb != nil {
  130. v := *update.Detector.HysteresisDb
  131. if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 {
  132. return m.cfg, errors.New("hysteresis_db must be >= 0")
  133. }
  134. next.Detector.HysteresisDb = v
  135. }
  136. if update.Detector.MinStableFrames != nil {
  137. if *update.Detector.MinStableFrames < 1 {
  138. return m.cfg, errors.New("min_stable_frames must be >= 1")
  139. }
  140. next.Detector.MinStableFrames = *update.Detector.MinStableFrames
  141. }
  142. if update.Detector.GapToleranceMs != nil {
  143. if *update.Detector.GapToleranceMs < 0 {
  144. return m.cfg, errors.New("gap_tolerance_ms must be >= 0")
  145. }
  146. next.Detector.GapToleranceMs = *update.Detector.GapToleranceMs
  147. }
  148. if update.Detector.CFAREnabled != nil {
  149. next.Detector.CFAREnabled = *update.Detector.CFAREnabled
  150. }
  151. if update.Detector.CFARGuardCells != nil {
  152. if *update.Detector.CFARGuardCells < 0 {
  153. return m.cfg, errors.New("cfar_guard_cells must be >= 0")
  154. }
  155. next.Detector.CFARGuardCells = *update.Detector.CFARGuardCells
  156. }
  157. if update.Detector.CFARTrainCells != nil {
  158. if *update.Detector.CFARTrainCells <= 0 {
  159. return m.cfg, errors.New("cfar_train_cells must be > 0")
  160. }
  161. next.Detector.CFARTrainCells = *update.Detector.CFARTrainCells
  162. }
  163. if update.Detector.CFARRank != nil {
  164. if *update.Detector.CFARRank <= 0 {
  165. return m.cfg, errors.New("cfar_rank must be > 0")
  166. }
  167. if next.Detector.CFARTrainCells > 0 && *update.Detector.CFARRank > 2*next.Detector.CFARTrainCells {
  168. return m.cfg, errors.New("cfar_rank must be <= 2 * cfar_train_cells")
  169. }
  170. next.Detector.CFARRank = *update.Detector.CFARRank
  171. }
  172. if update.Detector.CFARScaleDb != nil {
  173. next.Detector.CFARScaleDb = *update.Detector.CFARScaleDb
  174. }
  175. }
  176. if update.Recorder != nil {
  177. if update.Recorder.Enabled != nil {
  178. next.Recorder.Enabled = *update.Recorder.Enabled
  179. }
  180. if update.Recorder.MinSNRDb != nil {
  181. next.Recorder.MinSNRDb = *update.Recorder.MinSNRDb
  182. }
  183. if update.Recorder.MinDuration != nil {
  184. next.Recorder.MinDuration = *update.Recorder.MinDuration
  185. }
  186. if update.Recorder.MaxDuration != nil {
  187. next.Recorder.MaxDuration = *update.Recorder.MaxDuration
  188. }
  189. if update.Recorder.PrerollMs != nil {
  190. next.Recorder.PrerollMs = *update.Recorder.PrerollMs
  191. }
  192. if update.Recorder.RecordIQ != nil {
  193. next.Recorder.RecordIQ = *update.Recorder.RecordIQ
  194. }
  195. if update.Recorder.RecordAudio != nil {
  196. next.Recorder.RecordAudio = *update.Recorder.RecordAudio
  197. }
  198. if update.Recorder.AutoDemod != nil {
  199. next.Recorder.AutoDemod = *update.Recorder.AutoDemod
  200. }
  201. if update.Recorder.AutoDecode != nil {
  202. next.Recorder.AutoDecode = *update.Recorder.AutoDecode
  203. }
  204. if update.Recorder.MaxDiskMB != nil {
  205. next.Recorder.MaxDiskMB = *update.Recorder.MaxDiskMB
  206. }
  207. if update.Recorder.OutputDir != nil {
  208. next.Recorder.OutputDir = *update.Recorder.OutputDir
  209. }
  210. if update.Recorder.ClassFilter != nil {
  211. next.Recorder.ClassFilter = *update.Recorder.ClassFilter
  212. }
  213. if update.Recorder.RingSeconds != nil {
  214. next.Recorder.RingSeconds = *update.Recorder.RingSeconds
  215. }
  216. }
  217. m.cfg = next
  218. return m.cfg, nil
  219. }
  220. func (m *Manager) ApplySettings(update SettingsUpdate) (config.Config, error) {
  221. m.mu.Lock()
  222. defer m.mu.Unlock()
  223. next := m.cfg
  224. if update.AGC != nil {
  225. next.AGC = *update.AGC
  226. }
  227. if update.DCBlock != nil {
  228. next.DCBlock = *update.DCBlock
  229. }
  230. if update.IQBalance != nil {
  231. next.IQBalance = *update.IQBalance
  232. }
  233. m.cfg = next
  234. return m.cfg, nil
  235. }