Append an optional station log and announce the software after login.

A log path that cannot be written leaves the station on the reflector, and the reflector learns which program joined.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Konrad Neitzel
2026-10-03 19:24:00 +02:00
co-authored by Cursor
parent 6233292931
commit ec5f660fdd
12 changed files with 848 additions and 43 deletions
+34 -16
View File
@@ -20,6 +20,7 @@ var errStopped = errors.New("session stopped")
// Run keeps the reflector session until ctx is canceled.
func (s *Station) Run(ctx context.Context) error {
defer s.Close()
for {
if ctx.Err() != nil {
return nil
@@ -41,8 +42,7 @@ func (s *Station) Run(ctx context.Context) error {
return nil
}
s.mu.Lock()
s.state.Connection = "Disconnected"
s.publish()
s.noteDownLocked(s.clock.Now(), true)
s.mu.Unlock()
if !s.wait(ctx, 2*time.Second) {
return nil
@@ -54,14 +54,13 @@ func (s *Station) Run(ctx context.Context) error {
conn.Close()
s.mu.Lock()
s.closeUDP()
if s.fatal == "" {
s.state.Connection = "Disconnected"
s.remoteTalker = ""
s.roomBusy = false
}
s.mic = nil
s.play = nil
s.publish()
if s.fatal == "" {
s.noteDownLocked(s.clock.Now(), false)
} else {
s.publish()
}
fatal := s.fatal != ""
s.mu.Unlock()
if ctx.Err() != nil {
@@ -289,7 +288,7 @@ func (s *Station) onTCP(conn net.Conn, f frame, done <-chan struct{}, udpCh *cha
if err != nil {
msg = "could not be read"
}
s.stop(msg)
s.endFatal(msg, true)
return errStopped
case tcpServerInfo:
return s.onServerInfo(conn, f.body, done, udpCh, udpErr, udpReady, udpRx, lastTCP)
@@ -310,7 +309,7 @@ func (s *Station) onServerInfo(conn net.Conn, body []byte, done <-chan struct{},
return err
}
if !hasOPUS(codecs) {
s.stop(noOpus)
s.endFatal(noOpus, true)
return errStopped
}
enc, err := opus.NewEncoder()
@@ -322,6 +321,17 @@ func (s *Station) onServerInfo(conn net.Conn, body []byte, done <-chan struct{},
enc.Close()
return err
}
info, err := softwareIdentityBody()
if err != nil {
enc.Close()
dec.Close()
return err
}
if err := writeFrame(conn, tcpNodeInfo, info); err != nil {
enc.Close()
dec.Close()
return err
}
var tg [4]byte
binary.BigEndian.PutUint32(tg[:], uint32(s.cfg.talkGroup))
if err := writeFrame(conn, tcpSelectTG, tg[:]); err != nil {
@@ -362,6 +372,10 @@ func (s *Station) onServerInfo(conn net.Conn, body []byte, done <-chan struct{},
return err
}
s.state.Connection = "Connected"
s.up = true
s.outage = false
s.noteTransmitLocked()
s.writeLog(s.clock.Now(), "connected")
s.publish()
s.mu.Unlock()
@@ -372,12 +386,7 @@ func (s *Station) onServerInfo(conn net.Conn, body []byte, done <-chan struct{},
return nil
}
func (s *Station) stop(msg string) {
s.mu.Lock()
defer s.mu.Unlock()
s.fatal = msg
s.publish()
}
func (s *Station) stop(msg string) { s.endFatal(msg, false) }
// Fail stops retries and shows message until the operator quits.
func (s *Station) Fail(message string) { s.stop(message) }
@@ -393,19 +402,24 @@ func (s *Station) onTalker(body []byte, start bool) {
}
s.mu.Lock()
defer s.mu.Unlock()
now := s.clock.Now()
if start {
if call == s.cfg.callsign {
return
}
s.closeTalkerLocked(now)
was := s.transmitting()
s.remoteTalker = call
s.roomBusy = true
s.talkCall = call
s.talkStart = now
if was && !s.transmitting() {
s.endTransmit()
}
s.publish()
return
}
s.closeTalkerLocked(now)
s.remoteTalker = ""
s.roomBusy = false
s.publish()
@@ -500,6 +514,7 @@ func (s *Station) transmitting() bool {
}
func (s *Station) publish() {
s.noteTransmitLocked()
s.state.Callsign = s.cfg.callsign
s.state.TalkGroup = s.cfg.talkGroup
if s.fatal != "" {
@@ -517,6 +532,9 @@ func (s *Station) publish() {
} else {
s.state.Talker = s.remoteTalker
}
if s.state.Talker == "" && s.logNotice != "" {
s.state.Detail = s.logNotice
}
}
select {
case s.wake <- struct{}{}: