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.

200 rindas
4.6KB

  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. }
  17. type queuedDecision struct {
  18. ID int64
  19. SNRDb float64
  20. FirstSeen time.Time
  21. LastSeen time.Time
  22. }
  23. type decisionQueues struct {
  24. record map[int64]*queuedDecision
  25. decode map[int64]*queuedDecision
  26. recordHold map[int64]time.Time
  27. decodeHold map[int64]time.Time
  28. }
  29. func newDecisionQueues() *decisionQueues {
  30. return &decisionQueues{
  31. record: map[int64]*queuedDecision{},
  32. decode: map[int64]*queuedDecision{},
  33. recordHold: map[int64]time.Time{},
  34. decodeHold: map[int64]time.Time{},
  35. }
  36. }
  37. func (dq *decisionQueues) Apply(decisions []pipeline.SignalDecision, maxRecord int, maxDecode int, hold time.Duration, now time.Time) decisionQueueStats {
  38. if dq == nil {
  39. return decisionQueueStats{}
  40. }
  41. recSeen := map[int64]bool{}
  42. decSeen := map[int64]bool{}
  43. for i := range decisions {
  44. id := decisions[i].Candidate.ID
  45. if id == 0 {
  46. continue
  47. }
  48. if decisions[i].ShouldRecord {
  49. qd := dq.record[id]
  50. if qd == nil {
  51. qd = &queuedDecision{ID: id, FirstSeen: now}
  52. dq.record[id] = qd
  53. }
  54. qd.SNRDb = decisions[i].Candidate.SNRDb
  55. qd.LastSeen = now
  56. recSeen[id] = true
  57. }
  58. if decisions[i].ShouldAutoDecode {
  59. qd := dq.decode[id]
  60. if qd == nil {
  61. qd = &queuedDecision{ID: id, FirstSeen: now}
  62. dq.decode[id] = qd
  63. }
  64. qd.SNRDb = decisions[i].Candidate.SNRDb
  65. qd.LastSeen = now
  66. decSeen[id] = true
  67. }
  68. }
  69. for id := range dq.record {
  70. if !recSeen[id] {
  71. delete(dq.record, id)
  72. }
  73. }
  74. for id := range dq.decode {
  75. if !decSeen[id] {
  76. delete(dq.decode, id)
  77. }
  78. }
  79. purgeExpired(dq.recordHold, now)
  80. purgeExpired(dq.decodeHold, now)
  81. recSelected := selectQueued(dq.record, dq.recordHold, maxRecord, hold, now)
  82. decSelected := selectQueued(dq.decode, dq.decodeHold, maxDecode, hold, now)
  83. stats := decisionQueueStats{
  84. RecordQueued: len(dq.record),
  85. DecodeQueued: len(dq.decode),
  86. RecordSelected: len(recSelected),
  87. DecodeSelected: len(decSelected),
  88. RecordActive: len(dq.recordHold),
  89. DecodeActive: len(dq.decodeHold),
  90. RecordOldestS: oldestAge(dq.record, now),
  91. DecodeOldestS: oldestAge(dq.decode, now),
  92. }
  93. for i := range decisions {
  94. id := decisions[i].Candidate.ID
  95. if decisions[i].ShouldRecord {
  96. if _, ok := recSelected[id]; !ok {
  97. decisions[i].ShouldRecord = false
  98. decisions[i].Reason = "queued: record budget"
  99. }
  100. }
  101. if decisions[i].ShouldAutoDecode {
  102. if _, ok := decSelected[id]; !ok {
  103. decisions[i].ShouldAutoDecode = false
  104. if decisions[i].Reason == "" {
  105. decisions[i].Reason = "queued: decode budget"
  106. }
  107. }
  108. }
  109. }
  110. return stats
  111. }
  112. func selectQueued(queue map[int64]*queuedDecision, hold map[int64]time.Time, max int, holdDur time.Duration, now time.Time) map[int64]struct{} {
  113. selected := map[int64]struct{}{}
  114. if len(queue) == 0 {
  115. return selected
  116. }
  117. type scored struct {
  118. id int64
  119. score float64
  120. }
  121. scoredList := make([]scored, 0, len(queue))
  122. for id, qd := range queue {
  123. age := now.Sub(qd.FirstSeen).Seconds()
  124. boost := age / 2.0
  125. if boost > 5 {
  126. boost = 5
  127. }
  128. scoredList = append(scoredList, scored{id: id, score: qd.SNRDb + boost})
  129. }
  130. sort.Slice(scoredList, func(i, j int) bool {
  131. return scoredList[i].score > scoredList[j].score
  132. })
  133. limit := max
  134. if limit <= 0 || limit > len(scoredList) {
  135. limit = len(scoredList)
  136. }
  137. if len(hold) > 0 && len(hold) > limit {
  138. limit = len(hold)
  139. if limit > len(scoredList) {
  140. limit = len(scoredList)
  141. }
  142. }
  143. for id := range hold {
  144. if _, ok := queue[id]; ok {
  145. selected[id] = struct{}{}
  146. }
  147. }
  148. for _, s := range scoredList {
  149. if len(selected) >= limit {
  150. break
  151. }
  152. if _, ok := selected[s.id]; ok {
  153. continue
  154. }
  155. selected[s.id] = struct{}{}
  156. }
  157. if holdDur > 0 {
  158. for id := range selected {
  159. hold[id] = now.Add(holdDur)
  160. }
  161. }
  162. return selected
  163. }
  164. func purgeExpired(hold map[int64]time.Time, now time.Time) {
  165. for id, until := range hold {
  166. if now.After(until) {
  167. delete(hold, id)
  168. }
  169. }
  170. }
  171. func oldestAge(queue map[int64]*queuedDecision, now time.Time) float64 {
  172. oldest := 0.0
  173. first := true
  174. for _, qd := range queue {
  175. age := now.Sub(qd.FirstSeen).Seconds()
  176. if first || age > oldest {
  177. oldest = age
  178. first = false
  179. }
  180. }
  181. if first {
  182. return 0
  183. }
  184. return oldest
  185. }