This commit is contained in:
2026-02-28 10:52:04 +01:00
parent 0f2dc76d55
commit 238716fdae
5 changed files with 209 additions and 113 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7,11 +7,10 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-BqlArLJ0.js"></script> <script type="module" crossorigin src="/assets/index-Drom3Zfz.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bl7hatTL.css"> <link rel="stylesheet" crossorigin href="/assets/index-pnuRxXpy.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
</body> </body>

View File

@@ -45,6 +45,10 @@ type Client struct {
onFrequencyChange func(freqMHz float64) onFrequencyChange func(freqMHz float64)
checkTransmitAllowed func() bool checkTransmitAllowed func() bool
// Track current slice frequency
currentFreq float64
currentFreqMu sync.RWMutex
} }
func New(host string, port int) *Client { func New(host string, port int) *Client {
@@ -59,7 +63,11 @@ func New(host string, port int) *Client {
lastStatus: &Status{ lastStatus: &Status{
Connected: false, Connected: false,
RadioOn: false, RadioOn: false,
Tx: false, // Initialisé à false
ActiveSlices: 0,
Frequency: 0,
}, },
currentFreq: 0,
} }
} }
@@ -318,21 +326,108 @@ func (c *Client) handleMessage(msg string) {
// DEBUG: Log tous les messages reçus // DEBUG: Log tous les messages reçus
log.Printf("FlexRadio RAW: %s", msg) log.Printf("FlexRadio RAW: %s", msg)
// Router selon le premier caractère // Vérifier le type de message
switch msg[0] { if len(msg) < 2 {
case 'R': // Réponse à une commande return
}
// Messages commençant par R (réponses)
if msg[0] == 'R' {
c.handleCommandResponse(msg) c.handleCommandResponse(msg)
case 'S': // Message de statut return
c.handleStatusMessage(msg) }
case 'V': // Version/Handle
// Messages commençant par S (statut)
if msg[0] == 'S' {
// Enlever le préfixe S
msg = msg[1:]
// Séparer handle et données
parts := strings.SplitN(msg, "|", 2)
if len(parts) < 2 {
return
}
handle := parts[0]
data := parts[1]
// Parser les paires clé=valeur
statusMap := make(map[string]string)
pairs := strings.Fields(data)
for _, pair := range pairs {
if kv := strings.SplitN(pair, "=", 2); len(kv) == 2 {
statusMap[kv[0]] = kv[1]
}
}
// Identifier le type de message
if strings.Contains(data, "interlock") {
c.handleInterlockStatus(handle, statusMap)
} else if strings.Contains(data, "slice") {
c.handleSliceStatus(handle, statusMap)
} else if strings.Contains(data, "radio") {
c.handleRadioStatus(handle, statusMap)
} else {
// Vérifier si c'est une mise à jour de fréquence
if freqStr, ok := statusMap["RF_frequency"]; ok {
c.handleFrequencyUpdate(handle, freqStr, statusMap)
} else {
log.Printf("FlexRadio: Message inconnu (handle=%s): %s", handle, data)
}
}
return
}
// Autres types de messages
switch msg[0] {
case 'V':
log.Printf("FlexRadio: Version/Handle: %s", msg) log.Printf("FlexRadio: Version/Handle: %s", msg)
case 'M': // Message général case 'M':
log.Printf("FlexRadio: Message: %s", msg) log.Printf("FlexRadio: Message: %s", msg)
default: default:
log.Printf("FlexRadio: Unknown message type: %s", msg) log.Printf("FlexRadio: Type de message inconnu: %s", msg)
} }
} }
func (c *Client) handleSliceStatus(handle string, statusMap map[string]string) {
c.statusMu.Lock()
defer c.statusMu.Unlock()
if c.lastStatus == nil {
return
}
// Mettre à jour le nombre de slices actives
c.lastStatus.ActiveSlices = 1
// Mettre à jour la fréquence
if rfFreq, ok := statusMap["RF_frequency"]; ok {
if freq, err := strconv.ParseFloat(rfFreq, 64); err == nil && freq > 0 {
oldFreq := c.lastStatus.Frequency
c.lastStatus.Frequency = freq
c.lastStatus.RadioInfo = fmt.Sprintf("Active on %.3f MHz", freq)
// Déclencher le callback si la fréquence a changé
if oldFreq != freq && c.onFrequencyChange != nil {
go c.onFrequencyChange(freq)
}
} else if freq == 0 {
// Fréquence 0 = slice inactive
c.lastStatus.Frequency = 0
c.lastStatus.RadioInfo = "Slice inactive"
}
}
// Mettre à jour le mode
if mode, ok := statusMap["mode"]; ok {
c.lastStatus.Mode = mode
}
// NE PAS utiliser tx du slice pour l'état TX réel
// tx=1 dans le slice signifie seulement "capable de TX", pas "en train de TX"
// L'état TX réel vient de l'interlock
}
func (c *Client) handleCommandResponse(msg string) { func (c *Client) handleCommandResponse(msg string) {
// Format: R<seq>|<status>|<data> // Format: R<seq>|<status>|<data>
parts := strings.SplitN(msg, "|", 3) parts := strings.SplitN(msg, "|", 3)
@@ -381,39 +476,6 @@ func isSliceListResponse(data string) bool {
return true return true
} }
func (c *Client) handleStatusMessage(msg string) {
parts := strings.SplitN(msg, "|", 2)
if len(parts) < 2 {
return
}
handle := parts[0][1:]
data := parts[1]
statusMap := make(map[string]string)
pairs := strings.Fields(data)
for _, pair := range pairs {
if kv := strings.SplitN(pair, "=", 2); len(kv) == 2 {
statusMap[kv[0]] = kv[1]
}
}
switch {
case strings.Contains(msg, "interlock"):
c.handleInterlockStatus(handle, statusMap)
case strings.Contains(msg, "slice"):
c.handleSliceStatus(handle, statusMap)
case strings.Contains(msg, "radio"):
c.handleRadioStatus(handle, statusMap)
default:
log.Printf("FlexRadio: Unknown status (handle=%s): %s", handle, msg)
}
}
func (c *Client) handleInterlockStatus(handle string, statusMap map[string]string) { func (c *Client) handleInterlockStatus(handle string, statusMap map[string]string) {
c.statusMu.Lock() c.statusMu.Lock()
defer c.statusMu.Unlock() defer c.statusMu.Unlock()
@@ -424,45 +486,57 @@ func (c *Client) handleInterlockStatus(handle string, statusMap map[string]strin
} }
} }
func (c *Client) handleSliceStatus(handle string, statusMap map[string]string) { func (c *Client) handleRadioStatus(handle string, statusMap map[string]string) {
c.statusMu.Lock() c.statusMu.Lock()
defer c.statusMu.Unlock() defer c.statusMu.Unlock()
// Quand on reçoit un message de slice, on a au moins une slice active if c.lastStatus == nil {
c.lastStatus.ActiveSlices = 1 return
if rfFreq, ok := statusMap["RF_frequency"]; ok {
if freq, err := strconv.ParseFloat(rfFreq, 64); err == nil && freq > 0 {
c.lastStatus.Frequency = freq
c.lastStatus.RadioInfo = fmt.Sprintf("Active on %.3f MHz", freq)
if c.onFrequencyChange != nil {
go c.onFrequencyChange(freq)
} }
} else if freq == 0 {
// Fréquence 0 dans le message de slice = slice inactive // Mettre à jour les informations radio
c.lastStatus.Frequency = 0 c.lastStatus.RadioOn = true
c.lastStatus.RadioInfo = "Slice inactive" c.lastStatus.Connected = true
// Mettre à jour le nombre de slices
if slices, ok := statusMap["slices"]; ok {
if num, err := strconv.Atoi(slices); err == nil {
c.lastStatus.NumSlices = num
} }
} }
if mode, ok := statusMap["mode"]; ok { // Mettre à jour le callsign
c.lastStatus.Mode = mode if callsign, ok := statusMap["callsign"]; ok {
c.lastStatus.Callsign = callsign
} }
if tx, ok := statusMap["tx"]; ok { // Mettre à jour les autres infos
c.lastStatus.Tx = (tx == "1") if nickname, ok := statusMap["nickname"]; ok {
c.lastStatus.RadioInfo = fmt.Sprintf("Radio: %s", nickname)
} }
} }
func (c *Client) handleRadioStatus(handle string, statusMap map[string]string) { func (c *Client) handleFrequencyUpdate(handle string, freqStr string, statusMap map[string]string) {
if slices, ok := statusMap["slices"]; ok {
if num, err := strconv.Atoi(slices); err == nil {
c.statusMu.Lock() c.statusMu.Lock()
c.lastStatus.NumSlices = num defer c.statusMu.Unlock()
c.statusMu.Unlock()
if c.lastStatus == nil {
return
}
// Parser la fréquence
if freq, err := strconv.ParseFloat(freqStr, 64); err == nil && freq > 0 {
oldFreq := c.lastStatus.Frequency
c.lastStatus.Frequency = freq
c.lastStatus.RadioInfo = fmt.Sprintf("Active on %.3f MHz", freq)
// Déclencher le callback si la fréquence a changé
if oldFreq != freq && c.onFrequencyChange != nil {
go c.onFrequencyChange(freq)
} }
} }
log.Printf("FlexRadio: Frequency update: %s MHz", freqStr)
} }
func (c *Client) parseInfoResponse(data string) { func (c *Client) parseInfoResponse(data string) {
@@ -506,11 +580,18 @@ func (c *Client) parseInfoResponse(data string) {
c.radioInfoMu.Unlock() c.radioInfoMu.Unlock()
// Mettre à jour le statut
c.updateRadioStatus(true, "Radio is on") c.updateRadioStatus(true, "Radio is on")
go func() { go func() {
time.Sleep(300 * time.Millisecond) time.Sleep(300 * time.Millisecond)
c.SendSliceList() c.SendSliceList()
// S'abonner aux mises à jour
time.Sleep(200 * time.Millisecond)
c.sendCommand("sub slice all")
time.Sleep(100 * time.Millisecond)
c.sendCommand("sub interlock 0")
}() }()
} }
@@ -555,7 +636,10 @@ func (c *Client) updateRadioStatus(isOn bool, info string) {
c.statusMu.Lock() c.statusMu.Lock()
defer c.statusMu.Unlock() defer c.statusMu.Unlock()
if c.lastStatus != nil { if c.lastStatus == nil {
return
}
c.lastStatus.RadioOn = isOn c.lastStatus.RadioOn = isOn
c.lastStatus.RadioInfo = info c.lastStatus.RadioInfo = info
@@ -579,7 +663,6 @@ func (c *Client) updateRadioStatus(isOn bool, info string) {
if isOn && c.lastStatus.Frequency == 0 && c.lastStatus.ActiveSlices == 0 { if isOn && c.lastStatus.Frequency == 0 && c.lastStatus.ActiveSlices == 0 {
c.lastStatus.RadioInfo = "Radio is on without any active slice" c.lastStatus.RadioInfo = "Radio is on without any active slice"
} }
}
} }
func (c *Client) reconnectionMonitor() { func (c *Client) reconnectionMonitor() {
@@ -716,10 +799,12 @@ func (c *Client) GetStatus() (*Status, error) {
return &Status{ return &Status{
Connected: false, Connected: false,
RadioOn: false, RadioOn: false,
Tx: false,
RadioInfo: "Not initialized", RadioInfo: "Not initialized",
}, nil }, nil
} }
// Créer une copie
status := *c.lastStatus status := *c.lastStatus
return &status, nil return &status, nil
} }

View File

@@ -3,16 +3,16 @@ package flexradio
// Status represents the FlexRadio status // Status represents the FlexRadio status
type Status struct { type Status struct {
Connected bool `json:"connected"` Connected bool `json:"connected"`
Frequency float64 `json:"frequency"` RadioOn bool `json:"radio_on"`
RadioInfo string `json:"radio_info"`
Frequency float64 `json:"frequency"` // Primary frequency in MHz
Mode string `json:"mode"` Mode string `json:"mode"`
Tx bool `json:"tx"` Tx bool `json:"tx"` // Actually transmitting
RadioOn bool `json:"radio_on"` // Radio is powered on and responding ActiveSlices int `json:"active_slices"`
RadioInfo string `json:"radio_info"` // Additional info about radio state NumSlices int `json:"num_slices"`
Callsign string `json:"callsign"` // From info command Callsign string `json:"callsign"`
Model string `json:"model"` // From info command Model string `json:"model"`
SoftwareVer string `json:"software_ver"` // From info command SoftwareVer string `json:"software_ver"`
NumSlices int `json:"num_slices"` // From info command
ActiveSlices int `json:"active_slices"` // Count of active slices
} }
// InterlockState represents possible interlock states // InterlockState represents possible interlock states