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.gstatic.com" crossorigin>
<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>
<link rel="stylesheet" crossorigin href="/assets/index-Bl7hatTL.css">
<script type="module" crossorigin src="/assets/index-Drom3Zfz.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-pnuRxXpy.css">
</head>
<body>
<div id="app"></div>
</body>

View File

@@ -45,6 +45,10 @@ type Client struct {
onFrequencyChange func(freqMHz float64)
checkTransmitAllowed func() bool
// Track current slice frequency
currentFreq float64
currentFreqMu sync.RWMutex
}
func New(host string, port int) *Client {
@@ -57,9 +61,13 @@ func New(host string, port int) *Client {
radioInfo: make(map[string]string),
activeSlices: []int{},
lastStatus: &Status{
Connected: false,
RadioOn: false,
Connected: 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
log.Printf("FlexRadio RAW: %s", msg)
// Router selon le premier caractère
switch msg[0] {
case 'R': // Réponse à une commande
// Vérifier le type de message
if len(msg) < 2 {
return
}
// Messages commençant par R (réponses)
if msg[0] == 'R' {
c.handleCommandResponse(msg)
case 'S': // Message de statut
c.handleStatusMessage(msg)
case 'V': // Version/Handle
return
}
// 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)
case 'M': // Message général
case 'M':
log.Printf("FlexRadio: Message: %s", msg)
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) {
// Format: R<seq>|<status>|<data>
parts := strings.SplitN(msg, "|", 3)
@@ -381,39 +476,6 @@ func isSliceListResponse(data string) bool {
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) {
c.statusMu.Lock()
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()
defer c.statusMu.Unlock()
// Quand on reçoit un message de slice, on a au moins une slice active
c.lastStatus.ActiveSlices = 1
if c.lastStatus == nil {
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)
// Mettre à jour les informations radio
c.lastStatus.RadioOn = true
c.lastStatus.Connected = true
if c.onFrequencyChange != nil {
go c.onFrequencyChange(freq)
}
} else if freq == 0 {
// Fréquence 0 dans le message de slice = slice inactive
c.lastStatus.Frequency = 0
c.lastStatus.RadioInfo = "Slice inactive"
// 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 {
c.lastStatus.Mode = mode
// Mettre à jour le callsign
if callsign, ok := statusMap["callsign"]; ok {
c.lastStatus.Callsign = callsign
}
if tx, ok := statusMap["tx"]; ok {
c.lastStatus.Tx = (tx == "1")
// Mettre à jour les autres infos
if nickname, ok := statusMap["nickname"]; ok {
c.lastStatus.RadioInfo = fmt.Sprintf("Radio: %s", nickname)
}
}
func (c *Client) handleRadioStatus(handle string, statusMap map[string]string) {
if slices, ok := statusMap["slices"]; ok {
if num, err := strconv.Atoi(slices); err == nil {
c.statusMu.Lock()
c.lastStatus.NumSlices = num
c.statusMu.Unlock()
func (c *Client) handleFrequencyUpdate(handle string, freqStr string, statusMap map[string]string) {
c.statusMu.Lock()
defer 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) {
@@ -506,11 +580,18 @@ func (c *Client) parseInfoResponse(data string) {
c.radioInfoMu.Unlock()
// Mettre à jour le statut
c.updateRadioStatus(true, "Radio is on")
go func() {
time.Sleep(300 * time.Millisecond)
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,31 +636,33 @@ func (c *Client) updateRadioStatus(isOn bool, info string) {
c.statusMu.Lock()
defer c.statusMu.Unlock()
if c.lastStatus != nil {
c.lastStatus.RadioOn = isOn
c.lastStatus.RadioInfo = info
if c.lastStatus == nil {
return
}
c.radioInfoMu.RLock()
if callsign, ok := c.radioInfo["callsign"]; ok {
c.lastStatus.Callsign = callsign
}
if model, ok := c.radioInfo["model"]; ok {
c.lastStatus.Model = model
}
if softwareVer, ok := c.radioInfo["software_ver"]; ok {
c.lastStatus.SoftwareVer = softwareVer
}
if numSlicesStr, ok := c.radioInfo["num_slice"]; ok {
if numSlices, err := strconv.Atoi(numSlicesStr); err == nil {
c.lastStatus.NumSlices = numSlices
}
}
c.radioInfoMu.RUnlock()
c.lastStatus.RadioOn = isOn
c.lastStatus.RadioInfo = info
if isOn && c.lastStatus.Frequency == 0 && c.lastStatus.ActiveSlices == 0 {
c.lastStatus.RadioInfo = "Radio is on without any active slice"
c.radioInfoMu.RLock()
if callsign, ok := c.radioInfo["callsign"]; ok {
c.lastStatus.Callsign = callsign
}
if model, ok := c.radioInfo["model"]; ok {
c.lastStatus.Model = model
}
if softwareVer, ok := c.radioInfo["software_ver"]; ok {
c.lastStatus.SoftwareVer = softwareVer
}
if numSlicesStr, ok := c.radioInfo["num_slice"]; ok {
if numSlices, err := strconv.Atoi(numSlicesStr); err == nil {
c.lastStatus.NumSlices = numSlices
}
}
c.radioInfoMu.RUnlock()
if isOn && c.lastStatus.Frequency == 0 && c.lastStatus.ActiveSlices == 0 {
c.lastStatus.RadioInfo = "Radio is on without any active slice"
}
}
func (c *Client) reconnectionMonitor() {
@@ -716,10 +799,12 @@ func (c *Client) GetStatus() (*Status, error) {
return &Status{
Connected: false,
RadioOn: false,
Tx: false,
RadioInfo: "Not initialized",
}, nil
}
// Créer une copie
status := *c.lastStatus
return &status, nil
}

View File

@@ -3,16 +3,16 @@ package flexradio
// Status represents the FlexRadio status
type Status struct {
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"`
Tx bool `json:"tx"`
RadioOn bool `json:"radio_on"` // Radio is powered on and responding
RadioInfo string `json:"radio_info"` // Additional info about radio state
Callsign string `json:"callsign"` // From info command
Model string `json:"model"` // From info command
SoftwareVer string `json:"software_ver"` // From info command
NumSlices int `json:"num_slices"` // From info command
ActiveSlices int `json:"active_slices"` // Count of active slices
Tx bool `json:"tx"` // Actually transmitting
ActiveSlices int `json:"active_slices"`
NumSlices int `json:"num_slices"`
Callsign string `json:"callsign"`
Model string `json:"model"`
SoftwareVer string `json:"software_ver"`
}
// InterlockState represents possible interlock states