fix(elecraft): calibrate the S-meter, and stop the SWR spike lingering
The S-unit mapping was provisional, waiting for a real radio. One arrived: side by side with a K3's own display, raw 5 reads S7 and raw 9 reads S9+20, so S9 sits near raw 6.5 — not 9 — and each raw step above it is worth ~8 dB. The old scale showed everything two S-units low. The SWR meter borrowed the power meter's needle inertia — hold the peak, then close a quarter of the gap per poll. The K3 throws a brief SWR spike as an FT8 frame ends, and that decay turned one bad sample into twelve seconds of red on the next transmission. SWR now holds the peak for the same 1.5 s and then snaps back to the live reading: it is a warning light, not a needle.
This commit is contained in:
@@ -1,4 +1,16 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "0.27.1",
|
||||||
|
"date": "",
|
||||||
|
"en": [
|
||||||
|
"Elecraft console: the S-meter is calibrated against a real K3 — S9 and the +dB readings now match the radio’s own display (they read about two S-units low).",
|
||||||
|
"Elecraft console: an SWR spike (the K3’s own blip at the end of an FT8 frame) no longer fades over twelve seconds — the peak shows briefly, then the meter returns straight to the live reading."
|
||||||
|
],
|
||||||
|
"fr": [
|
||||||
|
"Console Elecraft : le S-mètre est calibré sur un vrai K3 — S9 et les +dB correspondent désormais à l’affichage de la radio (il lisait environ deux points S trop bas).",
|
||||||
|
"Console Elecraft : un pic de ROS (le sursaut du K3 en fin de trame FT8) ne s’estompe plus pendant douze secondes — le pic s’affiche brièvement, puis le ros-mètre revient directement à la valeur réelle."
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"date": "",
|
"date": "",
|
||||||
|
|||||||
@@ -42,18 +42,22 @@ const ZERO: KenwoodState = {
|
|||||||
// nobody notices are missing.
|
// nobody notices are missing.
|
||||||
const FILTERS = [200, 400, 700, 1000, 1800, 2400, 2800, 4000];
|
const FILTERS = [200, 400, 700, 1000, 1800, 2400, 2800, 4000];
|
||||||
|
|
||||||
// Raw S-meter → S units. The K3 answers 0-21 across S0…S9+60; S9 is taken at
|
// Raw S-meter → S units, CALIBRATED against a real K3 beside its own display
|
||||||
// raw 9 and each step above it as 6 dB. PROVISIONAL, like the rest of the
|
// (2026-08): raw 5 reads S7 on the radio, raw 9 reads S9+20. So S9 sits near
|
||||||
// scaling: the raw value is on screen and in the log, so a real radio settles
|
// raw 6.5 — not 9, which showed everything two S-units low — and each raw step
|
||||||
// it rather than this comment.
|
// above it is worth ~8 dB, shown in the 10 dB steps the K3's own meter uses.
|
||||||
const S9_RAW = 9;
|
// A Kenwood answers 0-30 on the same command and keeps the simple 1-per-raw
|
||||||
const DB_PER_RAW = 6;
|
// scale until someone calibrates one against a real radio too.
|
||||||
function sParts(rawV: number): { s: number; over: number; label: string } {
|
const K3_S9_RAW = 6.5;
|
||||||
if (rawV >= S9_RAW) {
|
const K3_DB_PER_RAW = 8;
|
||||||
const over = Math.max(0, Math.round((rawV - S9_RAW) * DB_PER_RAW));
|
function sParts(rawV: number, elecraft: boolean): { s: number; over: number; label: string } {
|
||||||
|
const s9raw = elecraft ? K3_S9_RAW : 9;
|
||||||
|
if (rawV >= s9raw) {
|
||||||
|
let over = Math.max(0, Math.round((rawV - s9raw) * (elecraft ? K3_DB_PER_RAW : 6)));
|
||||||
|
if (elecraft) over = Math.round(over / 10) * 10;
|
||||||
return { s: 9, over, label: over > 0 ? `S9+${over}` : 'S9' };
|
return { s: 9, over, label: over > 0 ? `S9+${over}` : 'S9' };
|
||||||
}
|
}
|
||||||
const s = Math.max(0, Math.min(9, rawV));
|
const s = Math.max(0, Math.min(9, Math.round(rawV * (elecraft ? 9 / K3_S9_RAW : 1))));
|
||||||
return { s, over: 0, label: `S${s}` };
|
return { s, over: 0, label: `S${s}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,10 +215,10 @@ export function ElecraftPanel({ onReportRST }: { onReportRST?: (rst: string) =>
|
|||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||||
<MeterBar label="S-METER" value={view.transmitting ? 0 : view.s_meter} lo={0} hi={100}
|
<MeterBar label="S-METER" value={view.transmitting ? 0 : view.s_meter} lo={0} hi={100}
|
||||||
accent="#16a34a" segColor={sSegColor}
|
accent="#16a34a" segColor={sSegColor}
|
||||||
display={view.transmitting ? '—' : sParts(view.s_meter_raw).label}
|
display={view.transmitting ? '—' : sParts(view.s_meter_raw, view.elecraft).label}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (view.transmitting || !onReportRST) return;
|
if (view.transmitting || !onReportRST) return;
|
||||||
const sp = sParts(view.s_meter_raw);
|
const sp = sParts(view.s_meter_raw, view.elecraft);
|
||||||
onReportRST(sMeterRST(sp.s, sp.over, view.mode));
|
onReportRST(sMeterRST(sp.s, sp.over, view.mode));
|
||||||
}}
|
}}
|
||||||
title={t('k3.sMeterHint', { raw: String(view.s_meter_raw) })} />
|
title={t('k3.sMeterHint', { raw: String(view.s_meter_raw) })} />
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ func (k *Kenwood) readTXMeters() {
|
|||||||
if v, ok := k.askNum("SW;", "SW", 3); ok {
|
if v, ok := k.askNum("SW;", "SW", 3); ok {
|
||||||
k.panel.SWRRaw = v
|
k.panel.SWRRaw = v
|
||||||
if v > 0 {
|
if v > 0 {
|
||||||
k.panel.SWR = float64(k.swrPeak.update(v, now)) / 10
|
k.panel.SWR = float64(k.swrPeak.updateSnap(v, now)) / 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if k.metersLogged >= 20 {
|
if k.metersLogged >= 20 {
|
||||||
|
|||||||
@@ -905,3 +905,22 @@ func yaesuSplitCommand(cmd, vfo string, on bool) string {
|
|||||||
}
|
}
|
||||||
return cmd + "0;"
|
return cmd + "0;"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateSnap is update for a meter where lingering is misinformation: the peak
|
||||||
|
// stands for the hold, then the display returns to the live sample AT ONCE.
|
||||||
|
// Made for the K3's SWR — the radio throws a brief SWR spike as an FT8 frame
|
||||||
|
// ends, and the quarter-of-the-gap decay above turned that one bad sample into
|
||||||
|
// twelve seconds of alarming red on the next transmission. A needle's inertia
|
||||||
|
// suits a power meter; an SWR reading is a warning light, and a warning that
|
||||||
|
// fades slowly reads as a fault that is slowly getting better.
|
||||||
|
func (m *meterPeak) updateSnap(sample int, now time.Time) int {
|
||||||
|
if sample >= m.val {
|
||||||
|
m.val, m.at = sample, now
|
||||||
|
return m.val
|
||||||
|
}
|
||||||
|
if now.Sub(m.at) < meterHold {
|
||||||
|
return m.val
|
||||||
|
}
|
||||||
|
m.val, m.at = sample, now
|
||||||
|
return m.val
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user