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.

132 lines
4.1KB

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