Wideband autonomous SDR analysis engine forked from sdr-visual-suite
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.

144 lines
4.4KB

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