feat: icom Scope in Icom Tab
This commit is contained in:
+58
-2
@@ -376,8 +376,17 @@ type IcomTXState struct {
|
||||
Available bool `json:"available"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
AFGain int `json:"af_gain"`
|
||||
RFGain int `json:"rf_gain"`
|
||||
// Transmit + live status (polled).
|
||||
Transmitting bool `json:"transmitting"`
|
||||
Split bool `json:"split"`
|
||||
SMeter int `json:"s_meter"` // 0-100 (raw 0-255; S9≈120)
|
||||
PowerMeter int `json:"power_meter"` // 0-100 (TX Po)
|
||||
SWRMeter int `json:"swr_meter"` // 0-100 (TX SWR)
|
||||
// Set controls.
|
||||
RFPower int `json:"rf_power"` // 0-100 (TX output)
|
||||
MicGain int `json:"mic_gain"` // 0-100
|
||||
AFGain int `json:"af_gain"`
|
||||
RFGain int `json:"rf_gain"`
|
||||
NB bool `json:"nb"`
|
||||
NBLevel int `json:"nb_level"`
|
||||
NR bool `json:"nr"`
|
||||
@@ -406,6 +415,25 @@ type IcomController interface {
|
||||
SetPreamp(int) error
|
||||
SetAtt(int) error
|
||||
SetIcomFilter(int) error
|
||||
SetRFPower(int) error
|
||||
SetMicGain(int) error
|
||||
SetIcomSplit(bool) error
|
||||
TuneATU() error
|
||||
SetScope(bool) error // enable/disable the spectrum-scope waveform stream
|
||||
SetScopeMode(bool) error // true = fixed span, false = center-on-VFO
|
||||
ScopeData() ScopeSweep // latest assembled sweep (empty until enabled)
|
||||
}
|
||||
|
||||
// ScopeSweep is one complete spectrum-scope sweep reassembled from the Icom's
|
||||
// divided 0x27 waveform frames. Amp holds one amplitude byte per pixel (raw rig
|
||||
// scale, typically 0-160). Seq increments on every completed sweep so the UI can
|
||||
// tell fresh data from a repeated poll.
|
||||
type ScopeSweep struct {
|
||||
Amp []int `json:"amp"` // []int (not []byte) so it marshals as a JSON number array
|
||||
Seq int `json:"seq"`
|
||||
LowHz int64 `json:"low_hz"` // left edge frequency (0 when unknown)
|
||||
HighHz int64 `json:"high_hz"` // right edge frequency (0 when unknown)
|
||||
Fixed bool `json:"fixed"` // true = fixed-span mode, false = center-on-VFO
|
||||
}
|
||||
|
||||
// IcomState returns the current Icom DSP state, or (zero, false) when the active
|
||||
@@ -420,6 +448,19 @@ func (m *Manager) IcomState() (IcomTXState, bool) {
|
||||
return IcomTXState{}, false
|
||||
}
|
||||
|
||||
// IcomScope returns the latest spectrum-scope sweep, or (zero, false) when the
|
||||
// active backend isn't an Icom. The sweep is mutex-guarded in the backend, so
|
||||
// this reads it directly (no CAT-goroutine round trip) — cheap enough to poll.
|
||||
func (m *Manager) IcomScope() (ScopeSweep, bool) {
|
||||
m.mu.RLock()
|
||||
b := m.backend
|
||||
m.mu.RUnlock()
|
||||
if ic, ok := b.(IcomController); ok {
|
||||
return ic.ScopeData(), true
|
||||
}
|
||||
return ScopeSweep{}, false
|
||||
}
|
||||
|
||||
// IcomDo dispatches an Icom control onto the CAT goroutine. Errors if the
|
||||
// active backend isn't an Icom.
|
||||
func (m *Manager) IcomDo(fn func(IcomController) error) error {
|
||||
@@ -490,6 +531,21 @@ func (m *Manager) run(b Backend, stop, done chan struct{}, cmds chan func(), pol
|
||||
fn()
|
||||
m.applyCommandDelay()
|
||||
case <-ticker.C:
|
||||
// Drain any queued commands before polling. A serial backend reads
|
||||
// many registers per ReadState, so without this the shared select's
|
||||
// fairness lets polls repeatedly win and a user's Set* can lag by
|
||||
// seconds. Servicing commands first bounds that latency to a single
|
||||
// ReadState.
|
||||
for {
|
||||
select {
|
||||
case fn := <-cmds:
|
||||
fn()
|
||||
m.applyCommandDelay()
|
||||
continue
|
||||
default:
|
||||
}
|
||||
break
|
||||
}
|
||||
if !connected {
|
||||
tryConnect()
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user