Files
FlexDXClusterGui/spot.go
2025-10-21 19:27:58 +02:00

462 lines
11 KiB
Go

package main
import (
"regexp"
"strconv"
"strings"
"sync"
_ "github.com/mattn/go-sqlite3"
)
type TelnetSpot struct {
DX string
Spotter string
Frequency string
Mode string
Band string
Time string
DXCC string
CountryName string
Comment string
CommandNumber int
FlexSpotNumber int
NewDXCC bool
NewBand bool
NewMode bool
NewSlot bool
CallsignWorked bool
}
func ProcessTelnetSpot(re *regexp.Regexp, spotRaw string, SpotChanToFlex chan TelnetSpot, SpotChanToHTTPServer chan TelnetSpot, Countries Countries, contactRepo *Log4OMContactsRepository) {
match := re.FindStringSubmatch(spotRaw)
if len(match) == 0 {
IncrementSpotsRejected()
Log.Warnf("❌ Regex no match: %s", spotRaw)
return
}
spot := TelnetSpot{
DX: match[3],
Spotter: match[1],
Frequency: match[2],
Mode: match[4],
Comment: strings.Trim(match[5], " "),
Time: match[6],
}
DXCC := GetDXCC(spot.DX, Countries)
spot.DXCC = DXCC.DXCC
spot.CountryName = DXCC.CountryName
if spot.DXCC == "" {
IncrementSpotsRejected()
Log.Warnf("❌ DXCC not found: %s", spot.DX)
return
}
spot.GetBand()
spot.GuessMode(spotRaw)
spot.CallsignWorked = false
spot.NewBand = false
spot.NewMode = false
spot.NewDXCC = false
spot.NewSlot = false
contactsChan := make(chan []Contact)
contactsModeChan := make(chan []Contact)
contactsModeBandChan := make(chan []Contact)
contactsBandChan := make(chan []Contact)
contactsCallChan := make(chan []Contact)
wg := new(sync.WaitGroup)
wg.Add(5)
go contactRepo.ListByCountry(spot.DXCC, contactsChan, wg)
contacts := <-contactsChan
go contactRepo.ListByCountryMode(spot.DXCC, spot.Mode, contactsModeChan, wg)
contactsMode := <-contactsModeChan
go contactRepo.ListByCountryBand(spot.DXCC, spot.Band, contactsBandChan, wg)
contactsBand := <-contactsBandChan
go contactRepo.ListByCallSign(spot.DX, spot.Band, spot.Mode, contactsCallChan, wg)
contactsCall := <-contactsCallChan
go contactRepo.ListByCountryModeBand(spot.DXCC, spot.Band, spot.Mode, contactsModeBandChan, wg)
contactsModeBand := <-contactsModeBandChan
wg.Wait()
// ✅ Déterminer le statut
if len(contacts) == 0 {
spot.NewDXCC = true
}
if len(contactsMode) == 0 {
spot.NewMode = true
}
if len(contactsBand) == 0 {
spot.NewBand = true
}
if len(contactsModeBand) == 0 && !spot.NewDXCC && !spot.NewBand && !spot.NewMode {
spot.NewSlot = true
}
if len(contactsCall) > 0 {
spot.CallsignWorked = true
}
// ✅ Envoyer le spot
select {
case SpotChanToHTTPServer <- spot:
IncrementSpotsProcessed()
default:
IncrementSpotsRejected()
Log.Errorf("❌ Spot dropped (channel full): %s @ %s", spot.DX, spot.Frequency)
return
}
// ✅ LOGS CONCIS ET ADAPTES
statusIcon := ""
statusText := ""
if spot.NewDXCC {
statusIcon = "🆕"
statusText = "NEW DXCC"
} else if spot.NewBand && spot.NewMode {
statusIcon = "📻"
statusText = "NEW BAND+MODE"
} else if spot.NewBand {
statusIcon = "📡"
statusText = "NEW BAND"
} else if spot.NewMode {
statusIcon = "🔧"
statusText = "NEW MODE"
} else if spot.NewSlot {
statusIcon = "✨"
statusText = "NEW SLOT"
} else if spot.CallsignWorked {
statusIcon = "✓"
statusText = "WORKED"
} else {
statusIcon = "·"
statusText = "SPOT"
}
// ✅ Log unique et concis
Log.Debugf("%s [%s] %s on %.1f kHz (%s %s) - %s @ %s",
statusIcon,
statusText,
spot.DX,
mustParseFloat(spot.Frequency),
spot.Band,
spot.Mode,
spot.CountryName,
spot.Time,
)
}
// ✅ Helper pour convertir la fréquence
func mustParseFloat(s string) float64 {
f, _ := strconv.ParseFloat(s, 64)
return f
}
func (spot *TelnetSpot) GetBand() {
freq := FreqMhztoHz(spot.Frequency)
switch true {
case strings.HasPrefix(freq, "1.8"):
spot.Band = "160M"
if spot.Mode == "SSB" {
spot.Mode = "LSB"
}
case strings.HasPrefix(freq, "3."):
spot.Band = "80M"
if spot.Mode == "SSB" {
spot.Mode = "LSB"
}
case strings.HasPrefix(freq, "5."):
spot.Band = "60M"
if spot.Mode == "SSB" {
spot.Mode = "LSB"
}
case strings.HasPrefix(freq, "7."):
spot.Band = "40M"
if spot.Mode == "SSB" {
spot.Mode = "LSB"
}
case strings.HasPrefix(freq, "10."):
spot.Band = "30M"
case strings.HasPrefix(freq, "14."):
spot.Band = "20M"
if spot.Mode == "SSB" {
spot.Mode = "USB"
}
case strings.HasPrefix(freq, "18."):
spot.Band = "17M"
if spot.Mode == "SSB" {
spot.Mode = "USB"
}
case strings.HasPrefix(freq, "21."):
spot.Band = "15M"
if spot.Mode == "SSB" {
spot.Mode = "USB"
}
case strings.HasPrefix(freq, "24."):
spot.Band = "12M"
if spot.Mode == "SSB" {
spot.Mode = "USB"
}
case strings.HasPrefix(freq, "28."):
spot.Band = "10M"
if spot.Mode == "SSB" {
spot.Mode = "USB"
}
case strings.HasPrefix(freq, "29."):
spot.Band = "10M"
if spot.Mode == "SSB" {
spot.Mode = "USB"
}
case strings.HasPrefix(freq, "50."):
spot.Band = "6M"
if spot.Mode == "SSB" {
spot.Mode = "USB"
}
default:
spot.Band = "N/A"
}
}
func (spot *TelnetSpot) GuessMode(rawSpot string) {
// ✅ D'ABORD : Chercher le mode dans le commentaire
if spot.Mode == "" {
spot.Mode = extractModeFromComment(spot.Comment)
if spot.Mode != "" {
Log.Debugf("Mode extracted from comment: %s", spot.Mode)
}
}
// ✅ Normaliser SSB avant de deviner
if spot.Mode == "SSB" {
if spot.Band == "10M" || spot.Band == "12M" || spot.Band == "6M" || spot.Band == "15M" || spot.Band == "17M" || spot.Band == "20M" {
spot.Mode = "USB"
} else {
spot.Mode = "LSB"
}
Log.Debugf("Converted SSB to %s for band %s", spot.Mode, spot.Band)
return
}
// ✅ Si pas de mode, deviner depuis la fréquence
if spot.Mode == "" {
freqInt, err := strconv.ParseFloat(spot.Frequency, 32)
Log.Debugf("No mode specified in spot, will guess from frequency %v with spot: %s", spot.Frequency, strings.TrimSpace(rawSpot))
if err != nil {
Log.Errorf("could not convert frequency string to float: %v", err)
return
}
switch spot.Band {
case "160M": // 1.800 - 2.000 MHz
if freqInt < 1838 {
spot.Mode = "CW"
} else if freqInt < 1843 {
spot.Mode = "FT8"
} else {
spot.Mode = "LSB"
}
case "80M": // 3.500 - 4.000 MHz
if freqInt < 3570 {
spot.Mode = "CW"
} else if freqInt < 3575 {
spot.Mode = "FT4"
} else if freqInt < 3578 {
spot.Mode = "FT8"
} else if freqInt < 3590 {
spot.Mode = "RTTY"
} else {
spot.Mode = "LSB"
}
case "60M": // 5.330 - 5.405 MHz
if freqInt < 5357 {
spot.Mode = "CW"
} else if freqInt < 5359 {
spot.Mode = "FT8"
} else {
spot.Mode = "USB"
}
case "40M": // 7.000 - 7.300 MHz
if freqInt < 7040 {
spot.Mode = "CW"
} else if freqInt < 7047 {
spot.Mode = "RTTY"
} else if freqInt < 7050 {
spot.Mode = "FT4"
} else if freqInt < 7080 {
spot.Mode = "FT8" // ✅ 7.056 = FT8
} else if freqInt < 7125 {
spot.Mode = "RTTY" // ✅ 7.112 = RTTY
} else {
spot.Mode = "LSB"
}
case "30M": // 10.100 - 10.150 MHz (CW/Digital seulement)
if freqInt < 10130 {
spot.Mode = "CW"
} else if freqInt < 10142 {
spot.Mode = "FT8"
} else {
spot.Mode = "FT4"
}
case "20M": // 14.000 - 14.350 MHz
if freqInt < 14070 {
spot.Mode = "CW"
} else if freqInt < 14078 {
spot.Mode = "FT8"
} else if freqInt < 14083 {
spot.Mode = "FT4"
} else if freqInt < 14095 {
spot.Mode = "FT8"
} else if freqInt < 14112 {
spot.Mode = "RTTY"
} else {
spot.Mode = "USB"
}
case "17M": // 18.068 - 18.168 MHz
if freqInt < 18090 {
spot.Mode = "CW"
} else if freqInt < 18104 {
spot.Mode = "FT8"
} else if freqInt < 18106 {
spot.Mode = "FT4"
} else if freqInt < 18110 {
spot.Mode = "RTTY"
} else {
spot.Mode = "USB"
}
case "15M": // 21.000 - 21.450 MHz
if freqInt < 21070 {
spot.Mode = "CW"
} else if freqInt < 21078 {
spot.Mode = "FT8"
} else if freqInt < 21120 {
spot.Mode = "RTTY"
} else if freqInt < 21143 {
spot.Mode = "FT4"
} else {
spot.Mode = "USB"
}
case "12M": // 24.890 - 24.990 MHz
if freqInt < 24910 {
spot.Mode = "CW" // ✅ 24.896 = CW
} else if freqInt < 24918 {
spot.Mode = "FT8"
} else if freqInt < 24922 {
spot.Mode = "FT4"
} else if freqInt < 24930 {
spot.Mode = "RTTY"
} else {
spot.Mode = "USB"
}
case "10M": // 28.000 - 29.700 MHz
if freqInt < 28070 {
spot.Mode = "CW"
} else if freqInt < 28095 {
spot.Mode = "FT8"
} else if freqInt < 28179 {
spot.Mode = "RTTY"
} else if freqInt < 28190 {
spot.Mode = "FT4"
} else if freqInt < 29000 {
spot.Mode = "USB"
} else {
spot.Mode = "FM"
}
case "6M": // 50.000 - 54.000 MHz
if freqInt < 50100 {
spot.Mode = "CW"
} else if freqInt < 50313 {
spot.Mode = "USB" // ✅ DX Window + général
} else if freqInt < 50318 {
spot.Mode = "FT8" // ✅ 50.313-50.318
} else if freqInt < 50323 {
spot.Mode = "FT4" // ✅ 50.318-50.323
} else if freqInt < 51000 {
spot.Mode = "USB" // ✅ Retour à USB
} else {
spot.Mode = "FM"
}
default:
// ✅ Bande inconnue
if freqInt < 10.0 {
spot.Mode = "LSB"
} else {
spot.Mode = "USB"
}
}
if spot.Mode != "" {
Log.Debugf("✅ Guessed mode %s for %s on %s MHz (band %s)", spot.Mode, spot.DX, spot.Frequency, spot.Band)
} else {
Log.Warnf("❌ Could not guess mode for %s on %s MHz (band %s), raw spot: %s", spot.DX, spot.Frequency, spot.Band, rawSpot)
}
} else {
spot.Mode = strings.ToUpper(spot.Mode)
}
}
// ✅ Extraire le mode depuis le commentaire
func extractModeFromComment(comment string) string {
commentUpper := strings.ToUpper(comment)
// ✅ 1. Détecter FT8/FT4 avec leurs patterns typiques (dB + Hz)
if strings.Contains(commentUpper, "FT8") ||
(strings.Contains(commentUpper, "DB") && strings.Contains(commentUpper, "HZ")) {
return "FT8"
}
if strings.Contains(commentUpper, "FT4") {
return "FT4"
}
// ✅ 2. Détecter CW avec WPM (Words Per Minute)
if strings.Contains(commentUpper, "WPM") || strings.Contains(commentUpper, " CW ") ||
strings.HasSuffix(commentUpper, "CW") || strings.HasPrefix(commentUpper, "CW ") {
return "CW"
}
// ✅ 3. Autres modes digitaux
digitalModes := []string{"RTTY", "PSK31", "PSK63", "PSK", "MFSK", "OLIVIA", "CONTESTIA", "JT65", "JT9"}
for _, mode := range digitalModes {
if strings.Contains(commentUpper, mode) {
return mode
}
}
// ✅ 4. Modes voice
voiceModes := []string{"USB", "LSB", "SSB", "FM", "AM"}
for _, mode := range voiceModes {
// Chercher le mode comme mot complet (pas dans "SSBC" par exemple)
if strings.Contains(commentUpper, " "+mode+" ") ||
strings.HasPrefix(commentUpper, mode+" ") ||
strings.HasSuffix(commentUpper, " "+mode) ||
commentUpper == mode {
return mode
}
}
return ""
}