"PTT via CAT does nothing on TCI." What OpsLog sends is right — trx:0,true; is the documented command and the same one the reference clients send — so the command was going out and the radio was discarding it. ExpertSDR announces transmit permission with TX_ENABLE: on connect, and again whenever the band changes, "in case transmitter permission was changed" (§4.3). While it is false the radio simply IGNORES trx. OpsLog never read that command, so there was nothing in the log, nothing on screen, and a dead key. Now the permission is tracked and SetPTT refuses out loud, naming where to look: the frequency must be inside a transmit band and TX enabled in ExpertSDR. The refusal travels the path that already exists — Manager.SetPTT to pttKey, which logs it and hands it to the UI. Silence is not a "no". A radio that never mentions TX_ENABLE — an older ExpertSDR, or another program speaking TCI — is not treated as refusing: we key and let it decide. Permission is also forgotten on connect, so a refusal remembered from a band since left cannot block PTT until a restart. This may not be the operator's own cause, and that is the other half of the change: if it is not, the log now settles it in one line. cat: TCI: → trx:0,true; present means the command left OpsLog and the radio ignored it for a reason it has not told us; absent means the fault is on this side.
81 lines
2.8 KiB
Go
81 lines
2.8 KiB
Go
//go:build windows
|
|
|
|
package cat
|
|
|
|
import "testing"
|
|
|
|
// feed pushes messages at the backend the way the radio would.
|
|
func feed(t *TCI, msgs ...string) {
|
|
for _, m := range msgs {
|
|
t.handle(m)
|
|
}
|
|
}
|
|
|
|
// "PTT via CAT does nothing on TCI."
|
|
//
|
|
// ExpertSDR announces transmit permission with TX_ENABLE — on connect, and
|
|
// again whenever the band changes "in case transmitter permission was changed"
|
|
// (§4.3 of the protocol document). When it is false the radio simply IGNORES
|
|
// trx. OpsLog sent the documented command, the radio discarded it, and nothing
|
|
// anywhere said why: the operator pressed a dead key.
|
|
func TestPTTIsRefusedOutLoudWhenTheRadioForbidsTransmitting(t *testing.T) {
|
|
tci := NewTCI("localhost", 40001, "FT8", false)
|
|
feed(tci, "tx_enable:0,false")
|
|
|
|
err := tci.SetPTT(true)
|
|
if err == nil {
|
|
t.Fatal("keying was accepted while the radio forbids transmitting — the operator gets no reason at all")
|
|
}
|
|
// The message has to name where to look; "PTT failed" sends nobody anywhere.
|
|
for _, want := range []string{"transmit", "ExpertSDR"} {
|
|
if !contains(err.Error(), want) {
|
|
t.Errorf("the refusal reads %q, which does not mention %q", err.Error(), want)
|
|
}
|
|
}
|
|
|
|
// Unkeying is never blocked. Whatever the radio thinks about permission, a
|
|
// request to STOP transmitting must always reach it.
|
|
if err := tci.SetPTT(false); err != nil && contains(err.Error(), "refusing") {
|
|
t.Errorf("unkeying was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// Permission comes back when the operator returns to a band they may use, and
|
|
// PTT has to come back with it — not stay blocked until OpsLog is restarted.
|
|
func TestPermissionGrantedAgainRestoresPTT(t *testing.T) {
|
|
tci := NewTCI("localhost", 40001, "FT8", false)
|
|
feed(tci, "tx_enable:0,false")
|
|
if err := tci.SetPTT(true); err == nil {
|
|
t.Fatal("keying was accepted while forbidden")
|
|
}
|
|
feed(tci, "tx_enable:0,true")
|
|
// No connection here, so the send fails — but it must fail as a TRANSPORT
|
|
// error, never as a refusal.
|
|
if err := tci.SetPTT(true); err != nil && contains(err.Error(), "refusing") {
|
|
t.Errorf("still refusing after permission was granted: %v", err)
|
|
}
|
|
}
|
|
|
|
// A radio that never mentions TX_ENABLE — an older ExpertSDR, or one of the
|
|
// other programs that speak TCI — must not be treated as refusing. Silence is
|
|
// not a "no": we key, and let the radio decide.
|
|
func TestSilenceAboutPermissionIsNotARefusal(t *testing.T) {
|
|
tci := NewTCI("localhost", 40001, "FT8", false)
|
|
if err := tci.SetPTT(true); err != nil && contains(err.Error(), "refusing") {
|
|
t.Errorf("a radio that never sent TX_ENABLE was treated as forbidding transmit: %v", err)
|
|
}
|
|
}
|
|
|
|
func contains(s, sub string) bool {
|
|
return len(sub) == 0 || (len(s) >= len(sub) && indexOf(s, sub) >= 0)
|
|
}
|
|
|
|
func indexOf(s, sub string) int {
|
|
for i := 0; i+len(sub) <= len(s); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|