Wideband autonomous SDR analysis engine forked from sdr-visual-suite
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

146 líneas
4.5KB

  1. package pipeline
  2. import (
  3. "strings"
  4. "sdr-wideband-suite/internal/classifier"
  5. )
  6. type SignalDecision struct {
  7. Candidate Candidate `json:"candidate"`
  8. Class string `json:"class,omitempty"`
  9. ShouldRecord bool `json:"should_record"`
  10. ShouldAutoDecode bool `json:"should_auto_decode"`
  11. Reason string `json:"reason,omitempty"`
  12. MonitorBias float64 `json:"monitor_bias,omitempty"`
  13. RecordBias float64 `json:"record_bias,omitempty"`
  14. DecodeBias float64 `json:"decode_bias,omitempty"`
  15. MonitorDetail *MonitorWindowMatch `json:"monitor_detail,omitempty"`
  16. MonitorWindow *MonitorWindowMatch `json:"monitor_window,omitempty"`
  17. RecordWindow *MonitorWindowMatch `json:"record_window,omitempty"`
  18. DecodeWindow *MonitorWindowMatch `json:"decode_window,omitempty"`
  19. RecordAdmission *PriorityAdmission `json:"record_admission,omitempty"`
  20. DecodeAdmission *PriorityAdmission `json:"decode_admission,omitempty"`
  21. }
  22. func DecideSignalAction(policy Policy, candidate Candidate, cls *classifier.Classification) SignalDecision {
  23. if len(policy.MonitorWindows) > 0 {
  24. _ = ApplyMonitorWindowMatches(policy, &candidate)
  25. }
  26. decision := SignalDecision{Candidate: candidate}
  27. classTag := ""
  28. hintTag := strings.TrimSpace(candidate.Hint)
  29. if cls != nil {
  30. decision.Class = string(cls.ModType)
  31. classTag = decision.Class
  32. }
  33. if classTag != "" && WantsClass(policy.AutoRecordClasses, classTag) {
  34. decision.ShouldRecord = true
  35. decision.Reason = DecisionReasonRecordClass
  36. } else if classTag == "" && hintTag != "" && WantsClass(policy.AutoRecordClasses, hintTag) {
  37. decision.ShouldRecord = true
  38. decision.Reason = DecisionReasonRecordHint
  39. }
  40. if classTag != "" && WantsClass(policy.AutoDecodeClasses, classTag) {
  41. decision.ShouldAutoDecode = true
  42. if decision.Reason == "" {
  43. decision.Reason = DecisionReasonDecodeClass
  44. }
  45. } else if classTag == "" && hintTag != "" && WantsClass(policy.AutoDecodeClasses, hintTag) {
  46. decision.ShouldAutoDecode = true
  47. if decision.Reason == "" {
  48. decision.Reason = DecisionReasonDecodeHint
  49. }
  50. }
  51. recordMatch := bestMonitorActionMatch(candidate.MonitorMatches, true, false)
  52. if recordMatch != nil {
  53. decision.RecordBias = recordMatch.RecordBias
  54. if decision.RecordWindow == nil {
  55. decision.RecordWindow = recordMatch
  56. }
  57. if !decision.ShouldRecord {
  58. decision.ShouldRecord = true
  59. if decision.Reason == "" {
  60. decision.Reason = DecisionReasonRecordWindow
  61. }
  62. }
  63. }
  64. decodeMatch := bestMonitorActionMatch(candidate.MonitorMatches, false, true)
  65. if decodeMatch != nil {
  66. decision.DecodeBias = decodeMatch.DecodeBias
  67. if decision.DecodeWindow == nil {
  68. decision.DecodeWindow = decodeMatch
  69. }
  70. if !decision.ShouldAutoDecode {
  71. decision.ShouldAutoDecode = true
  72. if decision.Reason == "" {
  73. decision.Reason = DecisionReasonDecodeWindow
  74. }
  75. }
  76. }
  77. if decision.Reason == "" && candidate.Hint != "" {
  78. decision.Reason = DecisionReasonHintOnly
  79. }
  80. monitorBias, monitorDetail := MonitorWindowBias(policy, candidate)
  81. if monitorDetail == nil {
  82. monitorDetail = selectMonitorDetail(recordMatch, decodeMatch)
  83. }
  84. if monitorBias != 0 {
  85. decision.MonitorBias = monitorBias
  86. }
  87. if monitorDetail != nil {
  88. decision.MonitorDetail = monitorDetail
  89. decision.MonitorWindow = monitorDetail
  90. }
  91. return decision
  92. }
  93. func bestMonitorActionMatch(matches []MonitorWindowMatch, wantRecord bool, wantDecode bool) *MonitorWindowMatch {
  94. if len(matches) == 0 || (!wantRecord && !wantDecode) {
  95. return nil
  96. }
  97. best := -1
  98. for i := range matches {
  99. match := matches[i]
  100. if wantRecord && !match.AutoRecord {
  101. continue
  102. }
  103. if wantDecode && !match.AutoDecode {
  104. continue
  105. }
  106. if best == -1 || betterMonitorActionMatch(match, matches[best]) {
  107. best = i
  108. }
  109. }
  110. if best == -1 {
  111. return nil
  112. }
  113. return &matches[best]
  114. }
  115. func betterMonitorActionMatch(candidate MonitorWindowMatch, best MonitorWindowMatch) bool {
  116. if candidate.Coverage != best.Coverage {
  117. return candidate.Coverage > best.Coverage
  118. }
  119. if candidate.DistanceHz != best.DistanceHz {
  120. return candidate.DistanceHz < best.DistanceHz
  121. }
  122. if candidate.Bias != best.Bias {
  123. return candidate.Bias > best.Bias
  124. }
  125. return candidate.Index < best.Index
  126. }
  127. func selectMonitorDetail(recordMatch *MonitorWindowMatch, decodeMatch *MonitorWindowMatch) *MonitorWindowMatch {
  128. if recordMatch == nil {
  129. return decodeMatch
  130. }
  131. if decodeMatch == nil {
  132. return recordMatch
  133. }
  134. if betterMonitorActionMatch(*recordMatch, *decodeMatch) {
  135. return recordMatch
  136. }
  137. return decodeMatch
  138. }