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

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