feat(elecraft): the rest of the main controls on the K3/K4 console
So the tester can exercise everything in one session instead of one control per build: RF and mic gain, squelch, preamp, attenuator, NB, NR, AGC, filter width, antenna, RIT/XIT with a clear, and the keyer speed. The analogue scales differ between an Elecraft and a Kenwood — RF gain 000-250, mic 000-060, squelch 000-029 against 0-255 — so each is mapped through its own full scale and shown as a percentage. Using one rig's range on the other silently halves or doubles every setting. Antenna only appears when the radio answers AN: a K3 without the internal ATU has one socket, and a switch that goes nowhere is worse than none.
This commit is contained in:
@@ -49,6 +49,28 @@ type KenwoodTXState struct {
|
||||
|
||||
RFPower int `json:"rf_power"` // watts, the PC setting
|
||||
AFGain int `json:"af_gain"` // 0-100
|
||||
RFGain int `json:"rf_gain"` // 0-100
|
||||
MicGain int `json:"mic_gain"` // 0-100
|
||||
Squelch int `json:"squelch"` // 0-100
|
||||
|
||||
// Receive controls. Preamp and attenuator are the K3's single-step ones
|
||||
// (PA/RA); a rig that answers neither leaves them false and the row shows
|
||||
// the state it read, not a state it assumed.
|
||||
Preamp bool `json:"preamp"`
|
||||
Att bool `json:"att"`
|
||||
NB bool `json:"nb"`
|
||||
NR bool `json:"nr"`
|
||||
AGC string `json:"agc,omitempty"` // "OFF", "SLOW", "FAST"
|
||||
// FilterHz is the DSP bandwidth in Hz (the K3 reports it in 10 Hz units).
|
||||
FilterHz int `json:"filter_hz"`
|
||||
// Antenna is 1 or 2 on a K3 with the internal ATU, 0 when the rig does not
|
||||
// answer AN — which is also how the panel knows to hide the row rather than
|
||||
// offer a switch that goes nowhere.
|
||||
Antenna int `json:"antenna"`
|
||||
|
||||
RIT bool `json:"rit"`
|
||||
XIT bool `json:"xit"`
|
||||
KeySpeed int `json:"key_speed"` // WPM
|
||||
|
||||
// MetersProvisional says the meter scaling has not been confirmed against a
|
||||
// real radio. The panel says so rather than presenting a guess as a
|
||||
@@ -63,6 +85,19 @@ type KenwoodPanelController interface {
|
||||
RefreshKenwood() error
|
||||
SetKenwoodPower(int) error
|
||||
SetKenwoodAFGain(int) error
|
||||
SetKenwoodRFGain(int) error
|
||||
SetKenwoodMicGain(int) error
|
||||
SetKenwoodSquelch(int) error
|
||||
SetKenwoodPreamp(bool) error
|
||||
SetKenwoodAtt(bool) error
|
||||
SetKenwoodNB(bool) error
|
||||
SetKenwoodNR(bool) error
|
||||
SetKenwoodAGC(string) error
|
||||
SetKenwoodFilter(int) error
|
||||
SetKenwoodAntenna(int) error
|
||||
SetKenwoodRIT(bool) error
|
||||
SetKenwoodXIT(bool) error
|
||||
ClearKenwoodRIT() error
|
||||
SetKenwoodTX(bool) error
|
||||
TuneKenwoodATU() error
|
||||
}
|
||||
@@ -140,6 +175,116 @@ func (k *Kenwood) readPanelSettings() {
|
||||
} else if v, ok := k.askNum("AG0;", "AG0", 3); ok {
|
||||
k.panel.AFGain = scale255(v)
|
||||
}
|
||||
// RF gain. The K3 counts 000-250 in dB of reduction; a Kenwood uses the
|
||||
// familiar 0-255. Both are shown as a percentage, so the slider means the
|
||||
// same thing on either radio.
|
||||
if v, ok := k.askNum("RG;", "RG", 3); ok {
|
||||
k.panel.RFGain = scalePercent(v, k.rfGainFull())
|
||||
}
|
||||
if v, ok := k.askNum("MG;", "MG", 3); ok {
|
||||
k.panel.MicGain = scalePercent(v, k.micGainFull())
|
||||
}
|
||||
if v, ok := k.askNum("SQ;", "SQ", 3); ok {
|
||||
k.panel.Squelch = scalePercent(v, k.squelchFull())
|
||||
} else if v, ok := k.askNum("SQ0;", "SQ0", 3); ok {
|
||||
k.panel.Squelch = scale255(v)
|
||||
}
|
||||
if v, ok := k.askNum("PA;", "PA", 1); ok {
|
||||
k.panel.Preamp = v != 0
|
||||
}
|
||||
if v, ok := k.askNum("RA;", "RA", 2); ok {
|
||||
k.panel.Att = v != 0
|
||||
}
|
||||
if v, ok := k.askNum("NB;", "NB", 1); ok {
|
||||
k.panel.NB = v != 0
|
||||
}
|
||||
if v, ok := k.askNum("NR;", "NR", 1); ok {
|
||||
k.panel.NR = v != 0
|
||||
}
|
||||
if v, ok := k.askNum("GT;", "GT", 3); ok {
|
||||
k.panel.AGC = kenwoodAGCName(v)
|
||||
}
|
||||
// Filter width. The K3 answers BW in 10 Hz units (BW0270 = 2.7 kHz); a
|
||||
// Kenwood that does not implement it says nothing and the row stays as it
|
||||
// was rather than showing a zero-width filter.
|
||||
if v, ok := k.askNum("BW;", "BW", 4); ok && v > 0 {
|
||||
k.panel.FilterHz = v * 10
|
||||
}
|
||||
// Antenna. Only a K3 with the internal ATU answers; 0 means "this radio has
|
||||
// no antenna switching", which is what hides the row.
|
||||
if v, ok := k.askNum("AN;", "AN", 1); ok {
|
||||
k.panel.Antenna = v
|
||||
}
|
||||
if v, ok := k.askNum("RT;", "RT", 1); ok {
|
||||
k.panel.RIT = v != 0
|
||||
}
|
||||
if v, ok := k.askNum("XT;", "XT", 1); ok {
|
||||
k.panel.XIT = v != 0
|
||||
}
|
||||
if v, ok := k.askNum("KS;", "KS", 3); ok {
|
||||
k.panel.KeySpeed = v
|
||||
}
|
||||
}
|
||||
|
||||
// The full-scale value of the analogue controls differs between an Elecraft and
|
||||
// a Kenwood, and using one rig's scale on the other silently halves or doubles
|
||||
// every setting. Kept as three small functions rather than a table so each one
|
||||
// carries the range it comes from.
|
||||
func (k *Kenwood) rfGainFull() int {
|
||||
if k.elecraft {
|
||||
return 250 // K3: 000-250, dB of reduction
|
||||
}
|
||||
return 255
|
||||
}
|
||||
|
||||
func (k *Kenwood) micGainFull() int {
|
||||
if k.elecraft {
|
||||
return 60 // K3: 000-060
|
||||
}
|
||||
return 255
|
||||
}
|
||||
|
||||
func (k *Kenwood) squelchFull() int {
|
||||
if k.elecraft {
|
||||
return 29 // K3: 000-029
|
||||
}
|
||||
return 255
|
||||
}
|
||||
|
||||
// scalePercent turns a raw value into 0-100 against its own full scale.
|
||||
func scalePercent(v, full int) int {
|
||||
if full <= 0 || v <= 0 {
|
||||
return 0
|
||||
}
|
||||
if v >= full {
|
||||
return 100
|
||||
}
|
||||
return v * 100 / full
|
||||
}
|
||||
|
||||
// kenwoodAGCName decodes GT. The K3 answers 000 (off), 002 (slow) or 004
|
||||
// (fast); a Kenwood uses the same three values for the same three states.
|
||||
func kenwoodAGCName(v int) string {
|
||||
switch {
|
||||
case v == 0:
|
||||
return "OFF"
|
||||
case v <= 2:
|
||||
return "SLOW"
|
||||
default:
|
||||
return "FAST"
|
||||
}
|
||||
}
|
||||
|
||||
// kenwoodAGCValue is the inverse, for the buttons.
|
||||
func kenwoodAGCValue(name string) int {
|
||||
switch strings.ToUpper(strings.TrimSpace(name)) {
|
||||
case "OFF":
|
||||
return 0
|
||||
case "SLOW":
|
||||
return 2
|
||||
default:
|
||||
return 4
|
||||
}
|
||||
}
|
||||
|
||||
// kenwoodMeterProbes are the candidate meter commands, asked once per
|
||||
@@ -241,6 +386,91 @@ func (k *Kenwood) SetKenwoodAFGain(p int) error {
|
||||
return k.setPanel(fmt.Sprintf("AG%03d;", p*255/100))
|
||||
}
|
||||
|
||||
// SetKenwoodRFGain sets the RF gain, 0-100 of the rig's own scale.
|
||||
func (k *Kenwood) SetKenwoodRFGain(p int) error {
|
||||
return k.setPanel(fmt.Sprintf("RG%03d;", clampPercentTo(p, k.rfGainFull())))
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetKenwoodMicGain(p int) error {
|
||||
return k.setPanel(fmt.Sprintf("MG%03d;", clampPercentTo(p, k.micGainFull())))
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetKenwoodSquelch(p int) error {
|
||||
return k.setPanel(fmt.Sprintf("SQ%03d;", clampPercentTo(p, k.squelchFull())))
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetKenwoodPreamp(on bool) error {
|
||||
return k.setPanel(fmt.Sprintf("PA%d;", boolDigit(on)))
|
||||
}
|
||||
|
||||
// SetKenwoodAtt switches the attenuator. Two digits: the K3 reads RA01 as its
|
||||
// single 10 dB pad, and a Kenwood with several steps takes the first one.
|
||||
func (k *Kenwood) SetKenwoodAtt(on bool) error {
|
||||
if on {
|
||||
return k.setPanel("RA01;")
|
||||
}
|
||||
return k.setPanel("RA00;")
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetKenwoodNB(on bool) error {
|
||||
return k.setPanel(fmt.Sprintf("NB%d;", boolDigit(on)))
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetKenwoodNR(on bool) error {
|
||||
return k.setPanel(fmt.Sprintf("NR%d;", boolDigit(on)))
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetKenwoodAGC(name string) error {
|
||||
return k.setPanel(fmt.Sprintf("GT%03d;", kenwoodAGCValue(name)))
|
||||
}
|
||||
|
||||
// SetKenwoodFilter sets the DSP bandwidth in Hz — sent in the K3's 10 Hz units.
|
||||
func (k *Kenwood) SetKenwoodFilter(hz int) error {
|
||||
if hz < 50 {
|
||||
hz = 50
|
||||
}
|
||||
if hz > 4000 {
|
||||
hz = 4000
|
||||
}
|
||||
return k.setPanel(fmt.Sprintf("BW%04d;", hz/10))
|
||||
}
|
||||
|
||||
// SetKenwoodAntenna selects antenna 1 or 2 (a K3 with the internal ATU).
|
||||
func (k *Kenwood) SetKenwoodAntenna(n int) error {
|
||||
if n < 1 {
|
||||
n = 1
|
||||
}
|
||||
if n > 2 {
|
||||
n = 2
|
||||
}
|
||||
return k.setPanel(fmt.Sprintf("AN%d;", n))
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetKenwoodRIT(on bool) error {
|
||||
return k.setPanel(fmt.Sprintf("RT%d;", boolDigit(on)))
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetKenwoodXIT(on bool) error {
|
||||
return k.setPanel(fmt.Sprintf("XT%d;", boolDigit(on)))
|
||||
}
|
||||
|
||||
// ClearKenwoodRIT zeroes the RIT/XIT offset, both at once — which is what RC
|
||||
// does and what the operator means by "clear it".
|
||||
func (k *Kenwood) ClearKenwoodRIT() error {
|
||||
return k.setPanel("RC;")
|
||||
}
|
||||
|
||||
// clampPercentTo maps 0-100 onto a rig's own full scale.
|
||||
func clampPercentTo(p, full int) int {
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
if p > 100 {
|
||||
p = 100
|
||||
}
|
||||
return p * full / 100
|
||||
}
|
||||
|
||||
// SetKenwoodTX keys or unkeys the transmitter — the panel's MOX.
|
||||
//
|
||||
// Goes through SetPTT rather than writing TX;/RX; here, so the backend's own
|
||||
|
||||
Reference in New Issue
Block a user