chore: release v0.26.0
This commit is contained in:
@@ -85,7 +85,18 @@ func reusingListenConfig() net.ListenConfig {
|
||||
// of the list for the rest of the session. More than half a day apart is read as
|
||||
// the wrong side of midnight and moved.
|
||||
func decodeTime(msSinceMidnight uint32) time.Time {
|
||||
now := time.Now().UTC()
|
||||
return decodeTimeAt(msSinceMidnight, time.Now().UTC())
|
||||
}
|
||||
|
||||
// decodeTimeAt is decodeTime with the clock passed in.
|
||||
//
|
||||
// Split out so the midnight rule can be tested at midnight instead of at
|
||||
// whatever time the suite happens to run: the test used to read the real clock
|
||||
// and failed every afternoon, because a 23:59:58 stamp seen at 17:00 is hours in
|
||||
// the future and no rule can make it otherwise. A test that fails for half the
|
||||
// day teaches everyone to ignore the suite.
|
||||
func decodeTimeAt(msSinceMidnight uint32, now time.Time) time.Time {
|
||||
now = now.UTC()
|
||||
midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
|
||||
at := midnight.Add(time.Duration(msSinceMidnight) * time.Millisecond)
|
||||
switch {
|
||||
@@ -112,7 +123,7 @@ type Event struct {
|
||||
|
||||
// A WSJT-X Decode (heard station) to render on the panadapter.
|
||||
DecodeCall string // transmitting (DE) callsign
|
||||
DecodeGrid string // 4-char grid, CQ decodes only
|
||||
DecodeGrid string // 4-char grid: from a CQ, or a reply answering with its locator
|
||||
DecodeFreqHz int64 // RF frequency (dial + audio offset)
|
||||
DecodeSNR int // reported SNR (dB)
|
||||
DecodeCQ bool // the decode was a CQ
|
||||
@@ -134,6 +145,17 @@ type Event struct {
|
||||
DecodeAudioHz int64 // audio offset inside the passband
|
||||
DecodeMs uint32 // the decode's raw ms-since-midnight, as sent
|
||||
DecodeLowConf bool
|
||||
// DecodeModeRaw is the mode field EXACTLY as the Decode carried it — the
|
||||
// one-character marker ("~", "+"), not the resolved name in Mode.
|
||||
//
|
||||
// Both are needed and they are not interchangeable. Mode is what the log and
|
||||
// the status resolver want. This is what a Reply must echo: the receiving
|
||||
// application matches a Reply against its own decode list field for field,
|
||||
// and JTDX rejects one whose mode reads "FT8" where it decoded "~" — the
|
||||
// click is accepted, nothing is transmitted, and nothing is reported.
|
||||
DecodeModeRaw string
|
||||
// DecodeMsgRaw is the message as sent, untrimmed — see DecodeModeRaw.
|
||||
DecodeMsgRaw string
|
||||
// ProgramID is the sending application's own id ("WSJT-X", "MSHV", or
|
||||
// "WSJT-X - 2" for a second instance started with --rig-name). It is what
|
||||
// tells two receivers apart on one multicast group — and it is the address a
|
||||
@@ -145,7 +167,11 @@ type Event struct {
|
||||
// true while the carrier is actually up. From Status, so ~1 Hz.
|
||||
TxMessage string
|
||||
Transmitting bool
|
||||
DECall string // the operator's own call, as the digital app knows it
|
||||
// TxEnabled is the sender's Enable Tx toggle — see WSJTEvent.TxEnabled. The
|
||||
// watchdog clears it, which is the only reliable sign an exchange was
|
||||
// abandoned rather than merely paused between overs.
|
||||
TxEnabled bool
|
||||
DECall string // the operator's own call, as the digital app knows it
|
||||
|
||||
// ClearCall is set when a WSJT/JTDX/MSHV Status message reports an EMPTY DX
|
||||
// Call after previously reporting one — i.e. the operator cleared the call in
|
||||
@@ -162,7 +188,10 @@ type Event struct {
|
||||
|
||||
// Server is a single inbound UDP listener.
|
||||
type Server struct {
|
||||
cfg Config
|
||||
cfg Config
|
||||
// mgr is the owning Manager, so a listener can reach the outbound rows.
|
||||
// The relay is the only thing that needs it.
|
||||
mgr *Manager
|
||||
conn *net.UDPConn
|
||||
out chan<- Event
|
||||
stop chan struct{}
|
||||
@@ -230,10 +259,11 @@ func describePacket(pkt []byte) string {
|
||||
return fmt.Sprintf("%d bytes | text %q%s | hex %s%s", len(pkt), text.String(), more, strings.TrimSpace(hex.String()), more)
|
||||
}
|
||||
|
||||
func newServer(cfg Config, out chan<- Event) *Server {
|
||||
func newServer(cfg Config, out chan<- Event, mgr *Manager) *Server {
|
||||
return &Server{
|
||||
cfg: cfg,
|
||||
out: out,
|
||||
mgr: mgr,
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
@@ -399,6 +429,17 @@ func (s *Server) noteIgnoredADIF(pkt []byte) {
|
||||
}
|
||||
|
||||
func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
// Relay FIRST, and unconditionally: before parsing, and whatever the parse
|
||||
// then makes of it. A datagram this build cannot decode is still one the
|
||||
// application downstream may understand, and a relay that forwards only what
|
||||
// it understood drops exactly the fields it has yet to learn about.
|
||||
//
|
||||
// WSJT listeners only. The other inbound services are text protocols with no
|
||||
// second consumer to speak of, and relaying an ADIF record twice would log
|
||||
// the contact twice.
|
||||
if s.cfg.ServiceType == ServiceWSJT && s.mgr != nil {
|
||||
s.mgr.RelayInbound(pkt, s.cfg.Port)
|
||||
}
|
||||
ev := Event{ConfigID: s.cfg.ID, Service: s.cfg.ServiceType, Source: remote.String()}
|
||||
switch s.cfg.ServiceType {
|
||||
case ServiceWSJT:
|
||||
@@ -477,7 +518,9 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
ev.DecodeSNR = w.SNR
|
||||
ev.DecodeCQ = w.IsCQ
|
||||
ev.Mode = DecodeModeName(w.Mode, statusMode)
|
||||
ev.DecodeModeRaw = w.Mode
|
||||
ev.DecodeMsg = w.DecodeMsg
|
||||
ev.DecodeMsgRaw = w.DecodeMsgRaw
|
||||
ev.DecodeAt = decodeTime(w.DecodeMsSinceMidnight)
|
||||
ev.DecodeTRPeriod = tr
|
||||
ev.DecodeDial = dial
|
||||
@@ -677,6 +720,13 @@ type Manager struct {
|
||||
httpFailMu sync.Mutex
|
||||
httpFailAt map[int64]time.Time
|
||||
|
||||
// relayWarned keeps a relay row's complaint (a loop target, a dead
|
||||
// destination) to one line a session. Keyed by row id for the loop warning
|
||||
// and by -id-1 for the send failure, so one row can say each once.
|
||||
// Without this a busy band writes hundreds of identical lines a minute.
|
||||
relayWarnMu sync.Mutex
|
||||
relayWarned map[int64]bool
|
||||
|
||||
mu sync.Mutex
|
||||
inbound map[int64]*Server
|
||||
outbound []Config
|
||||
@@ -729,7 +779,7 @@ func (m *Manager) Reload(ctx context.Context) []string {
|
||||
m.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
srv := newServer(c, m.out)
|
||||
srv := newServer(c, m.out, m)
|
||||
if err := srv.start(); err != nil {
|
||||
applog.Printf("udp: start %q failed: %v", c.Name, err)
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", c.Name, err))
|
||||
|
||||
Reference in New Issue
Block a user