TCI already carries the frequency, the mode, the meters and now the audio. It also carries everything else about the radio — and OpsLog was logging most of it once as '(unhandled once)' and throwing it away. The console is mostly a place to put what was already arriving. That makes it the cheapest panel here, and it is worth saying why. A K3 console costs a command and a reply for every value it shows, which is why it reads its settings in a rotation and its meters only while on screen. TCI PUSHES: the radio announces its drive, its filters, its noise blanker and the rest on connect, and again whenever any of them changes — including when the operator changes them in ExpertSDR3's own window, which this panel therefore follows without asking anything. What it drives: drive and tune drive, mic gain, TUNE, volume, mute, squelch and its threshold, NB, NR, ANF, APF, AGC speed, the passband, RIT and XIT with their offsets, and the VFO lock. The S-meter is a real dBm reading, so its S units are arithmetic rather than the calibration guess a K3's meter needs. Setters never update the cached state. The radio answers with the new value, and taking its word is what keeps the panel honest when a setting is refused, clamped, or changed at the radio a second later — the one exception being a slider mid-drag, held for 900 ms so it is not dragged back by its own echo. Capped width and centred, like the other consoles. Also offered as a docked pane — and the Elecraft console is offered there too now: App has always had that pane, Settings simply never listed it.
99 lines
3.0 KiB
Go
99 lines
3.0 KiB
Go
//go:build windows
|
|
|
|
package cat
|
|
|
|
import "fmt"
|
|
|
|
// TCIAudioController is the receive-audio capability of the TCI backend, kept
|
|
// as an interface for the same reason as the Flex and Yaesu ones: the host asks
|
|
// the manager, and a station running something else gets a clear "this backend
|
|
// does not do that" instead of a nil dereference.
|
|
type TCIAudioController interface {
|
|
StartTCIAudio(rx, rate int) error
|
|
StopTCIAudio() error
|
|
TCIAudioStatus() TCIAudioStatus
|
|
}
|
|
|
|
// TCIAudioState returns the stream's state, or (zero, false) when the active
|
|
// backend is not a TCI radio.
|
|
func (m *Manager) TCIAudioState() (TCIAudioStatus, bool) {
|
|
m.mu.RLock()
|
|
b := m.backend
|
|
m.mu.RUnlock()
|
|
if tc, ok := b.(TCIAudioController); ok {
|
|
return tc.TCIAudioStatus(), true
|
|
}
|
|
return TCIAudioStatus{}, false
|
|
}
|
|
|
|
// TCIAudioDo dispatches an audio command onto the CAT goroutine, like every
|
|
// other backend-specific control.
|
|
func (m *Manager) TCIAudioDo(fn func(TCIAudioController) error) error {
|
|
return m.exec(func(b Backend) error {
|
|
tc, ok := b.(TCIAudioController)
|
|
if !ok {
|
|
return fmt.Errorf("active CAT backend is not a TCI radio")
|
|
}
|
|
return fn(tc)
|
|
})
|
|
}
|
|
|
|
// TCIPanelController is the control console of a TCI radio — everything the
|
|
// panel reads and everything it sets.
|
|
//
|
|
// Listed one by one rather than accepted as *TCI, for the same reason the audio
|
|
// controller is: the manager hands out capabilities, not backends, and a
|
|
// station on OmniRig asking for the TCI console gets a sentence instead of a
|
|
// crash.
|
|
type TCIPanelController interface {
|
|
TCIPanel() TCIPanelState
|
|
SetDrive(v int) error
|
|
SetTuneDrive(v int) error
|
|
SetMicLevel(v int) error
|
|
SetVolume(db int) error
|
|
SetMute(on bool) error
|
|
SetAGC(mode string) error
|
|
SetSquelch(on bool) error
|
|
SetSquelchLevel(v int) error
|
|
SetNB(on bool) error
|
|
SetNR(on bool) error
|
|
SetANF(on bool) error
|
|
SetAPF(on bool) error
|
|
SetFilter(lo, hi int) error
|
|
SetRIT(on bool) error
|
|
SetXIT(on bool) error
|
|
SetRITOffset(hz int) error
|
|
SetXITOffset(hz int) error
|
|
SetLock(on bool) error
|
|
SetTune(on bool) error
|
|
}
|
|
|
|
// TCIPanelState returns the console snapshot, or (zero, false) when the active
|
|
// backend is not a TCI radio.
|
|
//
|
|
// Read WITHOUT going through the CAT goroutine: the state is a cached copy of
|
|
// what the radio pushed, guarded by its own lock, and the panel polls it several
|
|
// times a second. Queueing that behind whatever the poll loop is doing would put
|
|
// the console's smoothness at the mercy of a rig command's timeout.
|
|
func (m *Manager) TCIPanelState() (TCIPanelState, bool) {
|
|
m.mu.RLock()
|
|
b := m.backend
|
|
m.mu.RUnlock()
|
|
if tc, ok := b.(TCIPanelController); ok {
|
|
return tc.TCIPanel(), true
|
|
}
|
|
return TCIPanelState{}, false
|
|
}
|
|
|
|
// TCIPanelDo dispatches one console command onto the CAT goroutine, where every
|
|
// other write to the radio goes.
|
|
func (m *Manager) TCIPanelDo(fn func(TCIPanelController) error) error {
|
|
return m.exec(func(b Backend) error {
|
|
tc, ok := b.(TCIPanelController)
|
|
if !ok {
|
|
return fmt.Errorf("the active CAT backend is not a TCI radio")
|
|
}
|
|
return fn(tc)
|
|
})
|
|
}
|