up
This commit is contained in:
@@ -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,
|
||||
|
||||
211
flexradio.go
211
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)
|
||||
|
||||
@@ -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 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white min-h-screen p-4">
|
||||
<div class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white min-h-screen p-2">
|
||||
<!-- Gestionnaire de sons -->
|
||||
<SoundManager bind:enabled={soundEnabled} />
|
||||
|
||||
@@ -460,7 +481,9 @@
|
||||
{stats}
|
||||
{solarData}
|
||||
{wsStatus}
|
||||
on:shutdown={shutdownApp}
|
||||
{soundEnabled}
|
||||
on:shutdown={shutdownApp}
|
||||
on:toggleSound={() => soundEnabled = !soundEnabled}
|
||||
/>
|
||||
|
||||
<StatsCards
|
||||
@@ -475,9 +498,9 @@
|
||||
on:toggleFilter={(e) => toggleFilter(e.detail)}
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-4 gap-3 overflow-hidden" style="height: calc(100vh - 360px); min-height: 500px;">
|
||||
<div class="col-span-3 overflow-hidden">
|
||||
<SpotsTable
|
||||
<div class="grid grid-cols-[2.8fr_1.2fr] gap-3 overflow-hidden" style="height: calc(100vh - 280px); min-height: 500px;">
|
||||
<div class="overflow-hidden">
|
||||
<SpotsTable
|
||||
spots={filteredSpots}
|
||||
{watchlist}
|
||||
myCallsign={stats.myCallsign}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let soundEnabled = true;
|
||||
export let stats;
|
||||
export let solarData;
|
||||
export let wsStatus;
|
||||
@@ -50,7 +51,7 @@
|
||||
FlexDXCluster
|
||||
</h1>
|
||||
<div class="flex items-center gap-3 text-xs text-slate-400">
|
||||
<span>F4BPO • <span>{stats.totalContacts}</span> Contacts</span>
|
||||
<span>{stats.myCallsign || 'N/A'} • <span>{stats.totalContacts}</span> Contacts</span>
|
||||
<span class="text-slate-600">|</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="font-semibold text-amber-400">SFI:</span>
|
||||
@@ -100,6 +101,25 @@
|
||||
Flex
|
||||
</span>
|
||||
|
||||
<!-- Bouton Son -->
|
||||
<button
|
||||
on:click={() => dispatch('toggleSound')}
|
||||
class="px-3 py-1.5 rounded-lg transition-colors flex items-center gap-2 text-sm {soundEnabled ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-slate-700/50 hover:bg-slate-700 text-slate-300'}"
|
||||
title={soundEnabled ? 'Mute sounds' : 'Enable sounds'}>
|
||||
{#if soundEnabled}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"></path>
|
||||
</svg>
|
||||
<span>Sound On</span>
|
||||
{:else}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" clip-rule="evenodd"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"></path>
|
||||
</svg>
|
||||
<span>Sound Off</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => dispatch('shutdown')}
|
||||
class="px-3 py-1.5 text-xs bg-red-600 hover:bg-red-700 rounded transition-colors flex items-center gap-1">
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
});
|
||||
|
||||
function handlePlaySound(event) {
|
||||
if (!enabled || isMuted) return;
|
||||
if (!enabled) return; // ✅ Utiliser 'enabled' (qui vient de App.svelte) au lieu de 'isMuted'
|
||||
|
||||
const { type } = event.detail;
|
||||
|
||||
@@ -89,10 +89,20 @@
|
||||
oscillator.start(startTime);
|
||||
oscillator.stop(startTime + duration);
|
||||
}
|
||||
|
||||
function playMilestoneSound() {
|
||||
if (!audioContext) return;
|
||||
|
||||
const now = audioContext.currentTime;
|
||||
// Mélodie festive : E5 -> G5 -> A5 -> C6
|
||||
playBeep(now, 659.25, 0.15);
|
||||
playBeep(now + 0.15, 783.99, 0.15);
|
||||
playBeep(now + 0.3, 880.00, 0.15);
|
||||
playBeep(now + 0.45, 1046.50, 0.2);
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
isMuted = !isMuted;
|
||||
localStorage.setItem('soundMuted', isMuted.toString());
|
||||
enabled = !enabled; // ✅ Modifier 'enabled' au lieu de 'isMuted'
|
||||
}
|
||||
|
||||
function updateVolume(newVolume) {
|
||||
@@ -105,9 +115,9 @@
|
||||
<div class="fixed bottom-4 right-4 flex items-center gap-2 bg-slate-800/90 backdrop-blur rounded-lg border border-slate-700/50 p-2 shadow-lg z-50">
|
||||
<button
|
||||
on:click={toggleMute}
|
||||
class="p-2 rounded transition-colors {isMuted ? 'bg-red-600/20 text-red-400 hover:bg-red-600/30' : 'bg-slate-700/50 text-slate-300 hover:bg-slate-700'}"
|
||||
title={isMuted ? 'Unmute sounds' : 'Mute sounds'}>
|
||||
{#if isMuted}
|
||||
class="p-2 rounded transition-colors {!enabled ? 'bg-red-600/20 text-red-400 hover:bg-red-600/30' : 'bg-slate-700/50 text-slate-300 hover:bg-slate-700'}"
|
||||
title={!enabled ? 'Unmute sounds' : 'Mute sounds'}>
|
||||
{#if !enabled}
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" clip-rule="evenodd"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"></path>
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
<script>
|
||||
export let message;
|
||||
export let type = 'info'; // 'success', 'error', 'warning', 'info'
|
||||
export let type = 'info'; // 'success', 'error', 'warning', 'info', 'milestone', 'band'
|
||||
|
||||
const icons = {
|
||||
success: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>`,
|
||||
error: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>`,
|
||||
warning: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>`,
|
||||
info: ''
|
||||
info: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>`,
|
||||
milestone: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path>`,
|
||||
band: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>`
|
||||
};
|
||||
|
||||
const colors = {
|
||||
success: 'bg-green-500',
|
||||
error: 'bg-red-500',
|
||||
warning: 'bg-orange-500',
|
||||
info: 'bg-blue-500'
|
||||
info: 'bg-blue-500',
|
||||
milestone: 'bg-gradient-to-r from-purple-500 to-pink-500',
|
||||
band: 'bg-gradient-to-r from-orange-500 to-amber-500'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="fixed bottom-5 right-5 {colors[type]} text-white px-5 py-3 rounded-lg shadow-lg z-50 animate-in slide-in-from-bottom-5 duration-300">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="fixed bottom-5 right-5 {colors[type]} text-white px-5 py-3 rounded-lg shadow-lg z-50 animate-in slide-in-from-bottom-5 duration-300 min-w-[300px] backdrop-blur-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
{#if icons[type]}
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{@html icons[type]}
|
||||
</svg>
|
||||
{/if}
|
||||
<span>{message}</span>
|
||||
<span class="font-medium text-sm">{message}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes slide-in-from-bottom {
|
||||
from {
|
||||
transform: translateY(400px);
|
||||
transform: translateY(100px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
|
||||
@@ -75,17 +75,24 @@
|
||||
).length;
|
||||
}
|
||||
|
||||
function getMatchingSpotsForCallsign(callsign) {
|
||||
const spots = watchlistSpots.filter(s => s.dx === callsign || s.dx.startsWith(callsign));
|
||||
function getMatchingSpotsForCallsign(callsign) {
|
||||
const spots = watchlistSpots.filter(s => s.dx === callsign || s.dx.startsWith(callsign));
|
||||
|
||||
// ✅ Trier par bande d'abord, puis par heure
|
||||
const bandOrder = { '160M': 0, '80M': 1, '60M': 2, '40M': 3, '30M': 4, '20M': 5, '17M': 6, '15M': 7, '12M': 8, '10M': 9, '6M': 10 };
|
||||
|
||||
return spots.sort((a, b) => {
|
||||
// Trier par bande en premier
|
||||
const bandA = bandOrder[a.band] ?? 99;
|
||||
const bandB = bandOrder[b.band] ?? 99;
|
||||
if (bandA !== bandB) return bandA - bandB;
|
||||
|
||||
// ✅ Trier les spots par heure décroissante (plus récent en premier)
|
||||
return spots.sort((a, b) => {
|
||||
// Comparer les heures UTC (format "HH:MM")
|
||||
const timeA = a.utcTime || "00:00";
|
||||
const timeB = b.utcTime || "00:00";
|
||||
return timeB.localeCompare(timeA);
|
||||
});
|
||||
}
|
||||
// Si même bande, trier par heure (plus récent en premier)
|
||||
const timeA = a.utcTime || "00:00";
|
||||
const timeB = b.utcTime || "00:00";
|
||||
return timeB.localeCompare(timeA);
|
||||
});
|
||||
}
|
||||
|
||||
async function addToWatchlist() {
|
||||
const callsign = newCallsign.trim().toUpperCase();
|
||||
|
||||
128
httpserver.go
128
httpserver.go
@@ -22,21 +22,23 @@ import (
|
||||
var frontendFiles embed.FS
|
||||
|
||||
type HTTPServer struct {
|
||||
Router *mux.Router
|
||||
FlexRepo *FlexDXClusterRepository
|
||||
ContactRepo *Log4OMContactsRepository
|
||||
TCPServer *TCPServer
|
||||
TCPClient *TCPClient
|
||||
FlexClient *FlexClient
|
||||
Port string
|
||||
Log *log.Logger
|
||||
statsCache Stats
|
||||
statsMutex sync.RWMutex
|
||||
lastUpdate time.Time
|
||||
wsClients map[*websocket.Conn]bool
|
||||
wsMutex sync.RWMutex
|
||||
broadcast chan WSMessage
|
||||
Watchlist *Watchlist
|
||||
Router *mux.Router
|
||||
FlexRepo *FlexDXClusterRepository
|
||||
ContactRepo *Log4OMContactsRepository
|
||||
TCPServer *TCPServer
|
||||
TCPClient *TCPClient
|
||||
FlexClient *FlexClient
|
||||
Port string
|
||||
Log *log.Logger
|
||||
lastQSOCount int
|
||||
lastBandOpening map[string]time.Time
|
||||
statsCache Stats
|
||||
statsMutex sync.RWMutex
|
||||
lastUpdate time.Time
|
||||
wsClients map[*websocket.Conn]bool
|
||||
wsMutex sync.RWMutex
|
||||
broadcast chan WSMessage
|
||||
Watchlist *Watchlist
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
@@ -99,17 +101,19 @@ func NewHTTPServer(flexRepo *FlexDXClusterRepository, contactRepo *Log4OMContact
|
||||
tcpServer *TCPServer, tcpClient *TCPClient, flexClient *FlexClient, port string) *HTTPServer {
|
||||
|
||||
server := &HTTPServer{
|
||||
Router: mux.NewRouter(),
|
||||
FlexRepo: flexRepo,
|
||||
ContactRepo: contactRepo,
|
||||
TCPServer: tcpServer,
|
||||
TCPClient: tcpClient,
|
||||
FlexClient: flexClient,
|
||||
Port: port,
|
||||
Log: Log,
|
||||
wsClients: make(map[*websocket.Conn]bool),
|
||||
broadcast: make(chan WSMessage, 256),
|
||||
Watchlist: NewWatchlist("watchlist.json"),
|
||||
Router: mux.NewRouter(),
|
||||
FlexRepo: flexRepo,
|
||||
ContactRepo: contactRepo,
|
||||
TCPServer: tcpServer,
|
||||
TCPClient: tcpClient,
|
||||
FlexClient: flexClient,
|
||||
Port: port,
|
||||
Log: Log,
|
||||
wsClients: make(map[*websocket.Conn]bool),
|
||||
broadcast: make(chan WSMessage, 256),
|
||||
Watchlist: NewWatchlist("watchlist.json"),
|
||||
lastQSOCount: 0,
|
||||
lastBandOpening: make(map[string]time.Time),
|
||||
}
|
||||
|
||||
server.setupRoutes()
|
||||
@@ -239,7 +243,7 @@ func (s *HTTPServer) sendInitialData(conn *websocket.Conn) {
|
||||
conn.WriteJSON(WSMessage{Type: "watchlist", Data: watchlist})
|
||||
|
||||
// Send initial log data
|
||||
qsos := s.ContactRepo.GetRecentQSOs("8")
|
||||
qsos := s.ContactRepo.GetRecentQSOs("13")
|
||||
conn.WriteJSON(WSMessage{Type: "log", Data: qsos})
|
||||
|
||||
logStats := s.ContactRepo.GetQSOStats()
|
||||
@@ -276,8 +280,11 @@ func (s *HTTPServer) handleBroadcasts() {
|
||||
func (s *HTTPServer) broadcastUpdates() {
|
||||
statsTicker := time.NewTicker(1 * time.Second)
|
||||
logTicker := time.NewTicker(10 * time.Second)
|
||||
cleanupTicker := time.NewTicker(5 * time.Minute) // ✅ AJOUTER
|
||||
|
||||
defer statsTicker.Stop()
|
||||
defer logTicker.Stop()
|
||||
defer cleanupTicker.Stop() // ✅ AJOUTER
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -295,7 +302,8 @@ func (s *HTTPServer) broadcastUpdates() {
|
||||
s.broadcast <- WSMessage{Type: "stats", Data: stats}
|
||||
|
||||
// Broadcast spots
|
||||
spots := s.FlexRepo.GetAllSpots("0")
|
||||
spots := s.FlexRepo.GetAllSpots("300")
|
||||
s.checkBandOpening(spots)
|
||||
s.broadcast <- WSMessage{Type: "spots", Data: spots}
|
||||
|
||||
// Broadcast spotters
|
||||
@@ -312,10 +320,11 @@ func (s *HTTPServer) broadcastUpdates() {
|
||||
}
|
||||
|
||||
// Broadcast log data every 10 seconds
|
||||
qsos := s.ContactRepo.GetRecentQSOs("8")
|
||||
qsos := s.ContactRepo.GetRecentQSOs("13")
|
||||
s.broadcast <- WSMessage{Type: "log", Data: qsos}
|
||||
|
||||
stats := s.ContactRepo.GetQSOStats()
|
||||
s.checkQSOMilestones(stats.Today)
|
||||
s.broadcast <- WSMessage{Type: "logStats", Data: stats}
|
||||
|
||||
dxccCount := s.ContactRepo.GetDXCCCount()
|
||||
@@ -324,11 +333,72 @@ func (s *HTTPServer) broadcastUpdates() {
|
||||
"total": 340,
|
||||
"percentage": float64(dxccCount) / 340.0 * 100.0,
|
||||
}
|
||||
|
||||
s.broadcast <- WSMessage{Type: "dxccProgress", Data: dxccData}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) checkQSOMilestones(todayCount int) {
|
||||
s.statsMutex.Lock()
|
||||
defer s.statsMutex.Unlock()
|
||||
|
||||
if todayCount == s.lastQSOCount {
|
||||
return
|
||||
}
|
||||
|
||||
milestones := []int{5, 10, 25, 50, 100, 200, 500}
|
||||
|
||||
for _, milestone := range milestones {
|
||||
if todayCount >= milestone && s.lastQSOCount < milestone {
|
||||
s.broadcast <- WSMessage{
|
||||
Type: "milestone",
|
||||
Data: map[string]interface{}{
|
||||
"type": "qso",
|
||||
"count": milestone,
|
||||
"message": fmt.Sprintf("🎉 %d QSOs today!", milestone),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.lastQSOCount = todayCount
|
||||
}
|
||||
|
||||
func (s *HTTPServer) checkBandOpening(spots []FlexSpot) {
|
||||
s.statsMutex.Lock()
|
||||
defer s.statsMutex.Unlock()
|
||||
|
||||
bandCounts := make(map[string]int)
|
||||
for _, spot := range spots {
|
||||
bandCounts[spot.Band]++
|
||||
}
|
||||
|
||||
// ✅ Seulement surveiller 6M, 10M et 12M
|
||||
monitoredBands := []string{"6M", "10M", "12M"}
|
||||
|
||||
now := time.Now()
|
||||
for _, band := range monitoredBands {
|
||||
count := bandCounts[band]
|
||||
if count >= 20 { // Si 20+ spots sur une bande
|
||||
lastSeen, exists := s.lastBandOpening[band]
|
||||
// Notifier si première fois ou si pas vu depuis 2 heures
|
||||
if !exists || now.Sub(lastSeen) > 2*time.Hour {
|
||||
s.lastBandOpening[band] = now
|
||||
s.broadcast <- WSMessage{
|
||||
Type: "milestone",
|
||||
Data: map[string]interface{}{
|
||||
"type": "band",
|
||||
"band": band,
|
||||
"count": count,
|
||||
"message": fmt.Sprintf("📡 %s opening detected! (%d spots)", band, count),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) getRecentQSOs(w http.ResponseWriter, r *http.Request) {
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
if limitStr == "" {
|
||||
|
||||
12
main.go
12
main.go
@@ -68,18 +68,24 @@ func main() {
|
||||
defer cRepo.db.Close()
|
||||
contacts := cRepo.CountEntries()
|
||||
log.Infof("Log4OM Database Contains %v Contacts", contacts)
|
||||
defer cRepo.db.Close()
|
||||
|
||||
// ✅ Créer le canal pour le traitement centralisé des spots
|
||||
SpotChanToHTTPServer := make(chan TelnetSpot, 100)
|
||||
|
||||
// Initialize servers and clients
|
||||
TCPServer := NewTCPServer(cfg.TelnetServer.Host, cfg.TelnetServer.Port)
|
||||
TCPClient := NewTCPClient(TCPServer, Countries, cRepo)
|
||||
FlexClient := NewFlexClient(*fRepo, TCPServer, TCPClient.SpotChanToFlex, nil)
|
||||
TCPClient := NewTCPClient(TCPServer, Countries, cRepo, SpotChanToHTTPServer)
|
||||
FlexClient := NewFlexClient(*fRepo, TCPServer, nil, nil)
|
||||
|
||||
// Initialize HTTP Server for Dashboard
|
||||
HTTPServer := NewHTTPServer(fRepo, cRepo, TCPServer, TCPClient, FlexClient, "8080")
|
||||
|
||||
FlexClient.HTTPServer = HTTPServer
|
||||
|
||||
spotProcessor := NewSpotProcessor(fRepo, FlexClient, HTTPServer, SpotChanToHTTPServer)
|
||||
|
||||
go spotProcessor.Start()
|
||||
|
||||
// Start all services
|
||||
go FlexClient.StartFlexClient()
|
||||
go TCPClient.StartClient()
|
||||
|
||||
27
spot.go
27
spot.go
@@ -28,8 +28,6 @@ type TelnetSpot struct {
|
||||
CallsignWorked bool
|
||||
}
|
||||
|
||||
// var spotNumber = 1
|
||||
|
||||
func ProcessTelnetSpot(re *regexp.Regexp, spotRaw string, SpotChanToFlex chan TelnetSpot, SpotChanToHTTPServer chan TelnetSpot, Countries Countries, contactRepo *Log4OMContactsRepository) {
|
||||
|
||||
match := re.FindStringSubmatch(spotRaw)
|
||||
@@ -106,9 +104,16 @@ func ProcessTelnetSpot(re *regexp.Regexp, spotRaw string, SpotChanToFlex chan Te
|
||||
spot.CallsignWorked = true
|
||||
}
|
||||
|
||||
// Send spots to FlexRadio
|
||||
SpotChanToFlex <- spot
|
||||
// Envoyer TOUJOURS le spot vers le processeur principal (base de données + HTTP)
|
||||
// Ce canal est maintenant géré par une goroutine dans main.go
|
||||
select {
|
||||
case SpotChanToHTTPServer <- spot:
|
||||
// Spot envoyé avec succès
|
||||
default:
|
||||
Log.Warn("SpotChanToHTTPServer is full, spot may be lost")
|
||||
}
|
||||
|
||||
// Logging des spots
|
||||
if spot.NewDXCC {
|
||||
Log.Debugf("(** New DXCC **) DX: %s - Spotter: %s - Freq: %s - Band: %s - Mode: %s - Comment: %s - Time: %s - DXCC: %s",
|
||||
spot.DX, spot.Spotter, spot.Frequency, spot.Band, spot.Mode, spot.Comment, spot.Time, spot.DXCC)
|
||||
@@ -143,12 +148,7 @@ func ProcessTelnetSpot(re *regexp.Regexp, spotRaw string, SpotChanToFlex chan Te
|
||||
Log.Debugf("DX: %s - Spotter: %s - Freq: %s - Band: %s - Mode: %s - Comment: %s - Time: %s - DXCC: %s",
|
||||
spot.DX, spot.Spotter, spot.Frequency, spot.Band, spot.Mode, spot.Comment, spot.Time, spot.DXCC)
|
||||
}
|
||||
} else {
|
||||
// Log.Infof("Could not decode: %s", strings.Trim(spotRaw, "\n"))
|
||||
}
|
||||
|
||||
// Log.Infof("Spots Processed: %v", spotNumber)
|
||||
// spotNumber++
|
||||
}
|
||||
|
||||
func (spot *TelnetSpot) GetBand() {
|
||||
@@ -381,6 +381,15 @@ func (spot *TelnetSpot) GuessMode() {
|
||||
spot.Mode = "FM"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spot.Mode = strings.ToUpper(spot.Mode)
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if spot.Mode == "" {
|
||||
|
||||
201
spotprocessor.go
Normal file
201
spotprocessor.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SpotProcessor struct {
|
||||
FlexRepo *FlexDXClusterRepository
|
||||
FlexClient *FlexClient
|
||||
HTTPServer *HTTPServer
|
||||
SpotChan chan TelnetSpot
|
||||
}
|
||||
|
||||
func NewSpotProcessor(flexRepo *FlexDXClusterRepository, flexClient *FlexClient, httpServer *HTTPServer, spotChan chan TelnetSpot) *SpotProcessor {
|
||||
return &SpotProcessor{
|
||||
FlexRepo: flexRepo,
|
||||
FlexClient: flexClient,
|
||||
HTTPServer: httpServer,
|
||||
SpotChan: spotChan,
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SpotProcessor) Start() {
|
||||
Log.Info("Starting Spot Processor...")
|
||||
|
||||
for spot := range sp.SpotChan {
|
||||
sp.processSpot(spot)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SpotProcessor) processSpot(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 sp.HTTPServer != nil && sp.HTTPServer.Watchlist != nil {
|
||||
if sp.HTTPServer.Watchlist.Matches(flexSpot.DX) {
|
||||
flexSpot.InWatchlist = true
|
||||
flexSpot.Comment = flexSpot.Comment + " [Watchlist]"
|
||||
Log.Infof("🎯 Watchlist match: %s", flexSpot.DX)
|
||||
}
|
||||
}
|
||||
|
||||
sp.applySpotColors(&flexSpot, spot)
|
||||
Gotify(flexSpot)
|
||||
|
||||
flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0")
|
||||
|
||||
srcFlexSpot, err := sp.FlexRepo.FindDXSameBand(flexSpot)
|
||||
if err != nil {
|
||||
Log.Debugf("Could not find the DX in the database: %v", err)
|
||||
}
|
||||
|
||||
// Vérifier si le spot trouvé est valide (a un ID)
|
||||
if srcFlexSpot != nil && srcFlexSpot.ID == 0 {
|
||||
srcFlexSpot = nil
|
||||
}
|
||||
|
||||
sp.handleSpotStorage(flexSpot, srcFlexSpot)
|
||||
|
||||
if sp.FlexClient != nil && sp.FlexClient.Enabled && sp.FlexClient.IsConnected {
|
||||
sp.sendToFlexRadio(flexSpot, srcFlexSpot)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SpotProcessor) applySpotColors(flexSpot *FlexSpot, spot TelnetSpot) {
|
||||
if spot.NewDXCC {
|
||||
flexSpot.Priority = "1"
|
||||
flexSpot.Comment = flexSpot.Comment + " [New DXCC]"
|
||||
if Cfg.General.SpotColorNewEntity != "" {
|
||||
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 != "" {
|
||||
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 != "" {
|
||||
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 != "" {
|
||||
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 != "" {
|
||||
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]"
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SpotProcessor) handleSpotStorage(flexSpot FlexSpot, srcFlexSpot *FlexSpot) {
|
||||
if srcFlexSpot == nil {
|
||||
sp.FlexRepo.CreateSpot(flexSpot)
|
||||
CommandNumber++
|
||||
if sp.HTTPServer != nil {
|
||||
sp.HTTPServer.broadcast <- WSMessage{Type: "spots", Data: sp.FlexRepo.GetAllSpots("0")}
|
||||
}
|
||||
} else if srcFlexSpot.Band == flexSpot.Band {
|
||||
sp.FlexRepo.DeleteSpotByFlexSpotNumber(fmt.Sprintf("%d", srcFlexSpot.FlexSpotNumber))
|
||||
sp.FlexRepo.CreateSpot(flexSpot)
|
||||
CommandNumber++
|
||||
} else {
|
||||
sp.FlexRepo.CreateSpot(flexSpot)
|
||||
CommandNumber++
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SpotProcessor) sendToFlexRadio(flexSpot FlexSpot, srcFlexSpot *FlexSpot) {
|
||||
var stringSpot string
|
||||
|
||||
if srcFlexSpot == nil {
|
||||
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++
|
||||
sp.FlexClient.SendSpot(stringSpot)
|
||||
} else if srcFlexSpot.Band == flexSpot.Band {
|
||||
stringSpot = fmt.Sprintf("C%v|spot remove %v", flexSpot.CommandNumber, srcFlexSpot.FlexSpotNumber)
|
||||
sp.FlexClient.SendSpot(stringSpot)
|
||||
CommandNumber++
|
||||
|
||||
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++
|
||||
sp.FlexClient.SendSpot(stringSpot)
|
||||
} else {
|
||||
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++
|
||||
sp.FlexClient.SendSpot(stringSpot)
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
["H44MS","5X2I","PY0FB","PY0FBS","XT2AW","ZL7IO","YJ0CA","FW5K","J38","E6AD","E51MWA","PJ6Y","5J0EA","5K0UA","VP2M","5X1XA","C5R","C5LT","EL2BG","4X6TT","V85NPV","YI1MB"]
|
||||
["H44MS","5X2I","PY0FB","PY0FBS","XT2AW","ZL7IO","YJ0CA","FW5K","J38","E6AD","E51MWA","PJ6Y","5J0EA","5K0UA","VP2M","5X1XA","C5R","C5LT","EL2BG","4X6TT","V85NPV","YI1MB","C21TS","XV9","TJ1GD"]
|
||||
Reference in New Issue
Block a user