fix: added functionality to net control

This commit is contained in:
2026-06-24 19:09:34 +02:00
parent 8b831145ad
commit d6626d96d0
6 changed files with 205 additions and 207 deletions
+130 -104
View File
@@ -405,8 +405,9 @@ type App struct {
// session (in-memory only — active stations currently in QSO).
netStore *netctl.Store
netMu sync.Mutex
netOpenID string // id of the currently open net ("" = none)
netActive []*netActiveEntry // stations on the air right now, in check-in order
netOpenID string // id of the currently open net ("" = none)
netActive []*qso.QSO // on-air QSO drafts (transient negative ids), check-in order
netSeq int64 // transient-id counter for on-air drafts (decrements: -1, -2, …)
cwMu sync.Mutex // guards the CW decoder lifecycle
cwStop chan struct{} // stops the CW decoder capture loop; nil when off
@@ -4284,18 +4285,6 @@ func (a *App) RestartQSORecorder() { a.startQSORecorderIfEnabled() }
// the session. The session is RAM-only — closing the app mid-net drops any
// active stations that were never logged.
// netActiveEntry is one station currently on the air in the open net.
type netActiveEntry struct {
Callsign string `json:"callsign"`
Name string `json:"name"`
QTH string `json:"qth"`
Country string `json:"country"`
RSTSent string `json:"rst_sent"`
RSTRcvd string `json:"rst_rcvd"`
Comment string `json:"comment"`
TimeOn time.Time `json:"time_on"`
}
// NetList returns all nets (with rosters), ordered by name.
func (a *App) NetList() []netctl.Net {
if a.netStore == nil {
@@ -4410,97 +4399,26 @@ func (a *App) NetOpenID() string {
}
// NetActiveList returns the stations currently on the air, in check-in order.
func (a *App) NetActiveList() []netActiveEntry {
// Each is a full QSO *draft* (not yet in the DB) carrying a negative transient
// id so the same QSOEditModal as Recent QSOs can edit every field.
func (a *App) NetActiveList() []qso.QSO {
a.netMu.Lock()
defer a.netMu.Unlock()
out := make([]netActiveEntry, len(a.netActive))
out := make([]qso.QSO, len(a.netActive))
for i, e := range a.netActive {
out[i] = *e
}
return out
}
// NetActivate puts a station on the air (records time_on, seeds defaults from
// the net + roster). No-op if already active. The net must be open.
func (a *App) NetActivate(callsign string) (netActiveEntry, error) {
call := strings.ToUpper(strings.TrimSpace(callsign))
if call == "" {
return netActiveEntry{}, fmt.Errorf("callsign required")
}
a.netMu.Lock()
defer a.netMu.Unlock()
if a.netOpenID == "" {
return netActiveEntry{}, fmt.Errorf("no net open")
}
for _, e := range a.netActive {
if e.Callsign == call {
return *e, nil // already on the air
}
}
e := &netActiveEntry{Callsign: call, TimeOn: time.Now().UTC()}
if net, ok := a.netStore.Get(a.netOpenID); ok {
e.RSTSent, e.RSTRcvd, e.Comment = net.DefaultRSTSent, net.DefaultRSTRcvd, net.DefaultComment
for _, st := range net.Stations {
if strings.EqualFold(st.Callsign, call) {
e.Name, e.QTH, e.Country = st.Name, st.QTH, st.Country
break
}
}
}
if e.RSTSent == "" {
e.RSTSent = "59"
}
if e.RSTRcvd == "" {
e.RSTRcvd = "59"
}
a.netActive = append(a.netActive, e)
return *e, nil
}
// NetUpdateActive edits the live fields (report/QTH/name/comment) of a station
// already on the air. TimeOn is preserved.
func (a *App) NetUpdateActive(e netActiveEntry) error {
call := strings.ToUpper(strings.TrimSpace(e.Callsign))
a.netMu.Lock()
defer a.netMu.Unlock()
for _, cur := range a.netActive {
if cur.Callsign == call {
cur.Name, cur.QTH, cur.Country = e.Name, e.QTH, e.Country
cur.RSTSent, cur.RSTRcvd, cur.Comment = e.RSTSent, e.RSTRcvd, e.Comment
return nil
}
}
return fmt.Errorf("station not active")
}
// NetDeactivate ends a station's QSO: it logs the contact to the active logbook
// (live CAT freq/mode, time_on→now) and removes it from the session. Returns
// the new QSO id.
func (a *App) NetDeactivate(callsign string) (int64, error) {
call := strings.ToUpper(strings.TrimSpace(callsign))
a.netMu.Lock()
var entry *netActiveEntry
idx := -1
for i, e := range a.netActive {
if e.Callsign == call {
entry, idx = e, i
break
}
}
if entry == nil {
a.netMu.Unlock()
return 0, fmt.Errorf("station not active")
}
a.netActive = append(a.netActive[:idx], a.netActive[idx+1:]...)
a.netMu.Unlock()
// Frequency/mode come live from the rig; fall back to the last UI-reported
// values when CAT is off.
// netLiveFreq returns the rig's live freq/band/mode, falling back to the last
// UI-reported values when CAT is off.
func (a *App) netLiveFreq() (freq int64, band, mode string) {
var st cat.RigState
if a.cat != nil {
st = a.cat.State()
}
freq, band, mode := st.FreqHz, st.Band, st.Mode
freq, band, mode = st.FreqHz, st.Band, st.Mode
if freq == 0 {
a.liveActMu.Lock()
freq, band, mode = a.liveFreqHz, a.liveBand, a.liveMode
@@ -4509,22 +4427,130 @@ func (a *App) NetDeactivate(callsign string) (int64, error) {
if band == "" && freq > 0 {
band = bandForHz(freq)
}
q := qso.QSO{
Callsign: call,
QSODate: entry.TimeOn,
QSODateOff: time.Now().UTC(),
Band: band,
Mode: mode,
RSTSent: entry.RSTSent,
RSTRcvd: entry.RSTRcvd,
Name: entry.Name,
QTH: entry.QTH,
Comment: entry.Comment,
return
}
// NetActivate puts a station on the air: it builds a QSO draft (time_on now,
// live freq/mode, defaults + roster info) with a transient negative id and
// returns it. No-op (returns the existing draft) if already active.
func (a *App) NetActivate(callsign string) (qso.QSO, error) {
call := strings.ToUpper(strings.TrimSpace(callsign))
if call == "" {
return qso.QSO{}, fmt.Errorf("callsign required")
}
a.netMu.Lock()
defer a.netMu.Unlock()
if a.netOpenID == "" {
return qso.QSO{}, fmt.Errorf("no net open")
}
for _, e := range a.netActive {
if strings.EqualFold(e.Callsign, call) {
return *e, nil // already on the air
}
}
a.netSeq--
q := &qso.QSO{ID: a.netSeq, Callsign: call, QSODate: time.Now().UTC()}
if net, ok := a.netStore.Get(a.netOpenID); ok {
q.RSTSent, q.RSTRcvd, q.Comment = net.DefaultRSTSent, net.DefaultRSTRcvd, net.DefaultComment
for _, st := range net.Stations {
if strings.EqualFold(st.Callsign, call) {
q.Name, q.QTH, q.Country = st.Name, st.QTH, st.Country
if st.DXCC != 0 {
d := st.DXCC
q.DXCC = &d
}
if st.CQ != 0 {
c := st.CQ
q.CQZ = &c
}
if st.ITU != 0 {
i := st.ITU
q.ITUZ = &i
}
break
}
}
}
if q.RSTSent == "" {
q.RSTSent = "59"
}
if q.RSTRcvd == "" {
q.RSTRcvd = "59"
}
freq, band, mode := a.netLiveFreq()
q.Band, q.Mode = band, mode
if freq > 0 {
f := freq
q.FreqHz = &f
}
a.applyDXCCNumber(q) // fill country/dxcc/zones for display
a.refineDistrictZones(q)
a.netActive = append(a.netActive, q)
return *q, nil
}
// NetUpdateActive replaces an on-air QSO draft (matched by its transient id)
// with the edited version from the QSOEditModal. Lets the operator change every
// field of a station before it's logged.
func (a *App) NetUpdateActive(q qso.QSO) error {
a.netMu.Lock()
defer a.netMu.Unlock()
for i, cur := range a.netActive {
if cur.ID == q.ID {
qq := q
a.netActive[i] = &qq
return nil
}
}
return fmt.Errorf("station not active")
}
// NetDiscardActive removes an on-air draft (by transient id) WITHOUT logging it
// — i.e. cancel a station added by mistake (the modal's Delete button).
func (a *App) NetDiscardActive(id int64) error {
a.netMu.Lock()
defer a.netMu.Unlock()
for i, e := range a.netActive {
if e.ID == id {
a.netActive = append(a.netActive[:i], a.netActive[i+1:]...)
return nil
}
}
return nil
}
// NetDeactivate ends a station's QSO (by transient id): it logs the draft to the
// active logbook (time_off = now; freq/mode refreshed from the rig only if the
// draft still has none, so manual edits are respected) and removes it from the
// session. Returns the new QSO id.
func (a *App) NetDeactivate(id int64) (int64, error) {
a.netMu.Lock()
var draft *qso.QSO
idx := -1
for i, e := range a.netActive {
if e.ID == id {
draft, idx = e, i
break
}
}
if draft == nil {
a.netMu.Unlock()
return 0, fmt.Errorf("station not active")
}
a.netActive = append(a.netActive[:idx], a.netActive[idx+1:]...)
a.netMu.Unlock()
q := *draft
q.ID = 0 // transient id must not reach the DB (AddQSO inserts a fresh row)
q.QSODateOff = time.Now().UTC()
if q.FreqHz == nil && q.Band == "" {
freq, band, mode := a.netLiveFreq()
q.Band, q.Mode = band, mode
if freq > 0 {
f := freq
q.FreqHz = &f
}
}
return a.AddQSO(q)
}