fix(icom): ask the radio which shape its scope commands take
The IC-7851 rejects "27 11 01" and wants "27 11 00 01" — the main/sub selector its two scopes need. The shape was picked from a list of CI-V addresses, so an address not on it got the wrong one and the scope stayed blank with a rejection in the log and nothing on screen. A rejection is an unambiguous answer, so a rejected 0x27 set is now re-sent in the other shape and the winner remembered. A TIMEOUT deliberately is not retried: several firmwares never ack a 0x27 set, and treating silence as a wrong guess would flip a rig that was already working.
This commit is contained in:
+39
-16
@@ -982,6 +982,40 @@ func (b *IcomSerial) assembleSweep(regions map[byte][]byte, total byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// execScope sends a 0x27 SET and, if the rig rejects it, sends it once more in
|
||||
// the other shape — with or without the leading main/sub selector byte — and
|
||||
// remembers which one this rig speaks.
|
||||
//
|
||||
// The shape used to be decided from the CI-V address, which meant every new
|
||||
// model was a blank scope until someone reported it: the IC-7851 (0x8E) rejects
|
||||
// "27 11 01" outright and wants "27 11 00 01", exactly as the IC-7610 does not.
|
||||
// A rejection is a cheap and unambiguous answer, so ask the rig instead of
|
||||
// keeping a list. Only the SET commands need this — the waveform parser already
|
||||
// detects the selector per frame.
|
||||
func (b *IcomSerial) execScope(what string, sub byte, args ...byte) error {
|
||||
try := func(sel bool) error {
|
||||
p := []byte{civ.CmdScope, sub}
|
||||
if sel {
|
||||
p = append(p, 0x00) // main scope
|
||||
}
|
||||
return b.exec(append(p, args...)...)
|
||||
}
|
||||
err := try(b.dualScope)
|
||||
// Only a REJECTION means "wrong shape". A timeout says nothing (several
|
||||
// firmwares simply don't ack a 0x27 set), and retrying it in the other shape
|
||||
// would flip a working rig onto the wrong one.
|
||||
if err == nil || !strings.Contains(err.Error(), "rejected") {
|
||||
return err
|
||||
}
|
||||
if err2 := try(!b.dualScope); err2 == nil {
|
||||
b.dualScope = !b.dualScope
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetScope enables or disables the spectrum scope. Two commands are needed and
|
||||
// RS-BA1 sends both: 0x27 0x10 turns the scope DISPLAY on (without it the rig
|
||||
// streams nothing — the case when we're remote and can't touch the front panel),
|
||||
@@ -1000,14 +1034,14 @@ func (b *IcomSerial) SetScope(on bool) error {
|
||||
// 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
|
||||
// 0x27 set; a timeout isn't fatal, so log and continue.
|
||||
if err := b.exec(civ.CmdScope, 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)
|
||||
}
|
||||
}
|
||||
// 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
|
||||
// left exactly as the operator had it.
|
||||
if err := b.exec(civ.CmdScope, civ.SubScopeOn, boolByte(on)); err != nil {
|
||||
if err := b.execScope("data output", civ.SubScopeOn, boolByte(on)); err != nil {
|
||||
applog.Printf("icom scope: output on=%v ack: %v", on, err)
|
||||
}
|
||||
b.scopeMu.Lock()
|
||||
@@ -1041,13 +1075,7 @@ func (b *IcomSerial) scopeReadCfg() {
|
||||
// makes the scope follow the VFO, so tuning pans the view left/right.
|
||||
func (b *IcomSerial) SetScopeMode(fixed bool) error {
|
||||
mode := boolByte(fixed) // 0 = center, 1 = fixed (verify on rig via the cfg log)
|
||||
var payload []byte
|
||||
if b.dualScope {
|
||||
payload = []byte{civ.CmdScope, civ.SubScopeMode, 0x00, mode}
|
||||
} else {
|
||||
payload = []byte{civ.CmdScope, civ.SubScopeMode, mode}
|
||||
}
|
||||
if err := b.exec(payload...); err != nil {
|
||||
if err := b.execScope("set mode", civ.SubScopeMode, mode); err != nil {
|
||||
applog.Printf("icom scope: set mode fixed=%v ack: %v", fixed, err)
|
||||
}
|
||||
b.scopeMu.Lock()
|
||||
@@ -1093,13 +1121,8 @@ func (b *IcomSerial) SetScopeEdges(low, high int64) error {
|
||||
if rangeID == 0 {
|
||||
return fmt.Errorf("icom scope: freq out of range")
|
||||
}
|
||||
if b.dualScope {
|
||||
_ = b.exec(civ.CmdScope, civ.SubScopeMode, 0x00, 0x01) // fixed mode (main)
|
||||
_ = b.exec(civ.CmdScope, civ.SubScopeEdge, 0x00, 0x01) // activate edge set 1
|
||||
} else {
|
||||
_ = b.exec(civ.CmdScope, civ.SubScopeMode, 0x01)
|
||||
_ = b.exec(civ.CmdScope, civ.SubScopeEdge, 0x01)
|
||||
}
|
||||
_ = b.execScope("fixed mode", civ.SubScopeMode, 0x01)
|
||||
_ = b.execScope("edge set 1", civ.SubScopeEdge, 0x01)
|
||||
payload := append([]byte{civ.CmdScope, civ.SubScopeFixEdge, rangeID, 0x01}, civ.FreqToBCD(low)...)
|
||||
payload = append(payload, civ.FreqToBCD(high)...)
|
||||
b.scopeMu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user