revert(icom): keep 0x27 0x11 as the scope data switch, log what the retry got

Hamlib documents the spectrum scope on the IC-785x, so a rejected 0x27 0x11 is
not proof the command is missing — and taking 0x27 0x10 for the data switch
would have blanked that operator's own scope screen when OpsLog disabled it.

Both shapes were refused within 96 ms, so neither was a timeout, and guessing
the next subcommand is how the last two rounds went. The retry now logs the
second attempt's own error and the failure points at the CI-V trace.
This commit is contained in:
2026-08-27 16:25:29 +02:00
parent 69d78ba8c0
commit d2cfad490a
+17 -34
View File
@@ -93,18 +93,15 @@ type IcomSerial struct {
// leading main/sub selector byte (IC-7610/9700). scopeAmp is the latest // leading main/sub selector byte (IC-7610/9700). scopeAmp is the latest
// reassembled sweep; scopeMu guards it (written by the scope goroutine, read // reassembled sweep; scopeMu guards it (written by the scope goroutine, read
// via ScopeData from the binding goroutine). // via ScopeData from the binding goroutine).
dualScope bool dualScope bool
// Which 0x27 subcommand switches the waveform stream on this rig: 0x11 where scopeMu sync.Mutex
// it exists, 0x10 on the ones that answer 0x11 with a rejection. scopeAmp []byte
scopeOutSub byte scopeLow int64 // spectrum left-edge frequency (from the sweep's header frame)
scopeMu sync.Mutex scopeHigh int64 // spectrum right-edge frequency
scopeAmp []byte scopeSeq int
scopeLow int64 // spectrum left-edge frequency (from the sweep's header frame) scopeOn bool
scopeHigh int64 // spectrum right-edge frequency scopeFixed bool // true = fixed-span mode (tracked optimistically)
scopeSeq int scopeSeen bool // logged the first sweep's structure once (on-rig verification)
scopeOn bool
scopeFixed bool // true = fixed-span mode (tracked optimistically)
scopeSeen bool // logged the first sweep's structure once (on-rig verification)
curFreq int64 // last frequency read (for sideband choice) curFreq int64 // last frequency read (for sideband choice)
curModeByte byte // last raw Icom mode byte (for filter re-send) curModeByte byte // last raw Icom mode byte (for filter re-send)
@@ -1023,7 +1020,10 @@ func (b *IcomSerial) execScope(what string, sub byte, args ...byte) error {
if err == nil || !strings.Contains(err.Error(), "rejected") { if err == nil || !strings.Contains(err.Error(), "rejected") {
return err return err
} }
if err2 := try(!b.dualScope); err2 == nil { err2 := try(!b.dualScope)
applog.Printf("icom scope: %s rejected in the %s form — the other form gave: %v",
what, map[bool]string{true: "27 xx 00 …", false: "27 xx …"}[b.dualScope], err2)
if err2 == nil {
b.dualScope = !b.dualScope b.dualScope = !b.dualScope
applog.Printf("icom scope: %s rejected — this rig wants the %s form (selector=%v)", applog.Printf("icom scope: %s rejected — this rig wants the %s form (selector=%v)",
what, map[bool]string{true: "27 xx 00 …", false: "27 xx …"}[b.dualScope], b.dualScope) what, map[bool]string{true: "27 xx 00 …", false: "27 xx …"}[b.dualScope], b.dualScope)
@@ -1050,32 +1050,15 @@ func (b *IcomSerial) SetScope(on bool) error {
// radio, and closing OpsLog (SetScope(false)) blanking a local IC-7300's // radio, and closing OpsLog (SetScope(false)) blanking a local IC-7300's
// screen is exactly the regression this avoids. Some firmwares don't ack a // screen is exactly the regression this avoids. Some firmwares don't ack a
// 0x27 set; a timeout isn't fatal, so log and continue. // 0x27 set; a timeout isn't fatal, so log and continue.
if b.scopeOutSub != civ.SubScopeOnOff { if err := b.execScope("display on", civ.SubScopeOnOff, 0x01); err != nil {
if err := b.execScope("display on", civ.SubScopeOnOff, 0x01); err != nil { applog.Printf("icom scope: display on ack: %v", err)
applog.Printf("icom scope: display on ack: %v", err)
}
} }
} }
// Waveform data OUTPUT over CI-V: enabled with the scope, and — crucially — // Waveform data OUTPUT over CI-V: enabled with the scope, and — crucially —
// the ONLY thing we switch off on disable, so the radio's own scope display is // the ONLY thing we switch off on disable, so the radio's own scope display is
// left exactly as the operator had it. // left exactly as the operator had it.
// if err := b.execScope("data output", civ.SubScopeOn, boolByte(on)); err != nil {
// scopeOutSub is 0x11 on the radios that have it. The IC-7851 does not: it applog.Printf("icom scope: output on=%v ack: %v — turn the CI-V trace on (Settings → CAT) and send the log", on, err)
// rejects 0x11 in both shapes, because on that radio 0x27 0x10 is itself the
// waveform-output switch rather than a display switch. Fall back to it, and
// from then on switch it off on disable too — on THAT radio doing so stops a
// data stream, not the operator's own scope screen.
if b.scopeOutSub == 0 {
b.scopeOutSub = civ.SubScopeOn
}
err := b.execScope("data output", b.scopeOutSub, boolByte(on))
if err != nil && b.scopeOutSub == civ.SubScopeOn && strings.Contains(err.Error(), "rejected") {
applog.Printf("icom scope: no 0x27 0x11 on this rig — 0x27 0x10 is its data-output switch")
b.scopeOutSub = civ.SubScopeOnOff
err = b.execScope("data output", b.scopeOutSub, boolByte(on))
}
if err != nil {
applog.Printf("icom scope: output on=%v ack: %v", on, err)
} }
b.scopeMu.Lock() b.scopeMu.Lock()
b.scopeOn = on b.scopeOn = on