fix(elecraft): MOX would not unkey, and the TX meters never moved
Both are the same bug. ReadState returns early while transmitting — the rig answers '?;' to IF; then, and treating that as a fault used to drop the link — but that early return also skipped the panel update and the meter read. So the panel believed the radio was still receiving. Pressing MOX again therefore sent ANOTHER transmit command instead of RX, and the K3 stayed keyed, exactly as reported. And the power and SWR bars were only ever read at the one moment they mean nothing. Reading meters while transmitting needs one more guard: a '?;' then says when the question was asked, not what the radio supports, so the unsupported-command memory is suspended for that read. Without it, one badly timed refusal would silence a meter for the rest of the session. Adds RIT/XIT offset control (±10/±100 Hz, offset shown), a 4.0 kHz filter button for FT8, and logs the K3's icon word when it changes: there is no command for 'is the ATU in line', the reference says the switch functions show up as icon changes, so an operator toggling the ATU while watching the log will name the bit and the button can then light up honestly.
This commit is contained in:
@@ -80,6 +80,11 @@ func (a *App) SetKenwoodXIT(on bool) error {
|
|||||||
return a.kenwoodPanelDo(func(k cat.KenwoodPanelController) error { return k.SetKenwoodXIT(on) })
|
return a.kenwoodPanelDo(func(k cat.KenwoodPanelController) error { return k.SetKenwoodXIT(on) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NudgeKenwoodRIT moves the RIT/XIT offset by delta Hz.
|
||||||
|
func (a *App) NudgeKenwoodRIT(delta int) error {
|
||||||
|
return a.kenwoodPanelDo(func(k cat.KenwoodPanelController) error { return k.NudgeKenwoodRIT(delta) })
|
||||||
|
}
|
||||||
|
|
||||||
// ClearKenwoodRIT zeroes the RIT and XIT offsets together, which is what the
|
// ClearKenwoodRIT zeroes the RIT and XIT offsets together, which is what the
|
||||||
// radio's own RC does and what an operator means by "clear it".
|
// radio's own RC does and what an operator means by "clear it".
|
||||||
func (a *App) ClearKenwoodRIT() error {
|
func (a *App) ClearKenwoodRIT() error {
|
||||||
|
|||||||
+10
-2
@@ -2,8 +2,16 @@
|
|||||||
{
|
{
|
||||||
"version": "0.26.13",
|
"version": "0.26.13",
|
||||||
"date": "",
|
"date": "",
|
||||||
"en": [],
|
"en": [
|
||||||
"fr": []
|
"Elecraft console: MOX unkeys again, and the power and SWR bars move while transmitting. The poll stops while the carrier is up — the rig refuses most questions then — and the panel was left believing the radio was still receiving, so pressing MOX a second time sent another transmit command instead of stopping.",
|
||||||
|
"Elecraft console: RIT and XIT can be moved — ±10 and ±100 Hz buttons with the offset shown beside them. A lit RIT button says the feature is on and nothing about where it has put the receiver.",
|
||||||
|
"Elecraft console: a 4.0 kHz filter button, the K3 maximum and the one FT8 wants."
|
||||||
|
],
|
||||||
|
"fr": [
|
||||||
|
"Console Elecraft : MOX repasse bien en réception, et les barres de puissance et de ROS bougent pendant l'émission. L'interrogation s'arrête quand la porteuse est levée — la radio refuse alors la plupart des questions — et le panneau croyait donc la radio toujours en réception : un second appui sur MOX envoyait une nouvelle commande d'émission au lieu d'arrêter.",
|
||||||
|
"Console Elecraft : le RIT et le XIT se règlent — boutons ±10 et ±100 Hz avec le décalage affiché à côté. Un bouton RIT allumé dit que la fonction est active et rien sur l'endroit où elle a mis le récepteur.",
|
||||||
|
"Console Elecraft : un bouton de filtre à 4,0 kHz, le maximum du K3 et celui qu'il faut pour le FT8."
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "0.26.12",
|
"version": "0.26.12",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
GetKenwoodState, RefreshKenwood, SetKenwoodPower, SetKenwoodAFGain, SetKenwoodTX, TuneKenwoodATU,
|
GetKenwoodState, RefreshKenwood, SetKenwoodPower, SetKenwoodAFGain, SetKenwoodTX, TuneKenwoodATU,
|
||||||
SetKenwoodRFGain, SetKenwoodMicGain, SetKenwoodSquelch, SetKenwoodPreamp, SetKenwoodAtt,
|
SetKenwoodRFGain, SetKenwoodMicGain, SetKenwoodSquelch, SetKenwoodPreamp, SetKenwoodAtt,
|
||||||
SetKenwoodNB, SetKenwoodNR, SetKenwoodAGC, SetKenwoodFilter, SetKenwoodAntenna,
|
SetKenwoodNB, SetKenwoodNR, SetKenwoodAGC, SetKenwoodFilter, SetKenwoodAntenna,
|
||||||
SetKenwoodRIT, SetKenwoodXIT, ClearKenwoodRIT, SetKenwoodKeySpeed, ToggleKenwoodATU,
|
SetKenwoodRIT, SetKenwoodXIT, ClearKenwoodRIT, SetKenwoodKeySpeed, ToggleKenwoodATU, NudgeKenwoodRIT,
|
||||||
GetCATState,
|
GetCATState,
|
||||||
} from '../../wailsjs/go/main/App';
|
} from '../../wailsjs/go/main/App';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@@ -20,7 +20,7 @@ type KenwoodState = {
|
|||||||
power_meter: number; swr: number; swr_raw: number;
|
power_meter: number; swr: number; swr_raw: number;
|
||||||
rf_power: number; af_gain: number; rf_gain: number; mic_gain: number; squelch: number;
|
rf_power: number; af_gain: number; rf_gain: number; mic_gain: number; squelch: number;
|
||||||
preamp: boolean; att: boolean; nb: boolean; nr: boolean; agc?: string;
|
preamp: boolean; att: boolean; nb: boolean; nr: boolean; agc?: string;
|
||||||
filter_hz: number; antenna: number; rit: boolean; xit: boolean; key_speed: number;
|
filter_hz: number; antenna: number; rit: boolean; xit: boolean; rit_offset: number; key_speed: number;
|
||||||
meters_provisional: boolean;
|
meters_provisional: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,14 +29,17 @@ const ZERO: KenwoodState = {
|
|||||||
s_meter: 0, s_meter_raw: 0, power_meter: 0, swr: 0, swr_raw: 0,
|
s_meter: 0, s_meter_raw: 0, power_meter: 0, swr: 0, swr_raw: 0,
|
||||||
rf_power: 0, af_gain: 0, rf_gain: 0, mic_gain: 0, squelch: 0,
|
rf_power: 0, af_gain: 0, rf_gain: 0, mic_gain: 0, squelch: 0,
|
||||||
preamp: false, att: false, nb: false, nr: false,
|
preamp: false, att: false, nb: false, nr: false,
|
||||||
filter_hz: 0, antenna: 0, rit: false, xit: false, key_speed: 0,
|
filter_hz: 0, antenna: 0, rit: false, xit: false, rit_offset: 0, key_speed: 0,
|
||||||
meters_provisional: true,
|
meters_provisional: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Filter widths worth a button. CW work happens at 200-500 Hz, SSB at 2.4-2.8
|
// Filter widths worth a button. CW work happens at 200-500 Hz, SSB at 2.4-2.8
|
||||||
// kHz; the rest of the range is reachable from the radio's own knob, and a
|
// kHz; the rest of the range is reachable from the radio's own knob, and a
|
||||||
// panel offering thirty widths is slower to use than the knob it replaces.
|
// panel offering thirty widths is slower to use than the knob it replaces.
|
||||||
const FILTERS = [200, 400, 700, 1000, 1800, 2400, 2800];
|
// 4.0k is the K3 maximum and the one FT8 wants: a 2.8 kHz filter clips the
|
||||||
|
// top of the FT8 sub-band, and the decodes that go missing are the ones
|
||||||
|
// nobody notices are missing.
|
||||||
|
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. The K3 answers 0-21 across S0…S9+60; S9 is taken at
|
||||||
// raw 9 and each step above it as 6 dB. PROVISIONAL, like the rest of the
|
// raw 9 and each step above it as 6 dB. PROVISIONAL, like the rest of the
|
||||||
@@ -281,6 +284,15 @@ export function ElecraftPanel({ onReportRST }: { onReportRST?: (rst: string) =>
|
|||||||
<div className="flex flex-wrap items-center gap-1.5">
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
<Toggle label="RIT" on={view.rit} off={off} onClick={() => SetKenwoodRIT(!view.rit).catch(setErrMsg)} />
|
<Toggle label="RIT" on={view.rit} off={off} onClick={() => SetKenwoodRIT(!view.rit).catch(setErrMsg)} />
|
||||||
<Toggle label="XIT" on={view.xit} off={off} onClick={() => SetKenwoodXIT(!view.xit).catch(setErrMsg)} />
|
<Toggle label="XIT" on={view.xit} off={off} onClick={() => SetKenwoodXIT(!view.xit).catch(setErrMsg)} />
|
||||||
|
{/* The offset itself. A lit RIT button says the feature is on and
|
||||||
|
nothing about where it has put the receiver. */}
|
||||||
|
{[-100, -10, 10, 100].map((d) => (
|
||||||
|
<Toggle key={d} label={(d > 0 ? '+' : '') + d} on={false} off={off}
|
||||||
|
onClick={() => NudgeKenwoodRIT(d).catch(setErrMsg)} />
|
||||||
|
))}
|
||||||
|
<span className="text-[11px] font-mono tabular-nums text-muted-foreground w-16">
|
||||||
|
{view.rit_offset > 0 ? '+' : ''}{view.rit_offset || 0} Hz
|
||||||
|
</span>
|
||||||
<Toggle label={t('k3.clear')} on={false} off={off} onClick={() => ClearKenwoodRIT().catch(setErrMsg)} />
|
<Toggle label={t('k3.clear')} on={false} off={off} onClick={() => ClearKenwoodRIT().catch(setErrMsg)} />
|
||||||
{/* Antenna only when the radio answered AN — a K3 without the internal
|
{/* Antenna only when the radio answered AN — a K3 without the internal
|
||||||
ATU has one socket and no switch to offer. */}
|
ATU has one socket and no switch to offer. */}
|
||||||
|
|||||||
Vendored
+2
@@ -803,6 +803,8 @@ export function NetSetDefaults(arg1:string,arg2:string,arg3:string,arg4:string):
|
|||||||
|
|
||||||
export function NetUpdateActive(arg1:qso.QSO):Promise<void>;
|
export function NetUpdateActive(arg1:qso.QSO):Promise<void>;
|
||||||
|
|
||||||
|
export function NudgeKenwoodRIT(arg1:number):Promise<void>;
|
||||||
|
|
||||||
export function OpenADIFFile():Promise<string>;
|
export function OpenADIFFile():Promise<string>;
|
||||||
|
|
||||||
export function OpenAwardsFolder():Promise<void>;
|
export function OpenAwardsFolder():Promise<void>;
|
||||||
|
|||||||
@@ -1546,6 +1546,10 @@ export function NetUpdateActive(arg1) {
|
|||||||
return window['go']['main']['App']['NetUpdateActive'](arg1);
|
return window['go']['main']['App']['NetUpdateActive'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function NudgeKenwoodRIT(arg1) {
|
||||||
|
return window['go']['main']['App']['NudgeKenwoodRIT'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function OpenADIFFile() {
|
export function OpenADIFFile() {
|
||||||
return window['go']['main']['App']['OpenADIFFile']();
|
return window['go']['main']['App']['OpenADIFFile']();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1094,6 +1094,7 @@ export namespace cat {
|
|||||||
antenna: number;
|
antenna: number;
|
||||||
rit: boolean;
|
rit: boolean;
|
||||||
xit: boolean;
|
xit: boolean;
|
||||||
|
rit_offset: number;
|
||||||
key_speed: number;
|
key_speed: number;
|
||||||
meters_provisional: boolean;
|
meters_provisional: boolean;
|
||||||
|
|
||||||
@@ -1129,6 +1130,7 @@ export namespace cat {
|
|||||||
this.antenna = source["antenna"];
|
this.antenna = source["antenna"];
|
||||||
this.rit = source["rit"];
|
this.rit = source["rit"];
|
||||||
this.xit = source["xit"];
|
this.xit = source["xit"];
|
||||||
|
this.rit_offset = source["rit_offset"];
|
||||||
this.key_speed = source["key_speed"];
|
this.key_speed = source["key_speed"];
|
||||||
this.meters_provisional = source["meters_provisional"];
|
this.meters_provisional = source["meters_provisional"];
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-1
@@ -91,11 +91,18 @@ type Kenwood struct {
|
|||||||
keyWPM int // CW keyer speed, for pacing the KY buffer (see kenwood_cw.go)
|
keyWPM int // CW keyer speed, for pacing the KY buffer (see kenwood_cw.go)
|
||||||
// Commands this rig answered "?;" to — asked once, then never again.
|
// Commands this rig answered "?;" to — asked once, then never again.
|
||||||
unsupported map[string]bool
|
unsupported map[string]bool
|
||||||
|
// noLatch suspends that memory while a refusal is expected to be about
|
||||||
|
// timing rather than capability (see ask).
|
||||||
|
noLatch bool
|
||||||
|
|
||||||
// Panel state — the K3/K4 control panel, see kenwood_panel.go. Read on the
|
// Panel state — the K3/K4 control panel, see kenwood_panel.go. Read on the
|
||||||
// same serialised link as everything else, on a slow beat for the settings
|
// same serialised link as everything else, on a slow beat for the settings
|
||||||
// and every poll for the meters.
|
// and every poll for the meters.
|
||||||
panel KenwoodTXState
|
panel KenwoodTXState
|
||||||
|
// The icon/status word, for working out which bit says "ATU in line" — see
|
||||||
|
// probeIcons. Kept so only CHANGES are logged.
|
||||||
|
lastIcons string
|
||||||
|
iconProbes int
|
||||||
panelCycle int
|
panelCycle int
|
||||||
panelLoaded bool
|
panelLoaded bool
|
||||||
metersLogged int
|
metersLogged int
|
||||||
@@ -309,6 +316,15 @@ func (k *Kenwood) ReadState() (RigState, error) {
|
|||||||
if k.tx && !k.txAt.IsZero() && time.Since(k.txAt) < 30*time.Second {
|
if k.tx && !k.txAt.IsZero() && time.Since(k.txAt) < 30*time.Second {
|
||||||
s := k.lastState
|
s := k.lastState
|
||||||
s.Connected = true
|
s.Connected = true
|
||||||
|
// The panel still has to be told the radio is transmitting, and the
|
||||||
|
// transmit meters still have to be read — this early return used to skip
|
||||||
|
// both. The panel therefore showed MOX unlit while the rig was keyed, so
|
||||||
|
// pressing it again sent ANOTHER transmit command instead of unkeying,
|
||||||
|
// and the K3 stayed in TX. The power and SWR bars never moved either,
|
||||||
|
// for the same reason: the only moment they mean anything is the one
|
||||||
|
// moment they were not being read.
|
||||||
|
k.panel.Transmitting = true
|
||||||
|
k.readTXMeters()
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
raw, err := k.ask("IF;")
|
raw, err := k.ask("IF;")
|
||||||
@@ -663,7 +679,12 @@ func (k *Kenwood) ask(cmd string) (string, error) {
|
|||||||
// NOT "unsupported". Latching them off would blind the poll loop for
|
// NOT "unsupported". Latching them off would blind the poll loop for
|
||||||
// good and read as "lost the rig". Only remember the OPTIONAL commands
|
// good and read as "lost the rig". Only remember the OPTIONAL commands
|
||||||
// (FR/FT/…) so the poll loop stops paying a 600 ms timeout for those.
|
// (FR/FT/…) so the poll loop stops paying a 600 ms timeout for those.
|
||||||
if want != "IF" && want != "ID" {
|
// While TRANSMITTING the rig refuses a great deal that it answers
|
||||||
|
// perfectly well on receive, so a "?;" then says nothing about what
|
||||||
|
// the radio supports. Latching it would silence a meter for the rest
|
||||||
|
// of the session on the strength of one badly timed question — and
|
||||||
|
// the meters are read precisely while transmitting.
|
||||||
|
if want != "IF" && want != "ID" && !k.noLatch {
|
||||||
k.unsupported[want] = true
|
k.unsupported[want] = true
|
||||||
debugLog.Printf("kenwood: this rig does not support %q — not asking again", cmd)
|
debugLog.Printf("kenwood: this rig does not support %q — not asking again", cmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ type KenwoodTXState struct {
|
|||||||
|
|
||||||
RIT bool `json:"rit"`
|
RIT bool `json:"rit"`
|
||||||
XIT bool `json:"xit"`
|
XIT bool `json:"xit"`
|
||||||
|
// RITOffset is the RIT/XIT offset in Hz — the number the buttons move and
|
||||||
|
// the one an operator is actually reading when they look at RIT at all. A
|
||||||
|
// lit RIT button with no offset beside it says the feature is on and
|
||||||
|
// nothing about where it has put the receiver.
|
||||||
|
RITOffset int `json:"rit_offset"`
|
||||||
KeySpeed int `json:"key_speed"` // WPM
|
KeySpeed int `json:"key_speed"` // WPM
|
||||||
|
|
||||||
// MetersProvisional says the meter scaling has not been confirmed against a
|
// MetersProvisional says the meter scaling has not been confirmed against a
|
||||||
@@ -97,6 +102,7 @@ type KenwoodPanelController interface {
|
|||||||
SetKenwoodAntenna(int) error
|
SetKenwoodAntenna(int) error
|
||||||
SetKenwoodRIT(bool) error
|
SetKenwoodRIT(bool) error
|
||||||
SetKenwoodXIT(bool) error
|
SetKenwoodXIT(bool) error
|
||||||
|
NudgeKenwoodRIT(int) error
|
||||||
ClearKenwoodRIT() error
|
ClearKenwoodRIT() error
|
||||||
SetKenwoodTX(bool) error
|
SetKenwoodTX(bool) error
|
||||||
TuneKenwoodATU() error
|
TuneKenwoodATU() error
|
||||||
@@ -222,9 +228,16 @@ func (k *Kenwood) readPanelSettings() {
|
|||||||
if v, ok := k.askNum("XT;", "XT", 1); ok {
|
if v, ok := k.askNum("XT;", "XT", 1); ok {
|
||||||
k.panel.XIT = v != 0
|
k.panel.XIT = v != 0
|
||||||
}
|
}
|
||||||
|
// RO carries a sign, so it is read as a whole rather than through askNum.
|
||||||
|
if r, err := k.ask("RO;"); err == nil {
|
||||||
|
if hz, ok := parseKenwoodOffset(r); ok {
|
||||||
|
k.panel.RITOffset = hz
|
||||||
|
}
|
||||||
|
}
|
||||||
if v, ok := k.askNum("KS;", "KS", 3); ok {
|
if v, ok := k.askNum("KS;", "KS", 3); ok {
|
||||||
k.panel.KeySpeed = v
|
k.panel.KeySpeed = v
|
||||||
}
|
}
|
||||||
|
k.probeIcons()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The full-scale value of the analogue controls differs between an Elecraft and
|
// The full-scale value of the analogue controls differs between an Elecraft and
|
||||||
@@ -310,6 +323,10 @@ var kenwoodMeterProbes = []struct {
|
|||||||
// readTXMeters reads the transmit meters.
|
// readTXMeters reads the transmit meters.
|
||||||
func (k *Kenwood) readTXMeters() {
|
func (k *Kenwood) readTXMeters() {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
// Refusals here are about WHEN the question was asked, not about what the
|
||||||
|
// radio can do: a K3 says "?;" to plenty while the carrier is up.
|
||||||
|
k.noLatch = true
|
||||||
|
defer func() { k.noLatch = false }()
|
||||||
if v, ok := k.askNum("BG;", "BG", 2); ok {
|
if v, ok := k.askNum("BG;", "BG", 2); ok {
|
||||||
k.panel.PowerMeter = k.powerPeak.update(kenwoodBargraphPercent(v), now)
|
k.panel.PowerMeter = k.powerPeak.update(kenwoodBargraphPercent(v), now)
|
||||||
}
|
}
|
||||||
@@ -396,6 +413,31 @@ func (k *Kenwood) SetKenwoodAFGain(p int) error {
|
|||||||
return k.setPanel(fmt.Sprintf("AG%03d;", p*255/100))
|
return k.setPanel(fmt.Sprintf("AG%03d;", p*255/100))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// probeIcons logs the K3's icon/status word whenever it changes.
|
||||||
|
//
|
||||||
|
// There is no command that asks "is the ATU in line": the reference says the
|
||||||
|
// front-panel switch functions show up as icon changes readable through IC (or
|
||||||
|
// DS), which means the answer is a bit in a word nobody here can name yet. So
|
||||||
|
// the word is logged when it changes, and an operator toggling the ATU while
|
||||||
|
// watching the log hands us the bit — after which the ATU button can light up
|
||||||
|
// honestly instead of guessing from what it last sent.
|
||||||
|
func (k *Kenwood) probeIcons() {
|
||||||
|
if k.iconProbes >= 40 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r, err := k.ask("IC;")
|
||||||
|
if err != nil {
|
||||||
|
k.iconProbes = 40 // this rig has no IC; stop asking
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r == k.lastIcons {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k.lastIcons = r
|
||||||
|
k.iconProbes++
|
||||||
|
debugLog.Printf("kenwood: icon status changed → %s (toggle ATU/PRE/ATT while watching this line to name the bits)", r)
|
||||||
|
}
|
||||||
|
|
||||||
// SetKenwoodRFGain sets the RF gain, 0-100 of the rig's own scale.
|
// SetKenwoodRFGain sets the RF gain, 0-100 of the rig's own scale.
|
||||||
func (k *Kenwood) SetKenwoodRFGain(p int) error {
|
func (k *Kenwood) SetKenwoodRFGain(p int) error {
|
||||||
return k.setPanel(fmt.Sprintf("RG%03d;", clampPercentTo(p, k.rfGainFull())))
|
return k.setPanel(fmt.Sprintf("RG%03d;", clampPercentTo(p, k.rfGainFull())))
|
||||||
@@ -456,6 +498,60 @@ func (k *Kenwood) SetKenwoodAntenna(n int) error {
|
|||||||
return k.setPanel(fmt.Sprintf("AN%d;", n))
|
return k.setPanel(fmt.Sprintf("AN%d;", n))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseKenwoodOffset reads "RO+0100;" / "RO-0250;" into Hz.
|
||||||
|
func parseKenwoodOffset(frame string) (int, bool) {
|
||||||
|
body := strings.TrimSuffix(strings.TrimPrefix(frame, "RO"), ";")
|
||||||
|
if len(body) < 2 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
sign := 1
|
||||||
|
switch body[0] {
|
||||||
|
case '-':
|
||||||
|
sign = -1
|
||||||
|
body = body[1:]
|
||||||
|
case '+':
|
||||||
|
body = body[1:]
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(strings.TrimSpace(body))
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return sign * n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NudgeKenwoodRIT moves the RIT/XIT offset by delta Hz.
|
||||||
|
//
|
||||||
|
// The offset is SET rather than stepped, because RO is where the radio keeps
|
||||||
|
// it and stepping commands differ across the family. The panel's own reading is
|
||||||
|
// the starting point, so two quick presses do not both start from the same
|
||||||
|
// stale value.
|
||||||
|
func (k *Kenwood) NudgeKenwoodRIT(delta int) error {
|
||||||
|
k.mu.Lock()
|
||||||
|
cur := k.panel.RITOffset
|
||||||
|
k.mu.Unlock()
|
||||||
|
next := cur + delta
|
||||||
|
// A pile-up is chased with a few hundred hertz of RIT; ±5 kHz is already
|
||||||
|
// past anything an offset is for, and past what the rig accepts.
|
||||||
|
if next > 5000 {
|
||||||
|
next = 5000
|
||||||
|
}
|
||||||
|
if next < -5000 {
|
||||||
|
next = -5000
|
||||||
|
}
|
||||||
|
sign := "+"
|
||||||
|
if next < 0 {
|
||||||
|
sign = "-"
|
||||||
|
}
|
||||||
|
mag := next
|
||||||
|
if mag < 0 {
|
||||||
|
mag = -mag
|
||||||
|
}
|
||||||
|
k.mu.Lock()
|
||||||
|
k.panel.RITOffset = next // optimistic, like the sliders
|
||||||
|
k.mu.Unlock()
|
||||||
|
return k.setPanel(fmt.Sprintf("RO%s%04d;", sign, mag))
|
||||||
|
}
|
||||||
|
|
||||||
func (k *Kenwood) SetKenwoodRIT(on bool) error {
|
func (k *Kenwood) SetKenwoodRIT(on bool) error {
|
||||||
return k.setPanel(fmt.Sprintf("RT%d;", boolDigit(on)))
|
return k.setPanel(fmt.Sprintf("RT%d;", boolDigit(on)))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user