feat: panadapter spots that read, a CI-V link that survives, no auto-call
Panadapter spots are now worth reading. The comment carries the spotter, the
entity and the status in DXHunter's own shape — "CQ up 2 [F4BPO] [Franz Josef
Land] [New Slot]" — which needed two things nothing documents: SmartSDR splits
its command line on SPACES, so the words ran together until every space became
non-breaking; and it truncates past ~60 characters, so the cluster's own words
are trimmed first and the three brackets always survive. RBN column padding is
collapsed on the way in, or a preserved run of spaces opened a gap wide enough
to push the rest off screen.
"Already worked" means the CALLSIGN is in the log, not the entity: saying it of
a station never contacted was simply wrong. Each status can also be kept off the
panadapter entirely, and the WSJT-X decode spots obey the same switches — the
palette governs the panadapter, not one of the two things that feed it.
And the radio is no longer hammered: a spot whose frequency, colour and comment
are unchanged is not removed and redrawn. A busy skimmer feed re-spots the same
station every few seconds; one two-minute session sent 2128 adds, 88 of them for
a single callsign, and the display did not move a pixel for any of them.
CI-V, from an IC-7850 that kept killing JTDX: a reply the rig sent to another
controller on the same bus is no longer taken for ours, and a set_ptt, set_freq
or set_mode whose acknowledgement goes missing is verified by reading the rig
back instead of being reported as a failure. WSJT-X and JTDX answer a failed
command with a Rig Control Error and drop the link mid-over — 98 keyings, 6 lost
acknowledgements, 2 dropped connections in one session. The check waits 700 ms,
not the poll's 150: the rig has just failed to answer twice because it was
retuning, and a short probe would fail for the same reason.
Auto-call is withdrawn — it duplicated DXHunter, which already answers decodes,
and two programs deciding that from one shack key over each other. The library
is kept whole and dormant; a guard in App.tsx makes sure a stored preference
cannot key a transmitter whose switch no longer exists.
Also:
- the log rotates while running, not only at startup: the CI-V trace left on
wrote 416 MB and nothing would have stopped it before the disk did. Closing
it now releases the crash file too — the runtime keeps its own duplicate.
- the interface zoom announces itself, with a badge, a click back to 100% and
a View menu; Ctrl+wheel and Ctrl+0 always worked and nothing said so.
- no more elastic bounce, and no swipe-to-navigate out of the app.
- Edit QSO: your own TX power and the contacted station's extended locator
were saved and written back with no box to set them.
- FT decodes: continents are a multiple choice; a compound MSHV message that
answers two stations in one line is recognised as addressed to you.
This commit is contained in:
@@ -8708,15 +8708,20 @@ func (a *App) clusterEventWorker() {
|
||||
// grid uses, so a spot is never one colour on screen and another on
|
||||
// the radio — and the index is cached, so this costs a map lookup.
|
||||
status := a.spotStatusName(s.DXCall, s.Band, mode, s.POTARef)
|
||||
col := a.spotColorFor(status)
|
||||
a.cat.SendSpot(cat.SpotInfo{
|
||||
FreqHz: s.FreqHz,
|
||||
Callsign: s.DXCall,
|
||||
Mode: mode,
|
||||
Comment: spotComment(s.Comment, status),
|
||||
Color: col.Text,
|
||||
BackgroundColor: col.Bg,
|
||||
})
|
||||
// A status the operator has switched off never reaches the radio. A
|
||||
// skipped SEND rather than a transparent colour: a spot that is not
|
||||
// drawn should not occupy one of the panadapter's slots either.
|
||||
if !a.spotHidden(status) {
|
||||
col := a.spotColorFor(status)
|
||||
a.cat.SendSpot(cat.SpotInfo{
|
||||
FreqHz: s.FreqHz,
|
||||
Callsign: s.DXCall,
|
||||
Mode: mode,
|
||||
Comment: spotComment(s.Comment, s.Spotter, s.Country, status),
|
||||
Color: col.Text,
|
||||
BackgroundColor: col.Bg,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13144,6 +13149,15 @@ func (a *App) LogUDPLoggedADIF(adifText string) (int64, error) {
|
||||
// (same reasoning as the manual path — award_refs on a busy shared MySQL blocked).
|
||||
if a.ctx != nil {
|
||||
wruntime.EventsEmit(a.ctx, "qso:logged", id)
|
||||
// A contact logged by WSJT-X / JTDX / MSHV is the one the operator has no
|
||||
// other confirmation of: the entry form did not clear, because they never
|
||||
// typed in it, and on the FT decodes tab the log grid is not even on
|
||||
// screen. Its own event, carrying enough to name the station — qso:logged
|
||||
// is an id, and fires twice per contact, so it can drive a refresh but not
|
||||
// a message.
|
||||
wruntime.EventsEmit(a.ctx, "qso:logged_external", map[string]any{
|
||||
"call": q.Callsign, "band": q.Band, "mode": q.Mode, "country": q.Country,
|
||||
})
|
||||
}
|
||||
qc := q
|
||||
go func() {
|
||||
@@ -13313,19 +13327,37 @@ func (a *App) consumeUDPEvents() {
|
||||
if len(lastDecodeSpot) > 4000 {
|
||||
lastDecodeSpot = map[string]time.Time{} // bound memory on long sessions
|
||||
}
|
||||
lastDecodeSpot[key] = now
|
||||
secs := a.catFlexDecodeSecs
|
||||
if secs <= 0 {
|
||||
secs = 120
|
||||
}
|
||||
// The palette governs the PANADAPTER, not one of the two things
|
||||
// that feed it. A decode spot is a callsign on the waterfall
|
||||
// exactly as a cluster spot is, so it answers to the same
|
||||
// per-status switch and the same colours — an operator who turned
|
||||
// "entity not resolved" off and still watched unresolved calls
|
||||
// pile up on the radio was right to call that a bug.
|
||||
status := a.spotStatusName(ev.DecodeCall, bandForHz(ev.DecodeFreqHz), ev.Mode, "")
|
||||
if a.spotHidden(status) {
|
||||
continue
|
||||
}
|
||||
lastDecodeSpot[key] = now
|
||||
// Green when the palette is off — the colour this feature has always
|
||||
// used, and what still tells a decode from a cluster spot.
|
||||
col := a.spotColorFor(status)
|
||||
textCol, bgCol := col.Text, col.Bg
|
||||
if textCol == "" {
|
||||
textCol, bgCol = "#FF34C759", ""
|
||||
}
|
||||
select {
|
||||
case spotQ <- cat.SpotInfo{
|
||||
FreqHz: ev.DecodeFreqHz,
|
||||
Callsign: ev.DecodeCall,
|
||||
Mode: ev.Mode,
|
||||
Color: "#FF34C759", // green — distinct from cluster orange
|
||||
Comment: fmt.Sprintf("%s %+ddB", ev.Mode, ev.DecodeSNR),
|
||||
LifetimeSec: secs,
|
||||
FreqHz: ev.DecodeFreqHz,
|
||||
Callsign: ev.DecodeCall,
|
||||
Mode: ev.Mode,
|
||||
Color: textCol,
|
||||
BackgroundColor: bgCol,
|
||||
Comment: spotComment(fmt.Sprintf("%s %+ddB", ev.Mode, ev.DecodeSNR), "", a.countryFor(ev.DecodeCall), status),
|
||||
LifetimeSec: secs,
|
||||
}:
|
||||
default:
|
||||
// Radio not keeping up: skip the spot rather than stall the
|
||||
@@ -19439,7 +19471,12 @@ func (a *App) spotStatusName(call, band, mode, potaRef string) string {
|
||||
case r.NewPfx:
|
||||
return "new-pfx"
|
||||
}
|
||||
if r.Status == "worked" {
|
||||
// WORKED means this CALLSIGN is in the log — the question an operator asks
|
||||
// of a spot ("have I had this station?"), and the one DXHunter answers.
|
||||
// The entity-level statuses above already say what the ENTITY is worth; an
|
||||
// entity worked here whose callsign is new is not "worked", it is simply no
|
||||
// news, which is what "none" means.
|
||||
if r.WorkedCall {
|
||||
return "worked"
|
||||
}
|
||||
return "none"
|
||||
@@ -20177,3 +20214,16 @@ func (a *App) tidyLogbookSchema(logbookConn *sql.DB) {
|
||||
// if this one is ever asked to serve as the logbook again.
|
||||
db.DropEmptyQSOTable(a.db, filepath.Base(a.dbPath))
|
||||
}
|
||||
|
||||
// countryFor names the DXCC entity of a callsign, or "" when it cannot be
|
||||
// resolved. Used to enrich a panadapter spot built from a WSJT-X decode, which
|
||||
// arrives as a bare callsign.
|
||||
func (a *App) countryFor(call string) string {
|
||||
if a.dxcc == nil || strings.TrimSpace(call) == "" {
|
||||
return ""
|
||||
}
|
||||
if m, ok := a.dxcc.Lookup(call); ok && m.Entity != nil {
|
||||
return m.Entity.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user