chore: release v0.22.9
This commit is contained in:
+8
-16
@@ -314,7 +314,12 @@ func (f *Flex) send(cmd string) int {
|
||||
debugLog.Printf("Flex: send %q failed: %v", cmd, err)
|
||||
return 0
|
||||
}
|
||||
debugLog.Printf("Flex: → %s", cmd)
|
||||
// Meter subscriptions are pure bookkeeping and a Flex announces meters one
|
||||
// status line at a time — tracing each "sub meter N" wrote dozens of lines
|
||||
// at every connect.
|
||||
if !strings.HasPrefix(cmd, "sub meter ") {
|
||||
debugLog.Printf("Flex: → %s", cmd)
|
||||
}
|
||||
return seq
|
||||
}
|
||||
|
||||
@@ -773,17 +778,11 @@ func (f *Flex) handleStatus(payload string) {
|
||||
f.meterMeta[num] = old
|
||||
}
|
||||
f.mu.Unlock()
|
||||
// One line for the whole batch, not one per meter: a Flex announces
|
||||
// dozens at connect and that was a wall of text every time.
|
||||
var names []string
|
||||
// Not logged: the radio announces meters ONE per status line, so even
|
||||
// a batched line meant dozens of them at every connect.
|
||||
for _, id := range newIDs {
|
||||
mi := f.meterMeta[id]
|
||||
names = append(names, nonEmpty(mi.name, strconv.Itoa(id)))
|
||||
f.subscribeMeter(id)
|
||||
}
|
||||
if len(names) > 0 {
|
||||
debugLog.Printf("Flex: subscribed to %d meter(s): %s", len(names), strings.Join(names, " "))
|
||||
}
|
||||
}
|
||||
// Spot status: "spot <index> …". Track the index so we can clear the
|
||||
// panadapter, and log it verbatim — a click on a panadapter spot pushes a
|
||||
@@ -2261,13 +2260,6 @@ func (f *Flex) subscribeMeter(id int) {
|
||||
f.send(fmt.Sprintf("sub meter %d", id))
|
||||
}
|
||||
|
||||
func nonEmpty(s, def string) string {
|
||||
if s == "" {
|
||||
return def
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func parseFloatDefault(s string, def float64) float64 {
|
||||
if v, err := strconv.ParseFloat(strings.TrimSpace(s), 64); err == nil {
|
||||
return v
|
||||
|
||||
+66
-15
@@ -114,6 +114,15 @@ type IcomSerial struct {
|
||||
silentGrace time.Duration // current width of that tolerance (backs off, see ReadState)
|
||||
dspLoaded bool // readDSP has run since the rig became responsive (loads all
|
||||
// the panel's set-once controls once the rig actually answers)
|
||||
// When the console last asked for the DSP snapshot. The meters and the
|
||||
// front-panel rotation are polled ONLY while something is displaying them:
|
||||
// they are half of OpsLog's CI-V traffic, and on a shared bus — a microHAM
|
||||
// Router splitting one CI-V link into two virtual ports, OpsLog on one and
|
||||
// WSJT-X/MSHV on the other — every frame we don't need is a frame that can
|
||||
// collide with the other program's.
|
||||
dspWantMu sync.Mutex
|
||||
dspWantAt time.Time
|
||||
|
||||
lastSetFreq int64 // last frequency commanded (spot click: freq then mode)
|
||||
lastSetFreqAt time.Time
|
||||
|
||||
@@ -409,15 +418,25 @@ func (b *IcomSerial) ReadState() (RigState, error) {
|
||||
}
|
||||
|
||||
// Live meters + TX state for the Icom panel (the rig doesn't push these).
|
||||
// The meters are polled only while the console is actually on screen: they
|
||||
// were three CI-V round trips per cycle, every cycle, whether or not anyone
|
||||
// could see them. The previous readings are kept so the panel opens on the
|
||||
// last known values rather than on zeros.
|
||||
tx := b.readTX()
|
||||
sm, _ := b.readMeter(civ.SubMeterS)
|
||||
po, swr := 0, 0
|
||||
if tx {
|
||||
if v, ok := b.readMeter(civ.SubMeterPo); ok {
|
||||
po = v
|
||||
}
|
||||
if v, ok := b.readMeter(civ.SubMeterSWR); ok {
|
||||
swr = v
|
||||
watched := b.panelWatched()
|
||||
b.dspMu.Lock()
|
||||
sm, po, swr := b.dsp.SMeter, b.dsp.PowerMeter, b.dsp.SWRMeter
|
||||
b.dspMu.Unlock()
|
||||
if watched {
|
||||
sm, _ = b.readMeter(civ.SubMeterS)
|
||||
po, swr = 0, 0
|
||||
if tx {
|
||||
if v, ok := b.readMeter(civ.SubMeterPo); ok {
|
||||
po = v
|
||||
}
|
||||
if v, ok := b.readMeter(civ.SubMeterSWR); ok {
|
||||
swr = v
|
||||
}
|
||||
}
|
||||
}
|
||||
b.dspMu.Lock()
|
||||
@@ -434,11 +453,15 @@ func (b *IcomSerial) ReadState() (RigState, error) {
|
||||
// snapshot so the panel's antenna, sliders, RIT, notch, etc. reflect the rig
|
||||
// instead of sitting at their zero defaults. Runs once; ↻ Refresh re-reads on
|
||||
// demand, and a reconnect re-arms it (Connect clears dspLoaded).
|
||||
if !b.dspLoaded {
|
||||
b.readDSP()
|
||||
b.dspLoaded = true
|
||||
} else {
|
||||
b.refreshFrontPanel()
|
||||
// Both of these only make sense for the console — don't spend the bus on them
|
||||
// while it is closed. The snapshot then loads the first time it is opened.
|
||||
if watched {
|
||||
if !b.dspLoaded {
|
||||
b.readDSP()
|
||||
b.dspLoaded = true
|
||||
} else {
|
||||
b.refreshFrontPanel()
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -532,12 +555,25 @@ func (b *IcomSerial) SetPower(on bool) error {
|
||||
return fmt.Errorf("icom: not connected")
|
||||
}
|
||||
if on {
|
||||
buf := make([]byte, 0, 32)
|
||||
for i := 0; i < 25; i++ {
|
||||
// The preamble has to last long enough IN TIME for the sleeping rig to
|
||||
// notice it, so its length scales with the baud rate — Icom's own table
|
||||
// asks for 7 bytes at 4800 baud and 150 at 115200, which is a fixed
|
||||
// ~25 ms of carrier. A flat 25 bytes (what this sent before) is right at
|
||||
// 19200 and far too short above it, so a rig configured for 115200 simply
|
||||
// ignored the command.
|
||||
n := b.baud / 768
|
||||
if n < 7 {
|
||||
n = 7
|
||||
} else if n > 150 {
|
||||
n = 150
|
||||
}
|
||||
buf := make([]byte, 0, n+8)
|
||||
for i := 0; i < n; i++ {
|
||||
buf = append(buf, 0xFE)
|
||||
}
|
||||
buf = append(buf, 0xFE, 0xFE, b.rigAddr, civ.AddrController, civ.CmdPower, 0x01, 0xFD)
|
||||
_, err := b.port.Write(buf)
|
||||
applog.Printf("icom: power ON — %d-byte wake preamble at %d baud, addr 0x%02X (err=%v)", n, b.baud, b.rigAddr, err)
|
||||
return err
|
||||
}
|
||||
_, err := b.port.Write(civ.Frame(b.rigAddr, civ.AddrController, civ.CmdPower, 0x00))
|
||||
@@ -1289,11 +1325,26 @@ func (b *IcomSerial) modeCode(mode string) (code byte, data bool, err error) {
|
||||
// ── IcomController: receive-DSP controls for the Icom tab ───────────────────
|
||||
|
||||
func (b *IcomSerial) IcomState() IcomTXState {
|
||||
// Asking for the snapshot is what marks the console as being watched, which
|
||||
// is what re-enables the meter polling in ReadState.
|
||||
b.dspWantMu.Lock()
|
||||
b.dspWantAt = time.Now()
|
||||
b.dspWantMu.Unlock()
|
||||
|
||||
b.dspMu.Lock()
|
||||
defer b.dspMu.Unlock()
|
||||
return b.dsp
|
||||
}
|
||||
|
||||
// panelWatched reports whether the Icom console asked for the DSP snapshot
|
||||
// recently — i.e. whether anything is on screen to read the meters.
|
||||
func (b *IcomSerial) panelWatched() bool {
|
||||
b.dspWantMu.Lock()
|
||||
at := b.dspWantAt
|
||||
b.dspWantMu.Unlock()
|
||||
return !at.IsZero() && time.Since(at) < 3*time.Second
|
||||
}
|
||||
|
||||
// RefreshIcom re-reads the whole DSP snapshot from the rig. Runs on the CAT
|
||||
// goroutine (dispatched via IcomDo).
|
||||
func (b *IcomSerial) RefreshIcom() error {
|
||||
|
||||
Reference in New Issue
Block a user