chore: release v0.27.12
This commit is contained in:
@@ -425,3 +425,49 @@ func indexPreamble(buf []byte, from int) int {
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ── Band stacking registers (CI-V 0x1A sub 0x01) ──────────────────────────
|
||||
//
|
||||
// Every modern Icom remembers the last few frequency/mode pairs used on each
|
||||
// band, and its front-panel band key walks through them. That is why pressing
|
||||
// [14] on the radio lands on the FT8 watering hole rather than on a number
|
||||
// somebody chose in software: the register is the operator's OWN last visit.
|
||||
//
|
||||
// The frame is a READ — it asks the rig what a register holds and changes
|
||||
// nothing — so a rig that does not know the command answers NG and the caller
|
||||
// is exactly where it started.
|
||||
//
|
||||
// → 1A 01 <band> <reg>
|
||||
// ← 1A 01 <band> <reg> <freq 5 BCD, LE> <mode> <filter> <data mode> …
|
||||
//
|
||||
// Anything past the data-mode byte (duplex, tone, DV squelch on the VHF rigs)
|
||||
// is not read here: the question is where the operator last was, and the answer
|
||||
// to that is the frequency and the mode.
|
||||
const SubBandStack = 0x01
|
||||
|
||||
// BandStack is one register's contents.
|
||||
type BandStack struct {
|
||||
FreqHz int64
|
||||
Mode byte
|
||||
Data bool // the data-mode flag that goes with Mode
|
||||
}
|
||||
|
||||
// DecodeBandStack reads the payload of a 0x1A 0x01 reply, i.e. everything after
|
||||
// the command byte. ok is false for a frame that is not the register asked for,
|
||||
// which is what a desynchronised read looks like.
|
||||
func DecodeBandStack(data []byte, band, reg byte) (BandStack, bool) {
|
||||
if len(data) < 9 || data[0] != SubBandStack || data[1] != band || data[2] != reg {
|
||||
return BandStack{}, false
|
||||
}
|
||||
hz, ok := BCDToFreq(data[3:8])
|
||||
if !ok || hz <= 0 {
|
||||
return BandStack{}, false
|
||||
}
|
||||
bs := BandStack{FreqHz: hz, Mode: data[8]}
|
||||
// Filter then data mode. A rig that stops at the filter byte is not an
|
||||
// error — it is a register without a data flag, so the flag stays false.
|
||||
if len(data) >= 11 {
|
||||
bs.Data = data[10] != 0
|
||||
}
|
||||
return bs, true
|
||||
}
|
||||
|
||||
@@ -204,3 +204,36 @@ func TestBCDToFreqRejectsNonDecimal(t *testing.T) {
|
||||
t.Error("a short but valid BCD frame must still decode")
|
||||
}
|
||||
}
|
||||
|
||||
// A band stacking register reply, byte for byte as the rigs send it:
|
||||
// 1A 01 <band> <reg> <freq 5 LE-BCD> <mode> <filter> <data>. The payload here
|
||||
// is everything after the command byte, which is what Decoded.Data holds.
|
||||
func TestDecodeBandStack(t *testing.T) {
|
||||
// 14.074.000 MHz, USB, FIL1, data mode on — the FT8 stack on 20 m.
|
||||
frame := []byte{0x01, 0x05, 0x03, 0x00, 0x40, 0x07, 0x14, 0x00, ModeUSB, 0x01, 0x01}
|
||||
bs, ok := DecodeBandStack(frame, 0x05, 0x03)
|
||||
if !ok {
|
||||
t.Fatal("a well-formed register was rejected")
|
||||
}
|
||||
if bs.FreqHz != 14_074_000 {
|
||||
t.Errorf("freq = %d, want 14074000", bs.FreqHz)
|
||||
}
|
||||
if bs.Mode != ModeUSB || !bs.Data {
|
||||
t.Errorf("mode = 0x%02X data = %v, want USB + data", bs.Mode, bs.Data)
|
||||
}
|
||||
// The register ASKED FOR is part of the answer: a reply about another one is
|
||||
// a desynchronised read, not a frequency to send the radio to.
|
||||
if _, ok := DecodeBandStack(frame, 0x05, 0x01); ok {
|
||||
t.Error("a reply for register 3 was accepted as register 1")
|
||||
}
|
||||
if _, ok := DecodeBandStack(frame, 0x03, 0x03); ok {
|
||||
t.Error("a reply about 40 m was accepted as 20 m")
|
||||
}
|
||||
// A register without the data-mode byte is a shorter frame, not a bad one.
|
||||
if bs, ok := DecodeBandStack(frame[:10], 0x05, 0x03); !ok || bs.FreqHz != 14_074_000 || bs.Data {
|
||||
t.Errorf("short register = %+v ok=%v, want the frequency with no data flag", bs, ok)
|
||||
}
|
||||
if _, ok := DecodeBandStack([]byte{0x01, 0x05, 0x03}, 0x05, 0x03); ok {
|
||||
t.Error("a truncated frame was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user