diag(icom net): name the last CI-V commands before the silence

Audio off, and the IC-7760 still answers at connect and then never again:
twenty-eight commands sent, not one reply, packets still arriving on the
stream. A count says how much went unanswered and never which command
went out last — which is the one thing that can identify a frame this rig
does not tolerate, on a radio nobody here has.

The transport now keeps the command headers of the last eight frames and
prints them with the silence report.
This commit is contained in:
2026-09-06 17:49:32 +02:00
parent f2168d339b
commit e8f9e68759
2 changed files with 40 additions and 2 deletions
+2 -2
View File
@@ -7,14 +7,14 @@
"ADIF export: a record is written on one line again. ADDRESS is a multi-line field by the standard and callbooks and other loggers fill it that way — “Kabul”, four blank lines, “Afghanistan” — and OpsLog wrote it out as it was, so a record ran down a dozen lines with the next apparently starting in the middle of the page. Line breaks inside a value are now joined with a comma, which is how an address reads on one line anyway. The files were always valid (ADIF counts bytes); they were unreadable.", "ADIF export: a record is written on one line again. ADDRESS is a multi-line field by the standard and callbooks and other loggers fill it that way — “Kabul”, four blank lines, “Afghanistan” — and OpsLog wrote it out as it was, so a record ran down a dozen lines with the next apparently starting in the middle of the page. Line breaks inside a value are now joined with a comma, which is how an address reads on one line anyway. The files were always valid (ADIF counts bytes); they were unreadable.",
"Icom over the network: a rig left in standby no longer sits in a dial-and-drop loop. The clock that bounds “the control link answers but no CI-V comes back” belongs to a session and was never cleared when a new one opened, so every fresh session started already past its grace — torn down at once, redialled twenty seconds later, and torn down again for as long as the radio was asleep. Silent since connect is now read as what it is: a rig in standby, with the session kept so it can be woken.", "Icom over the network: a rig left in standby no longer sits in a dial-and-drop loop. The clock that bounds “the control link answers but no CI-V comes back” belongs to a session and was never cleared when a new one opened, so every fresh session started already past its grace — torn down at once, redialled twenty seconds later, and torn down again for as long as the radio was asleep. Silent since connect is now read as what it is: a rig in standby, with the session kept so it can be woken.",
"The Icom console appears whenever the configured radio is an Icom, not only once the rig is talking — the console is where the power-ON button lives, so it used to be missing at the one moment it was needed. The consoles configured backend also follows a radio switched from the status bar, instead of waiting for a trip through Settings and a Save that changed nothing.", "The Icom console appears whenever the configured radio is an Icom, not only once the rig is talking — the console is where the power-ON button lives, so it used to be missing at the one moment it was needed. The consoles configured backend also follows a radio switched from the status bar, instead of waiting for a trip through Settings and a Save that changed nothing.",
"Icom over the network: when CI-V goes quiet while the experimental RX audio stream is still delivering, the log now says so and names the switch to try. The two share the rigs session, and the shape in the field is exactly that — hundreds of audio packets arriving, not one CI-V reply, the watchdog tearing the session down, and the whole thing starting again." "Icom over the network: when CI-V goes quiet while the experimental RX audio stream is still delivering, the log now says so and names the switch to try. The two share the rigs session, and the shape in the field is exactly that — hundreds of audio packets arriving, not one CI-V reply, the watchdog tearing the session down, and the whole thing starting again. The silence report also lists the last eight CI-V commands sent: a rig that answers at connect and then never again has usually been sent something it does not like, and a count of unanswered commands never said which one."
], ],
"fr": [ "fr": [
"Carte FTx : lindicatif, le locator et le report réapparaissent au survol. Le cercle invisible qui capte les clics est au-dessus du point, donc il capte aussi le survol — et l’étiquette n’était liée quau point du dessous, ce qui rendait la carte muette dès que les stations sont devenues cliquables.", "Carte FTx : lindicatif, le locator et le report réapparaissent au survol. Le cercle invisible qui capte les clics est au-dessus du point, donc il capte aussi le survol — et l’étiquette n’était liée quau point du dessous, ce qui rendait la carte muette dès que les stations sont devenues cliquables.",
"Export ADIF : un enregistrement tient de nouveau sur une ligne. ADDRESS est un champ multiligne selon la norme, et les callbooks comme les autres logiciels le remplissent ainsi — « Kabul », quatre lignes vides, « Afghanistan » — quOpsLog recopiait tel quel : un enregistrement s’étalait sur une douzaine de lignes, le suivant semblant commencer au milieu de la page. Les retours à la ligne dans une valeur sont désormais réunis par une virgule, ce qui est de toute façon la façon de lire une adresse sur une ligne. Les fichiers étaient valides (lADIF compte les octets) ; ils étaient illisibles.", "Export ADIF : un enregistrement tient de nouveau sur une ligne. ADDRESS est un champ multiligne selon la norme, et les callbooks comme les autres logiciels le remplissent ainsi — « Kabul », quatre lignes vides, « Afghanistan » — quOpsLog recopiait tel quel : un enregistrement s’étalait sur une douzaine de lignes, le suivant semblant commencer au milieu de la page. Les retours à la ligne dans une valeur sont désormais réunis par une virgule, ce qui est de toute façon la façon de lire une adresse sur une ligne. Les fichiers étaient valides (lADIF compte les octets) ; ils étaient illisibles.",
"Icom en réseau : un poste laissé en veille ne tourne plus en boucle connexion/déconnexion. Lhorloge qui borne « la liaison de contrôle répond mais aucun CI-V ne revient » appartient à une session et n’était jamais remise à zéro à louverture de la suivante : chaque nouvelle session démarrait déjà au-delà de son délai de grâce — coupée aussitôt, rappelée vingt secondes plus tard, recoupée, aussi longtemps que la radio dormait. « Silencieux depuis la connexion » se lit désormais pour ce que cest : un poste en veille, dont on garde la session pour pouvoir le réveiller.", "Icom en réseau : un poste laissé en veille ne tourne plus en boucle connexion/déconnexion. Lhorloge qui borne « la liaison de contrôle répond mais aucun CI-V ne revient » appartient à une session et n’était jamais remise à zéro à louverture de la suivante : chaque nouvelle session démarrait déjà au-delà de son délai de grâce — coupée aussitôt, rappelée vingt secondes plus tard, recoupée, aussi longtemps que la radio dormait. « Silencieux depuis la connexion » se lit désormais pour ce que cest : un poste en veille, dont on garde la session pour pouvoir le réveiller.",
"La console Icom saffiche dès que la radio configurée est un Icom, et pas seulement quand le poste parle — cest là que se trouve le bouton dallumage, il manquait donc au seul moment où il servait. Le backend configuré suit aussi un changement de radio fait depuis la barre d’état, au lieu dattendre un passage dans les réglages et un « Enregistrer » qui ne changeait rien.", "La console Icom saffiche dès que la radio configurée est un Icom, et pas seulement quand le poste parle — cest là que se trouve le bouton dallumage, il manquait donc au seul moment où il servait. Le backend configuré suit aussi un changement de radio fait depuis la barre d’état, au lieu dattendre un passage dans les réglages et un « Enregistrer » qui ne changeait rien.",
"Icom en réseau : quand le CI-V devient muet alors que le flux audio expérimental continue darriver, le journal le dit et nomme loption à essayer. Les deux partagent la session du poste, et cest exactement la forme observée en vrai — des centaines de paquets audio, pas une réponse CI-V, le chien de garde qui coupe la session, et tout qui recommence." "Icom en réseau : quand le CI-V devient muet alors que le flux audio expérimental continue darriver, le journal le dit et nomme loption à essayer. Les deux partagent la session du poste, et cest exactement la forme observée en vrai — des centaines de paquets audio, pas une réponse CI-V, le chien de garde qui coupe la session, et tout qui recommence. Le rapport de silence liste aussi les huit dernières commandes CI-V envoyées : un poste qui répond à la connexion puis plus jamais sest en général vu envoyer quelque chose quil naime pas, et un compteur de commandes sans réponse na jamais dit laquelle."
] ]
}, },
{ {
+38
View File
@@ -132,6 +132,13 @@ type icomNet struct {
// loop, not the rig — and RS-BA1 showing no such dropouts points that way. // loop, not the rig — and RS-BA1 showing no such dropouts points that way.
txCiv atomic.Uint32 txCiv atomic.Uint32
txAtData atomic.Uint32 txAtData atomic.Uint32
// WHAT was asked, not just how much. A rig that answers at connect and then
// never again has usually been sent something it does not like, and counting
// the unanswered commands says nothing about which one that was. The last few
// command headers are kept so the silence report can name them — the only way
// to find a poison command on a radio nobody here has.
cmdMu sync.Mutex
lastCmd []string
rx chan []byte // CI-V byte chunks from civPump → Read (control replies) rx chan []byte // CI-V byte chunks from civPump → Read (control replies)
scopeRx chan []byte // scope (0x27) frames, kept off rx so the panadapter scopeRx chan []byte // scope (0x27) frames, kept off rx so the panadapter
@@ -268,6 +275,7 @@ func (n *icomNet) Write(p []byte) (int, error) {
n.vCivSeq++ n.vCivSeq++
n.seqMu.Unlock() n.seqMu.Unlock()
n.txCiv.Add(1) n.txCiv.Add(1)
n.noteCmd(p)
pkt := icnCivData(seq, n.vID, n.vRemote, civSeq, p) pkt := icnCivData(seq, n.vID, n.vRemote, civSeq, p)
n.sentMu.Lock() n.sentMu.Lock()
n.sentBuf[seq] = pkt n.sentBuf[seq] = pkt
@@ -280,6 +288,35 @@ func (n *icomNet) Write(p []byte) (int, error) {
return len(p), nil return len(p), nil
} }
// noteCmd remembers the command bytes of a CI-V frame — everything after the
// preamble and the two addresses, up to four bytes, which is command,
// sub-command and the first of the data.
func (n *icomNet) noteCmd(p []byte) {
if len(p) < 5 {
return
}
body := p[4:]
if len(body) > 4 {
body = body[:4]
}
n.cmdMu.Lock()
n.lastCmd = append(n.lastCmd, fmt.Sprintf("% X", body))
if len(n.lastCmd) > 8 {
n.lastCmd = n.lastCmd[len(n.lastCmd)-8:]
}
n.cmdMu.Unlock()
}
// recentCmds is what noteCmd collected, oldest first.
func (n *icomNet) recentCmds() string {
n.cmdMu.Lock()
defer n.cmdMu.Unlock()
if len(n.lastCmd) == 0 {
return "none"
}
return strings.Join(n.lastCmd, " | ")
}
// icnTrace toggles verbose per-frame CI-V request/reply logging for diagnosing // icnTrace toggles verbose per-frame CI-V request/reply logging for diagnosing
// the network transport. Off by default (the connect-step logs stay); flip to // the network transport. Off by default (the connect-step logs stay); flip to
// true to trace every TX/RX again. // true to trace every TX/RX again.
@@ -496,6 +533,7 @@ func (n *icomNet) civPump() {
} }
debugLog.Printf("icom net: no CI-V DATA for 10 s (transport last heard %s ago; last scope frame %s ago; last socket error: %v; missing-seq backlog: %d; CI-V commands SENT since the last answer: %d)", debugLog.Printf("icom net: no CI-V DATA for 10 s (transport last heard %s ago; last scope frame %s ago; last socket error: %v; missing-seq backlog: %d; CI-V commands SENT since the last answer: %d)",
time.Since(lastPkt).Round(time.Second), scopeAge, lastErr, len(n.rxMissing), n.txCiv.Load()-n.txAtData.Load()) time.Since(lastPkt).Round(time.Second), scopeAge, lastErr, len(n.rxMissing), n.txCiv.Load()-n.txAtData.Load())
debugLog.Printf("icom net: the last CI-V commands sent, oldest first: %s", n.recentCmds())
// THE AUDIO STREAM IS THE FIRST SUSPECT, AND ONLY THE LOG CAN SAY SO. // THE AUDIO STREAM IS THE FIRST SUSPECT, AND ONLY THE LOG CAN SAY SO.
// //
// The RX audio stream is experimental and shares the rig's session with // The RX audio stream is experimental and shares the rig's session with