feat(ftmap): a layer for who hears me
The map drew one direction of every path on it: what this receiver decoded. The reverse — which stations are reporting our own transmissions — is the half an operator cannot see from their own radio, and on FT8 it is the half that decides whether calling is worth the cycle. It is the narrowest slice of the PSK Reporter feed there is. The v2 topic is pskr/filter/v2/<band>/<mode>/<tx call>/<rx call>/… so putting the operator's callsign in the TRANSMIT level makes the broker send nothing else. For scale, from internal/pskr's own measured numbers: four bands unfiltered is 83 messages a second, filtered on the receiver's square 0.2 to 1.2 a second — one callsign in the transmit level is a handful per FT8 cycle however open the band is. Both grids are in the payload, so the arc is arithmetic and there is no lookup. internal/pskrme, with its own connection, for the same reason internal/pskrtgt has its own: the three want slices of the feed that cannot be filtered out of one another. It also means this keeps working with the band-opening watch off — hanging it off that feed's lifecycle would have made it fail silently for anyone not chasing openings. Nothing is persisted. One entry per STATION inside a fifteen-minute window, carrying its freshest report: PSK Reporter's uploaders batch, many every five minutes, so a tighter window would show a fraction of who actually heard the last few calls. Stop clears the window, or switching the layer back on would redraw who heard us before it was on. On the map the layer is dashed and single-coloured. Solid is what we decoded, dashed is somebody decoding us; colour alone could not carry that distinction next to fourteen band colours. The receivers are rings rather than filled dots for the same reason. Per profile, since the callsign IS the subscription — a switch resubscribes rather than going on reporting who hears the previous station.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
// ── Who hears me (PSK Reporter) ────────────────────────────────────────────
|
||||
//
|
||||
// The FT map draws what this station decodes. This is the other direction:
|
||||
// which stations are reporting our own transmissions, which is the half an
|
||||
// operator cannot see from their own receiver and the half that decides whether
|
||||
// calling is worth the cycle.
|
||||
//
|
||||
// See internal/pskrme for why it costs almost nothing — the operator's callsign
|
||||
// goes in the topic's TRANSMIT level, so the broker sends nothing else.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"hamlog/internal/applog"
|
||||
"hamlog/internal/pskrme"
|
||||
)
|
||||
|
||||
// keyHearMeOn is per-profile, not global: a second profile is usually a second
|
||||
// callsign, and the answer to "who hears me" is not the same one.
|
||||
const keyHearMeOn = "hearme.on"
|
||||
|
||||
// GetHearMe reports whether the feed is wanted.
|
||||
func (a *App) GetHearMe() bool {
|
||||
if a.settings == nil || !a.settingsScoped.Load() {
|
||||
return false
|
||||
}
|
||||
v, _ := a.settings.Get(a.ctx, keyHearMeOn)
|
||||
return v == "1"
|
||||
}
|
||||
|
||||
// SetHearMe turns the feed on or off and brings the subscription with it.
|
||||
func (a *App) SetHearMe(on bool) error {
|
||||
if a.settings == nil {
|
||||
return fmt.Errorf("db not initialized")
|
||||
}
|
||||
if err := a.settings.Set(a.ctx, keyHearMeOn, boolStr(on)); err != nil {
|
||||
return err
|
||||
}
|
||||
a.startHearMe()
|
||||
if on {
|
||||
// Not an error: the feed is wanted and will come up as soon as there is
|
||||
// a callsign to watch for, and saying so beats a silent switch.
|
||||
if strings.TrimSpace(a.opCall) == "" {
|
||||
return fmt.Errorf("set the station callsign first — the feed watches for it")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// startHearMe rebuilds the watcher from the setting. Called at startup, when
|
||||
// the setting changes, and on a profile switch — the callsign is what it
|
||||
// subscribes to, so a new profile needs a new subscription.
|
||||
func (a *App) startHearMe() {
|
||||
if a.hearMe != nil {
|
||||
a.hearMe.Stop()
|
||||
a.hearMe = nil
|
||||
}
|
||||
if !a.GetHearMe() {
|
||||
return
|
||||
}
|
||||
call := strings.ToUpper(strings.TrimSpace(a.opCall))
|
||||
if call == "" {
|
||||
applog.Printf("pskrme: no station callsign — nothing to watch for")
|
||||
return
|
||||
}
|
||||
w := pskrme.New(pskrme.Config{MyCall: call, Logf: applog.Printf})
|
||||
if err := w.Start(); err != nil {
|
||||
applog.Printf("pskrme: feed did not start: %v", err)
|
||||
return
|
||||
}
|
||||
a.hearMe = w
|
||||
}
|
||||
|
||||
// GetWhoHearsMe is the map layer: one entry per station that has reported us
|
||||
// inside the window, carrying its own square so the arc is arithmetic.
|
||||
func (a *App) GetWhoHearsMe() []pskrme.Report {
|
||||
if a.hearMe == nil {
|
||||
return nil
|
||||
}
|
||||
return a.hearMe.Reports()
|
||||
}
|
||||
|
||||
// GetHearMeStatus tells a working feed from a silent one: a connection that is
|
||||
// up and reporting nothing looks exactly like a broken one until you can see a
|
||||
// number moving.
|
||||
func (a *App) GetHearMeStatus() pskrme.Status {
|
||||
if a.hearMe == nil {
|
||||
return pskrme.Status{Watching: strings.ToUpper(strings.TrimSpace(a.opCall))}
|
||||
}
|
||||
return a.hearMe.Status()
|
||||
}
|
||||
Reference in New Issue
Block a user