chore(tci): mark the transmit passes and count every frame type
The first transmit test came back with a log that said nothing, which is the one answer that cannot be read: either no transmit frames arrived, or they arrived and went unlogged. So each pass is now bounded by a line of its own, and every stream type is counted without limit. A pass that reports 'receive audio: 240, and nothing else' is a result — it says the radio sends no chrono unless something more is asked of it — where a log with no transmit lines was merely a silence. The forty-frame logging budget is also handed back to the transmit types on each pass, since it was always spent on receive audio long before anyone got round to keying. The start line says whether the receive stream is even open, because a radio with nothing streaming has no reason to send chrono, and that is the likeliest reason the first attempt saw nothing.
This commit is contained in:
@@ -444,7 +444,16 @@ func (t *TCI) handle(msg string) {
|
|||||||
}
|
}
|
||||||
case "trx":
|
case "trx":
|
||||||
if get(0) == "0" {
|
if get(0) == "0" {
|
||||||
|
was := t.tx
|
||||||
t.tx = get(1) == "true"
|
t.tx = get(1) == "true"
|
||||||
|
// Said out loud, every time. The transmit side of TCI can only be
|
||||||
|
// written from a log of a real transmission, and the first one came
|
||||||
|
// back without a single line to say whether the radio had even been
|
||||||
|
// keyed — which left the interesting question, why no transmit
|
||||||
|
// frames, indistinguishable from nobody having pressed anything.
|
||||||
|
if was != t.tx {
|
||||||
|
t.noteTXTransition(t.tx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "tx_enable":
|
case "tx_enable":
|
||||||
if get(0) == "0" {
|
if get(0) == "0" {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -82,6 +83,11 @@ type tciAudio struct {
|
|||||||
peak float64
|
peak float64
|
||||||
peakAt time.Time
|
peakAt time.Time
|
||||||
probeByType map[int]int
|
probeByType map[int]int
|
||||||
|
// countByType counts EVERY frame per stream type, capped by nothing.
|
||||||
|
// The probe above stops logging after forty frames of a type; these keep
|
||||||
|
// counting, so a transmission that produced no transmit frames at all can
|
||||||
|
// be reported as a fact rather than inferred from an absence of lines.
|
||||||
|
countByType map[int]int64
|
||||||
lastErr string
|
lastErr string
|
||||||
// widthLogged keeps the one-line note about the sample width to once a
|
// widthLogged keeps the one-line note about the sample width to once a
|
||||||
// session — it is a fact about the radio, not an event.
|
// session — it is a fact about the radio, not an event.
|
||||||
@@ -90,6 +96,9 @@ type tciAudio struct {
|
|||||||
// audio_stream_channels). Its own declaration, and it arrives before the
|
// audio_stream_channels). Its own declaration, and it arrives before the
|
||||||
// first frame — the frame arithmetic below stays as the check on it rather
|
// first frame — the frame arithmetic below stays as the check on it rather
|
||||||
// than as the only source.
|
// than as the only source.
|
||||||
|
// txMark is the per-type frame count when transmission began, so the census
|
||||||
|
// at the end reports the pass rather than the whole session.
|
||||||
|
txMark map[int]int64
|
||||||
declaredType string
|
declaredType string
|
||||||
declaredChans int
|
declaredChans int
|
||||||
|
|
||||||
@@ -192,6 +201,10 @@ func (t *TCI) handleBinary(data []byte) {
|
|||||||
if t.audio.probeByType == nil {
|
if t.audio.probeByType == nil {
|
||||||
t.audio.probeByType = map[int]int{}
|
t.audio.probeByType = map[int]int{}
|
||||||
}
|
}
|
||||||
|
if t.audio.countByType == nil {
|
||||||
|
t.audio.countByType = map[int]int64{}
|
||||||
|
}
|
||||||
|
t.audio.countByType[stype]++
|
||||||
probe := t.audio.probeByType[stype]
|
probe := t.audio.probeByType[stype]
|
||||||
if probe < tciAudioProbeMax {
|
if probe < tciAudioProbeMax {
|
||||||
t.audio.probeByType[stype]++
|
t.audio.probeByType[stype]++
|
||||||
@@ -333,3 +346,57 @@ func (t *TCI) resumeAudio() {
|
|||||||
// ignore the message type entirely and split every frame on ';', which would
|
// ignore the message type entirely and split every frame on ';', which would
|
||||||
// have fed audio bytes to the command parser the moment a stream was opened.
|
// have fed audio bytes to the command parser the moment a stream was opened.
|
||||||
func wsMessageIsBinary(mt int) bool { return mt == websocket.BinaryMessage }
|
func wsMessageIsBinary(mt int) bool { return mt == websocket.BinaryMessage }
|
||||||
|
|
||||||
|
// noteTXTransition reports what the stream did across a transmission.
|
||||||
|
//
|
||||||
|
// The voice keyer needs two numbers the documentation does not give: the size
|
||||||
|
// and the cadence of the frames the radio expects while transmitting. They can
|
||||||
|
// only be read off a real transmission — and the first attempt came back with a
|
||||||
|
// log that said nothing at all, which is ambiguous: either no transmit frames
|
||||||
|
// arrived, or they arrived and went unlogged.
|
||||||
|
//
|
||||||
|
// So the boundaries are marked and every stream type is counted. A pass that
|
||||||
|
// produces "type 1: 240, and nothing else" is a RESULT — it says the radio
|
||||||
|
// sends no chrono unless something more is asked of it — where a log with no
|
||||||
|
// transmit lines in it was merely a silence.
|
||||||
|
func (t *TCI) noteTXTransition(on bool) {
|
||||||
|
t.audio.mu.Lock()
|
||||||
|
if t.audio.countByType == nil {
|
||||||
|
t.audio.countByType = map[int]int64{}
|
||||||
|
}
|
||||||
|
if on {
|
||||||
|
// Let the transmit types speak again on every pass: forty frames is a
|
||||||
|
// budget spent long before the operator gets round to keying.
|
||||||
|
if t.audio.probeByType != nil {
|
||||||
|
delete(t.audio.probeByType, tciStreamTXAudio)
|
||||||
|
delete(t.audio.probeByType, tciStreamTXChrono)
|
||||||
|
}
|
||||||
|
t.audio.txMark = map[int]int64{}
|
||||||
|
for k, v := range t.audio.countByType {
|
||||||
|
t.audio.txMark[k] = v
|
||||||
|
}
|
||||||
|
streaming := t.audio.want
|
||||||
|
t.audio.mu.Unlock()
|
||||||
|
debugLog.Printf("TCI: TRANSMIT started — watching for transmit-audio (type %d) and chrono (type %d) frames; receive stream is %s",
|
||||||
|
tciStreamTXAudio, tciStreamTXChrono, map[bool]string{true: "open", false: "CLOSED (tick the TCI recording option, or the radio has no reason to stream)"}[streaming])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
names := map[int]string{
|
||||||
|
tciStreamIQ: "IQ",
|
||||||
|
tciStreamRXAudio: "receive audio",
|
||||||
|
tciStreamTXAudio: "transmit audio",
|
||||||
|
tciStreamTXChrono: "transmit chrono",
|
||||||
|
}
|
||||||
|
var parts []string
|
||||||
|
for _, k := range []int{tciStreamIQ, tciStreamRXAudio, tciStreamTXAudio, tciStreamTXChrono} {
|
||||||
|
if n := t.audio.countByType[k] - t.audio.txMark[k]; n > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%s (type %d): %d", names[k], k, n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.audio.mu.Unlock()
|
||||||
|
if len(parts) == 0 {
|
||||||
|
debugLog.Printf("TCI: TRANSMIT ended — NO binary frames of any type arrived during it")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debugLog.Printf("TCI: TRANSMIT ended — frames during the pass: %s", strings.Join(parts, ", "))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user