Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

271 Zeilen
7.9KB

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