fix(icom): say when a radio has no scope stream instead of drawing nothing
The CI-V trace settles it: the IC-7851 answers 27 10 01 with FB and 27 14 00 with its mode, but refuses 27 11 in BOTH shapes — it controls its scope over CI-V and never streams it. Its last firmware is from 2016, older than the waveform stream itself; Hamlib's caps claim otherwise, the radio is the authority. Rejected both ways is a permanent answer, so it is remembered and reported: the panadapter says the radio does not send its scope, which is the one thing a black rectangle could not say.
This commit is contained in:
+2
-2
@@ -5,12 +5,12 @@
|
||||
"en": [
|
||||
"Each radio carries its own MY_RIG (Settings → CAT), written on every QSO made with it — ahead of the per-band station in Operating conditions, which says what you planned to use rather than which radio is keying. Left empty, nothing changes.",
|
||||
"Worked-before matrix: a dot in the corner of a cell now means the callsign you are working has already been worked on that slot, whatever colour the entity status gave the cell. Its two colours (worked / confirmed with this callsign) are in Appearance with the other matrix colours.",
|
||||
"Icom spectrum scope: the IC-7851 (and any radio that wants the main/sub selector on its scope commands) now works over CI-V — the command shape is worked out from the radio’s own answer instead of a list of models."
|
||||
"Icom spectrum scope: the command shape (with or without the main/sub selector) is now worked out from the radio’s own answers instead of a list of models. A radio that has scope control but no waveform stream over CI-V — the IC-7851 — says so in the panel rather than showing a black rectangle."
|
||||
],
|
||||
"fr": [
|
||||
"Chaque radio porte son propre MY_RIG (Réglages → CAT), inscrit sur chaque QSO fait avec elle — avant la station par bande des Conditions de trafic, qui dit ce qui était prévu et non quelle radio émet. Laissé vide, rien ne change.",
|
||||
"Matrice des contacts : un point dans le coin d'une case indique que l'indicatif en cours a déjà été contacté sur ce créneau, quelle que soit la couleur donnée par le statut de l'entité. Ses deux couleurs (contacté / confirmé avec cet indicatif) se règlent dans Apparence avec les autres couleurs de la matrice.",
|
||||
"Scope Icom : l'IC-7851 (et toute radio qui attend le sélecteur main/sub sur les commandes de scope) fonctionne maintenant en CI-V — la forme de la commande est déduite de la réponse de la radio au lieu d'une liste de modèles."
|
||||
"Scope Icom : la forme des commandes (avec ou sans le sélecteur main/sub) est déduite des réponses de la radio au lieu d'une liste de modèles. Une radio qui pilote son scope mais ne l'envoie pas en CI-V — l'IC-7851 — l'indique dans le panneau au lieu d'afficher un rectangle noir."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -292,6 +292,9 @@ function ScopePanadapter() {
|
||||
const wfRef = useRef<HTMLCanvasElement>(null); // waterfall
|
||||
const peakRef = useRef(160); // running amplitude ceiling for auto-scale
|
||||
const holdRef = useRef<number[]>([]); // per-bin peak-hold line
|
||||
// Some radios control their scope over CI-V but never stream it (IC-7851).
|
||||
// Saying so beats a black rectangle, which reads as a bug in OpsLog.
|
||||
const [unsupported, setUnsupported] = useState(false);
|
||||
const spanRef = useRef({ low: 0, high: 0 }); // latest sweep edges, for click-to-tune
|
||||
const vfoRef = useRef(0); // latest VFO frequency, for wheel-tune
|
||||
const centerRef = useRef(0); // scope centre we last set (for pan ◀/▶)
|
||||
@@ -333,6 +336,7 @@ function ScopePanadapter() {
|
||||
if (!alive) return;
|
||||
try {
|
||||
const sw = await IcomScopeData();
|
||||
if (sw?.unsupported) setUnsupported(true);
|
||||
if (sw && sw.seq !== lastSeq && sw.amp && sw.amp.length) {
|
||||
lastSeq = sw.seq;
|
||||
setFixed(sw.fixed);
|
||||
@@ -543,7 +547,10 @@ function ScopePanadapter() {
|
||||
<Chip label={on ? 'ON' : 'OFF'} on={on} onClick={toggle} />
|
||||
</div>
|
||||
</div>
|
||||
{on && (
|
||||
{on && unsupported && (
|
||||
<div className="px-3 py-2 text-xs text-muted-foreground">{t('icmp.scopeNoStream')}</div>
|
||||
)}
|
||||
{on && !unsupported && (
|
||||
<div className="p-3">
|
||||
<div className="rounded-xl overflow-hidden ring-1 ring-info/20 shadow-lg shadow-sky-500/5 bg-[#05070e]">
|
||||
<canvas ref={canvasRef} onDoubleClick={onDblClick}
|
||||
|
||||
@@ -127,6 +127,7 @@ const en: Dict = {
|
||||
'mx.tipCallConf': 'This callsign confirmed', 'mx.tipCallWork': 'This callsign worked (not confirmed)',
|
||||
'mx.tipDxConf': 'Entity confirmed (other callsign)', 'mx.tipDxWork': 'Entity worked (other callsign)',
|
||||
'mx.tipNone': 'Never worked', 'mx.tipClick': 'click to list the QSOs',
|
||||
'icmp.scopeNoStream': 'This radio does not send its scope over CI-V — its own screen still works.',
|
||||
'mx.markWork': 'This callsign worked', 'mx.markConf': 'This callsign confirmed',
|
||||
'mx.tipThisCall': 'already worked with this callsign',
|
||||
'mx.tipThisCallConf': 'already confirmed with this callsign',
|
||||
@@ -616,6 +617,7 @@ const fr: Dict = {
|
||||
'mx.tipCallConf': 'Cet indicatif est confirmé', 'mx.tipCallWork': 'Cet indicatif est contacté (non confirmé)',
|
||||
'mx.tipDxConf': 'Entité confirmée (autre indicatif)', 'mx.tipDxWork': 'Entité contactée (autre indicatif)',
|
||||
'mx.tipNone': 'Jamais contacté', 'mx.tipClick': 'cliquer pour lister les QSO',
|
||||
'icmp.scopeNoStream': "Cette radio n'envoie pas son scope en CI-V — son propre écran fonctionne toujours.",
|
||||
'mx.markWork': 'Cet indicatif contacté', 'mx.markConf': 'Cet indicatif confirmé',
|
||||
'mx.tipThisCall': 'déjà contacté avec cet indicatif',
|
||||
'mx.tipThisCallConf': 'déjà confirmé avec cet indicatif',
|
||||
|
||||
@@ -1196,6 +1196,7 @@ export namespace cat {
|
||||
low_hz: number;
|
||||
high_hz: number;
|
||||
fixed: boolean;
|
||||
unsupported: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ScopeSweep(source);
|
||||
@@ -1208,6 +1209,7 @@ export namespace cat {
|
||||
this.low_hz = source["low_hz"];
|
||||
this.high_hz = source["high_hz"];
|
||||
this.fixed = source["fixed"];
|
||||
this.unsupported = source["unsupported"];
|
||||
}
|
||||
}
|
||||
export class TCIPanelState {
|
||||
|
||||
@@ -674,6 +674,12 @@ type ScopeSweep struct {
|
||||
LowHz int64 `json:"low_hz"` // left edge frequency (0 when unknown)
|
||||
HighHz int64 `json:"high_hz"` // right edge frequency (0 when unknown)
|
||||
Fixed bool `json:"fixed"` // true = fixed-span mode, false = center-on-VFO
|
||||
// Unsupported: this radio refuses the waveform-output command, so there will
|
||||
// never be a sweep. The IC-7851 does — its last firmware is from 2016, older
|
||||
// than the CI-V waveform stream — while still answering the scope's other
|
||||
// commands. Reported so the panadapter can say so instead of showing a black
|
||||
// rectangle that looks like a bug in OpsLog.
|
||||
Unsupported bool `json:"unsupported"`
|
||||
}
|
||||
|
||||
// IcomState returns the current Icom DSP state, or (zero, false) when the active
|
||||
|
||||
@@ -94,6 +94,9 @@ type IcomSerial struct {
|
||||
// reassembled sweep; scopeMu guards it (written by the scope goroutine, read
|
||||
// via ScopeData from the binding goroutine).
|
||||
dualScope bool
|
||||
// Set when the rig rejects the waveform-output command in both shapes: it has
|
||||
// no stream to give, and asking again on every enable is noise.
|
||||
scopeUnsupported bool
|
||||
scopeMu sync.Mutex
|
||||
scopeAmp []byte
|
||||
scopeLow int64 // spectrum left-edge frequency (from the sweep's header frame)
|
||||
@@ -1058,7 +1061,17 @@ func (b *IcomSerial) SetScope(on bool) error {
|
||||
// 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.execScope("data output", civ.SubScopeOn, boolByte(on)); err != nil {
|
||||
applog.Printf("icom scope: output on=%v ack: %v — turn the CI-V trace on (Settings → CAT) and send the log", on, err)
|
||||
applog.Printf("icom scope: output on=%v ack: %v", on, err)
|
||||
// Rejected in both shapes = the command does not exist on this rig, which
|
||||
// is a permanent answer and not a bad guess on our part. Remember it: the
|
||||
// panel can then say so, and we stop asking a radio that has already
|
||||
// said no.
|
||||
if strings.Contains(err.Error(), "rejected") {
|
||||
applog.Printf("icom scope: %s does not stream its scope over CI-V — control commands only", b.model)
|
||||
b.scopeMu.Lock()
|
||||
b.scopeUnsupported = true
|
||||
b.scopeMu.Unlock()
|
||||
}
|
||||
}
|
||||
b.scopeMu.Lock()
|
||||
b.scopeOn = on
|
||||
@@ -1302,7 +1315,8 @@ func (b *IcomSerial) ScopeData() ScopeSweep {
|
||||
for i, v := range b.scopeAmp {
|
||||
amp[i] = int(v)
|
||||
}
|
||||
return ScopeSweep{Amp: amp, Seq: b.scopeSeq, LowHz: b.scopeLow, HighHz: b.scopeHigh, Fixed: b.scopeFixed}
|
||||
return ScopeSweep{Amp: amp, Seq: b.scopeSeq, LowHz: b.scopeLow, HighHz: b.scopeHigh, Fixed: b.scopeFixed,
|
||||
Unsupported: b.scopeUnsupported}
|
||||
}
|
||||
|
||||
// exec sends a set command and waits for the rig's OK (FB) / NG (FA) ack.
|
||||
|
||||
Reference in New Issue
Block a user