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.

222 wiersze
5.4KB

  1. package main
  2. import (
  3. "sort"
  4. "time"
  5. "sdr-wideband-suite/internal/pipeline"
  6. )
  7. type decisionQueueStats struct {
  8. RecordQueued int `json:"record_queued"`
  9. DecodeQueued int `json:"decode_queued"`
  10. RecordSelected int `json:"record_selected"`
  11. DecodeSelected int `json:"decode_selected"`
  12. RecordActive int `json:"record_active"`
  13. DecodeActive int `json:"decode_active"`
  14. RecordOldestS float64 `json:"record_oldest_sec"`
  15. DecodeOldestS float64 `json:"decode_oldest_sec"`
  16. RecordBudget int `json:"record_budget"`
  17. DecodeBudget int `json:"decode_budget"`
  18. HoldMs int `json:"hold_ms"`
  19. RecordDropped int `json:"record_dropped"`
  20. DecodeDropped int `json:"decode_dropped"`
  21. }
  22. type queuedDecision struct {
  23. ID int64
  24. SNRDb float64
  25. Hint string
  26. Class string
  27. FirstSeen time.Time
  28. LastSeen time.Time
  29. }
  30. type decisionQueues struct {
  31. record map[int64]*queuedDecision
  32. decode map[int64]*queuedDecision
  33. recordHold map[int64]time.Time
  34. decodeHold map[int64]time.Time
  35. }
  36. func newDecisionQueues() *decisionQueues {
  37. return &decisionQueues{
  38. record: map[int64]*queuedDecision{},
  39. decode: map[int64]*queuedDecision{},
  40. recordHold: map[int64]time.Time{},
  41. decodeHold: map[int64]time.Time{},
  42. }
  43. }
  44. func (dq *decisionQueues) Apply(decisions []pipeline.SignalDecision, budget pipeline.BudgetModel, now time.Time, policy pipeline.Policy) decisionQueueStats {
  45. if dq == nil {
  46. return decisionQueueStats{}
  47. }
  48. hold := time.Duration(budget.HoldMs) * time.Millisecond
  49. recSeen := map[int64]bool{}
  50. decSeen := map[int64]bool{}
  51. for i := range decisions {
  52. id := decisions[i].Candidate.ID
  53. if id == 0 {
  54. continue
  55. }
  56. if decisions[i].ShouldRecord {
  57. qd := dq.record[id]
  58. if qd == nil {
  59. qd = &queuedDecision{ID: id, FirstSeen: now}
  60. dq.record[id] = qd
  61. }
  62. qd.SNRDb = decisions[i].Candidate.SNRDb
  63. qd.Hint = decisions[i].Candidate.Hint
  64. qd.Class = decisions[i].Class
  65. qd.LastSeen = now
  66. recSeen[id] = true
  67. }
  68. if decisions[i].ShouldAutoDecode {
  69. qd := dq.decode[id]
  70. if qd == nil {
  71. qd = &queuedDecision{ID: id, FirstSeen: now}
  72. dq.decode[id] = qd
  73. }
  74. qd.SNRDb = decisions[i].Candidate.SNRDb
  75. qd.Hint = decisions[i].Candidate.Hint
  76. qd.Class = decisions[i].Class
  77. qd.LastSeen = now
  78. decSeen[id] = true
  79. }
  80. }
  81. for id := range dq.record {
  82. if !recSeen[id] {
  83. delete(dq.record, id)
  84. }
  85. }
  86. for id := range dq.decode {
  87. if !decSeen[id] {
  88. delete(dq.decode, id)
  89. }
  90. }
  91. purgeExpired(dq.recordHold, now)
  92. purgeExpired(dq.decodeHold, now)
  93. recSelected := selectQueued("record", dq.record, dq.recordHold, budget.Record.Max, hold, now, policy)
  94. decSelected := selectQueued("decode", dq.decode, dq.decodeHold, budget.Decode.Max, hold, now, policy)
  95. stats := decisionQueueStats{
  96. RecordQueued: len(dq.record),
  97. DecodeQueued: len(dq.decode),
  98. RecordSelected: len(recSelected),
  99. DecodeSelected: len(decSelected),
  100. RecordActive: len(dq.recordHold),
  101. DecodeActive: len(dq.decodeHold),
  102. RecordOldestS: oldestAge(dq.record, now),
  103. DecodeOldestS: oldestAge(dq.decode, now),
  104. RecordBudget: budget.Record.Max,
  105. DecodeBudget: budget.Decode.Max,
  106. HoldMs: budget.HoldMs,
  107. }
  108. for i := range decisions {
  109. id := decisions[i].Candidate.ID
  110. if decisions[i].ShouldRecord {
  111. if _, ok := recSelected[id]; !ok {
  112. decisions[i].ShouldRecord = false
  113. decisions[i].Reason = "queued: record budget"
  114. stats.RecordDropped++
  115. }
  116. }
  117. if decisions[i].ShouldAutoDecode {
  118. if _, ok := decSelected[id]; !ok {
  119. decisions[i].ShouldAutoDecode = false
  120. if decisions[i].Reason == "" {
  121. decisions[i].Reason = "queued: decode budget"
  122. }
  123. stats.DecodeDropped++
  124. }
  125. }
  126. }
  127. return stats
  128. }
  129. func selectQueued(queueName string, queue map[int64]*queuedDecision, hold map[int64]time.Time, max int, holdDur time.Duration, now time.Time, policy pipeline.Policy) map[int64]struct{} {
  130. selected := map[int64]struct{}{}
  131. if len(queue) == 0 {
  132. return selected
  133. }
  134. type scored struct {
  135. id int64
  136. score float64
  137. }
  138. scoredList := make([]scored, 0, len(queue))
  139. for id, qd := range queue {
  140. age := now.Sub(qd.FirstSeen).Seconds()
  141. boost := age / 2.0
  142. if boost > 5 {
  143. boost = 5
  144. }
  145. hint := qd.Hint
  146. if hint == "" {
  147. hint = qd.Class
  148. }
  149. policyBoost := pipeline.DecisionPriorityBoost(policy, hint, qd.Class, queueName)
  150. scoredList = append(scoredList, scored{id: id, score: qd.SNRDb + boost + policyBoost})
  151. }
  152. sort.Slice(scoredList, func(i, j int) bool {
  153. return scoredList[i].score > scoredList[j].score
  154. })
  155. limit := max
  156. if limit <= 0 || limit > len(scoredList) {
  157. limit = len(scoredList)
  158. }
  159. if len(hold) > 0 && len(hold) > limit {
  160. limit = len(hold)
  161. if limit > len(scoredList) {
  162. limit = len(scoredList)
  163. }
  164. }
  165. for id := range hold {
  166. if _, ok := queue[id]; ok {
  167. selected[id] = struct{}{}
  168. }
  169. }
  170. for _, s := range scoredList {
  171. if len(selected) >= limit {
  172. break
  173. }
  174. if _, ok := selected[s.id]; ok {
  175. continue
  176. }
  177. selected[s.id] = struct{}{}
  178. }
  179. if holdDur > 0 {
  180. for id := range selected {
  181. hold[id] = now.Add(holdDur)
  182. }
  183. }
  184. return selected
  185. }
  186. func purgeExpired(hold map[int64]time.Time, now time.Time) {
  187. for id, until := range hold {
  188. if now.After(until) {
  189. delete(hold, id)
  190. }
  191. }
  192. }
  193. func oldestAge(queue map[int64]*queuedDecision, now time.Time) float64 {
  194. oldest := 0.0
  195. first := true
  196. for _, qd := range queue {
  197. age := now.Sub(qd.FirstSeen).Seconds()
  198. if first || age > oldest {
  199. oldest = age
  200. first = false
  201. }
  202. }
  203. if first {
  204. return 0
  205. }
  206. return oldest
  207. }