diff --git a/TCPClient.go b/TCPClient.go index 6511101..9fe940b 100644 --- a/TCPClient.go +++ b/TCPClient.go @@ -15,7 +15,7 @@ import ( log "github.com/sirupsen/logrus" ) -var spotRe *regexp.Regexp = regexp.MustCompile(`DX\sde\s([\w\d]+).*:\s+(\d+.\d)\s+([\w\d\/]+)\s+(CW|SSB|FT8|FT4|RTTY|USB|LSB)?\s+(.*)\s\s\s+([\d]+\w{1})`) +var spotRe *regexp.Regexp = regexp.MustCompile(`DX\sde\s([\w\d]+).*:\s+(\d+.\d)\s+([\w\d\/]+)\s+(CW|cw|SSB|ssb|FT8|ft8|FT4|ft4|RTTY|rtty|USB|usb|LSB|lsb)?\s+(.*)\s\s\s+([\d]+\w{1})`) var defaultLoginRe *regexp.Regexp = regexp.MustCompile("[\\w\\d-_]+ login:") var defaultPasswordRe *regexp.Regexp = regexp.MustCompile("Password:") @@ -51,7 +51,7 @@ type TCPClient struct { maxReconnectDelay time.Duration } -func NewTCPClient(TCPServer *TCPServer, Countries Countries, contactRepo *Log4OMContactsRepository) *TCPClient { +func NewTCPClient(TCPServer *TCPServer, Countries Countries, contactRepo *Log4OMContactsRepository, spotChanToHTTPServer chan TelnetSpot) *TCPClient { ctx, cancel := context.WithCancel(context.Background()) return &TCPClient{ @@ -61,9 +61,9 @@ func NewTCPClient(TCPServer *TCPServer, Countries Countries, contactRepo *Log4OM Password: Cfg.Cluster.Password, MsgChan: TCPServer.MsgChan, CmdChan: TCPServer.CmdChan, + SpotChanToHTTPServer: spotChanToHTTPServer, SpotChanToFlex: make(chan TelnetSpot, 100), TCPServer: *TCPServer, - SpotChanToHTTPServer: make(chan TelnetSpot, 100), Countries: Countries, ContactRepo: contactRepo, ctx: ctx, diff --git a/flexradio.go b/flexradio.go index 8cdf0a6..551860d 100644 --- a/flexradio.go +++ b/flexradio.go @@ -8,7 +8,6 @@ import ( "net" "os" "regexp" - "strings" "time" ) @@ -60,7 +59,6 @@ type FlexClient struct { Reader *bufio.Reader Writer *bufio.Writer Conn *net.TCPConn - SpotChanToFlex chan TelnetSpot MsgChan chan string Repo FlexDXClusterRepository TCPServer *TCPServer @@ -82,10 +80,10 @@ func NewFlexClient(repo FlexDXClusterRepository, TCPServer *TCPServer, SpotChanT return &FlexClient{ Port: "4992", - SpotChanToFlex: SpotChanToFlex, MsgChan: TCPServer.MsgChan, Repo: repo, TCPServer: TCPServer, + HTTPServer: httpServer, IsConnected: false, Enabled: enabled, ctx: ctx, @@ -114,7 +112,6 @@ func (fc *FlexClient) resolveAddress() (string, error) { if Cfg.Flex.Discover { Log.Debug("Attempting FlexRadio discovery...") - // Timeout sur la découverte (10 secondes max) discoveryDone := make(chan struct { success bool discovery *Discovery @@ -197,32 +194,9 @@ func (fc *FlexClient) StartFlexClient() { if !fc.Enabled { Log.Info("FlexRadio integration disabled in config - skipping") - - // Consommer les spots pour éviter les blocages - go func() { - for { - select { - case <-fc.ctx.Done(): - return - case <-fc.SpotChanToFlex: - } - } - }() return } - // Goroutine pour envoyer les spots au Flex - go func() { - for { - select { - case <-fc.ctx.Done(): - return - case spot := <-fc.SpotChanToFlex: - fc.SendSpottoFlex(spot) - } - } - }() - for { select { case <-fc.ctx.Done(): @@ -289,188 +263,7 @@ func (fc *FlexClient) Close() { } } -func (fc *FlexClient) SendSpottoFlex(spot TelnetSpot) { - - freq := FreqMhztoHz(spot.Frequency) - - flexSpot := FlexSpot{ - CommandNumber: CommandNumber, - DX: spot.DX, - FrequencyMhz: freq, - FrequencyHz: spot.Frequency, - Band: spot.Band, - Mode: spot.Mode, - Source: "FlexDXCluster", - SpotterCallsign: spot.Spotter, - TimeStamp: time.Now().Unix(), - UTCTime: spot.Time, - LifeTime: Cfg.Flex.SpotLife, - OriginalComment: spot.Comment, - Comment: spot.Comment, - Color: "#ffeaeaea", - BackgroundColor: "#ff000000", - Priority: "5", - NewDXCC: spot.NewDXCC, - NewBand: spot.NewBand, - NewMode: spot.NewMode, - NewSlot: spot.NewSlot, - Worked: spot.CallsignWorked, - InWatchlist: false, - CountryName: spot.CountryName, - DXCC: spot.DXCC, - } - - flexSpot.Comment = flexSpot.Comment + " [" + flexSpot.Mode + "] [" + flexSpot.SpotterCallsign + "] [" + flexSpot.UTCTime + "]" - - if fc.HTTPServer != nil && fc.HTTPServer.Watchlist != nil { - if fc.HTTPServer.Watchlist.Matches(flexSpot.DX) { - flexSpot.InWatchlist = true - flexSpot.Comment = flexSpot.Comment + " [Watchlist]" - Log.Infof("🎯 Watchlist match: %s", flexSpot.DX) - } - } - - // If new DXCC - if spot.NewDXCC { - flexSpot.Priority = "1" - flexSpot.Comment = flexSpot.Comment + " [New DXCC]" - if Cfg.General.SpotColorNewEntity != "" && Cfg.General.BackgroundColorNewEntity != "" { - flexSpot.Color = Cfg.General.SpotColorNewEntity - flexSpot.BackgroundColor = Cfg.General.BackgroundColorNewEntity - } else { - flexSpot.Color = "#ff3bf908" - flexSpot.BackgroundColor = "#ff000000" - } - } else if spot.DX == Cfg.General.Callsign { - flexSpot.Priority = "1" - if Cfg.General.SpotColorMyCallsign != "" && Cfg.General.BackgroundColorMyCallsign != "" { - flexSpot.Color = Cfg.General.SpotColorMyCallsign - flexSpot.BackgroundColor = Cfg.General.BackgroundColorMyCallsign - } else { - flexSpot.Color = "#ffff0000" - flexSpot.BackgroundColor = "#ff000000" - } - } else if spot.CallsignWorked { - flexSpot.Priority = "5" - flexSpot.Comment = flexSpot.Comment + " [Worked]" - if Cfg.General.SpotColorWorked != "" && Cfg.General.BackgroundColorWorked != "" { - flexSpot.Color = Cfg.General.SpotColorWorked - flexSpot.BackgroundColor = Cfg.General.BackgroundColorWorked - } else { - flexSpot.Color = "#ff000000" - flexSpot.BackgroundColor = "#ff00c0c0" - } - } else if spot.NewMode && spot.NewBand { - flexSpot.Priority = "1" - flexSpot.Comment = flexSpot.Comment + " [New Band & Mode]" - if Cfg.General.SpotColorNewBandMode != "" && Cfg.General.BackgroundColorNewBandMode != "" { - flexSpot.Color = Cfg.General.SpotColorNewBandMode - flexSpot.BackgroundColor = Cfg.General.BackgroundColorNewBandMode - } else { - flexSpot.Color = "#ffc603fc" - flexSpot.BackgroundColor = "#ff000000" - } - } else if spot.NewMode && !spot.NewBand { - flexSpot.Priority = "2" - flexSpot.Comment = flexSpot.Comment + " [New Mode]" - if Cfg.General.SpotColorNewMode != "" && Cfg.General.BackgroundColorNewMode != "" { - flexSpot.Color = Cfg.General.SpotColorNewMode - flexSpot.BackgroundColor = Cfg.General.BackgroundColorNewMode - } else { - flexSpot.Color = "#fff9a908" - flexSpot.BackgroundColor = "#ff000000" - } - } else if spot.NewBand && !spot.NewMode { - flexSpot.Color = "#fff9f508" - flexSpot.Priority = "3" - flexSpot.BackgroundColor = "#ff000000" - flexSpot.Comment = flexSpot.Comment + " [New Band]" - } else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked && spot.NewSlot { - flexSpot.Color = "#ff91d2ff" - flexSpot.Priority = "5" - flexSpot.BackgroundColor = "#ff000000" - flexSpot.Comment = flexSpot.Comment + " [New Slot]" - } else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked { - flexSpot.Color = "#ffeaeaea" - flexSpot.Priority = "5" - flexSpot.BackgroundColor = "#ff000000" - } else { - flexSpot.Color = "#ffeaeaea" - flexSpot.Priority = "5" - flexSpot.BackgroundColor = "#ff000000" - } - - // Send notification to Gotify - Gotify(flexSpot) - - flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0") - - srcFlexSpot, err := fc.Repo.FindDXSameBand(flexSpot) - if err != nil { - Log.Debugf("Could not find the DX in the database: %v", err) - } - - var stringSpot string - - if srcFlexSpot.DX == "" { - fc.Repo.CreateSpot(flexSpot) - CommandNumber++ - - if fc.HTTPServer != nil { - fc.HTTPServer.broadcast <- WSMessage{ - Type: "spots", - Data: fc.Repo.GetAllSpots("0"), - } - } - - if fc.IsConnected { - stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", - flexSpot.CommandNumber, flexSpot.FrequencyMhz, - flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, - flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, - flexSpot.BackgroundColor, flexSpot.Priority) - CommandNumber++ - fc.SendSpot(stringSpot) - } - - } else if srcFlexSpot.DX != "" && srcFlexSpot.Band == flexSpot.Band { - fc.Repo.DeleteSpotByFlexSpotNumber(string(flexSpot.FlexSpotNumber)) - - if fc.IsConnected { - stringSpot = fmt.Sprintf("C%v|spot remove %v", flexSpot.CommandNumber, srcFlexSpot.FlexSpotNumber) - fc.SendSpot(stringSpot) - CommandNumber++ - } - - fc.Repo.CreateSpot(flexSpot) - CommandNumber++ - - if fc.IsConnected { - stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", - flexSpot.CommandNumber, flexSpot.FrequencyMhz, - flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, - flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, - flexSpot.BackgroundColor, flexSpot.Priority) - CommandNumber++ - fc.SendSpot(stringSpot) - } - - } else if srcFlexSpot.DX != "" && srcFlexSpot.Band != flexSpot.Band { - fc.Repo.CreateSpot(flexSpot) - CommandNumber++ - - if fc.IsConnected { - stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", - flexSpot.CommandNumber, flexSpot.FrequencyMhz, - flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, - flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, - flexSpot.BackgroundColor, flexSpot.Priority) - CommandNumber++ - fc.SendSpot(stringSpot) - } - } -} - +// ✅ SendSpot - Méthode simplifiée pour envoyer une commande au Flex func (fc *FlexClient) SendSpot(stringSpot string) { if fc.IsConnected { fc.Write(stringSpot) diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 2d50616..9ec958e 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -79,15 +79,17 @@ filteredSpots = applyFilters(spots, spotFilters, watchlist); } } + + $: if (typeof localStorage !== 'undefined') { + localStorage.setItem('soundEnabled', soundEnabled.toString()); + } // Détecter les nouveaux spots et jouer les sons appropriés $: if (spots.length > 0 && soundEnabled) { checkForNewSpots(spots, previousSpots, watchlist); - previousSpots = [...spots]; + // ✅ Ne garder que les 100 derniers spots pour la comparaison + previousSpots = spots.slice(0, 100); } - - // ✅ SUPPRIMÉ - La watchlist est gérée côté serveur via WebSocket - // Les fonctions addToWatchlist et removeFromWatchlist ne sont plus nécessaires function checkForNewSpots(currentSpots, prevSpots, wl) { // Ne pas jouer de sons au chargement initial @@ -318,6 +320,14 @@ case 'dxccProgress': dxccProgress = message.data || { worked: 0, total: 340, percentage: 0 }; break; + case 'milestone': // ✅ AJOUTER + const milestoneData = message.data; + const toastType = milestoneData.type === 'qso' ? 'milestone' : 'band'; + showToast(milestoneData.message, toastType); + if (soundEnabled) { + playSound('milestone'); + } + break; } } @@ -425,11 +435,22 @@ } onMount(() => { + const savedSoundEnabled = localStorage.getItem('soundEnabled'); + if (savedSoundEnabled !== null) { + soundEnabled = savedSoundEnabled === 'true'; + } connectWebSocket(); fetchSolarData(); - + const solarInterval = setInterval(fetchSolarData, 15 * 60 * 1000); - + + const cleanupInterval = setInterval(() => { + // Nettoyer previousSpots + if (previousSpots.length > 100) { + previousSpots = previousSpots.slice(0, 100); + } + }, 60000); + const handleSendSpot = (e) => { sendCallsign(e.detail.callsign, e.detail.frequency, e.detail.mode); }; @@ -444,7 +465,7 @@ }); -
+
@@ -460,7 +481,9 @@ {stats} {solarData} {wsStatus} - on:shutdown={shutdownApp} + {soundEnabled} + on:shutdown={shutdownApp} + on:toggleSound={() => soundEnabled = !soundEnabled} /> toggleFilter(e.detail)} /> -
-
- +
+ import { createEventDispatcher } from 'svelte'; + export let soundEnabled = true; export let stats; export let solarData; export let wsStatus; @@ -50,7 +51,7 @@ FlexDXCluster
- F4BPO • {stats.totalContacts} Contacts + {stats.myCallsign || 'N/A'} • {stats.totalContacts} Contacts | SFI: @@ -100,6 +101,25 @@ Flex + + +