fix(rotator): put the SP/LP readout inside the compass itself

It went under the Station Control rotator; the compass that wanted it is the
docked one beside the entry strip. Both are the same RotorCompass, so the
readout moves INTO that component: every compass in the app carries it, and no
caller draws its own.

The bezel already showed the short path as a red marker, but a marker is a
direction, not a number — and the numbers in the status bar are 10px, which is
what prompted this.

Clickable only when the caller passed onGoto, since a compass rendered without
one cannot turn anything and a button that does nothing is worse than a label.
This commit is contained in:
2026-08-13 11:45:49 +02:00
parent 700b688fc0
commit 784d809290
7 changed files with 194 additions and 49 deletions
+19 -17
View File
@@ -65,23 +65,25 @@ const (
// AutoUpload + UploadMode are common to all (timing is per-service, so the
// user can run e.g. Club Log immediate and QRZ delayed).
type ServiceConfig struct {
APIKey string `json:"api_key"`
URL string `json:"url"` // Cloudlog/Wavelog: base URL of the user's own instance
StationID string `json:"station_id"` // Cloudlog/Wavelog: station profile (location) id
Email string `json:"email"` // Club Log account email
Username string `json:"username"` // LoTW website login (for confirmation download)
Password string `json:"password"` // Club Log account / LoTW website password
Callsign string `json:"callsign"` // Club Log / HRDLog logbook (owner) callsign
Code string `json:"code"` // HRDLog: account upload code
QTHNickname string `json:"qth_nickname"` // eQSL: QTH nickname (when the account has several)
ForceStationCallsign string `json:"force_station_callsign"` // QRZ + LoTW: override STATION_CALLSIGN
TQSLPath string `json:"tqsl_path"` // LoTW: path to tqsl.exe
StationLocation string `json:"station_location"` // LoTW: TQSL Station Location name
KeyPassword string `json:"key_password"` // LoTW: certificate private-key password (optional)
UploadFlags []string `json:"upload_flags"` // LoTW: set of lotw_sent values that mean "ready to upload" — any of "N"/"R"
WriteLog bool `json:"write_log"` // LoTW: pass -t to write a TQSL diagnostic log
AutoUpload bool `json:"auto_upload"`
UploadMode UploadMode `json:"upload_mode"`
APIKey string `json:"api_key"`
URL string `json:"url"` // Cloudlog/Wavelog: base URL of the user's own instance
StationID string `json:"station_id"` // Cloudlog/Wavelog: station profile (location) id
Email string `json:"email"` // Club Log account email
Username string `json:"username"` // LoTW website login (for confirmation download)
Password string `json:"password"` // Club Log account / LoTW website password
Callsign string `json:"callsign"` // Club Log / HRDLog logbook (owner) callsign
Code string `json:"code"` // HRDLog: account upload code
QTHNickname string `json:"qth_nickname"` // eQSL: QTH nickname (when the account has several)
ForceStationCallsign string `json:"force_station_callsign"` // QRZ + LoTW: override STATION_CALLSIGN
TQSLPath string `json:"tqsl_path"` // LoTW: path to tqsl.exe
StationLocation string `json:"station_location"` // LoTW: TQSL Station Location name
KeyPassword string `json:"key_password"` // LoTW: certificate private-key password (optional)
UploadFlags []string `json:"upload_flags"` // LoTW: set of lotw_sent values that mean "ready to upload" — any of "N"/"R"
WriteLog bool `json:"write_log"` // LoTW: pass -t to write a TQSL diagnostic log
AutoUpload bool `json:"auto_upload"`
// OnAir: HRDLog only — publish the live frequency, mode and rig on the site.
OnAir bool `json:"on_air"`
UploadMode UploadMode `json:"upload_mode"`
}
// normalised returns the config with whitespace trimmed and a valid upload
+57
View File
@@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
@@ -143,3 +144,59 @@ func TestHRDLog(ctx context.Context, client *http.Client, cfg ServiceConfig) (st
}
return fmt.Sprintf("Credentials accepted — %s", callsign), nil
}
// hrdlogOnAirURL is HRDLog's live-status endpoint — what puts "F4BPO is on air
// 14,074,000 FM IC-7300" on the site's front page.
//
// Endpoint and field names taken from HRDLog's own library (github.com/iw1qlh/
// HRDLOG-net-library, HrdProtocol.SendOnAirAsync), not from guesswork: it posts
// Frequency in Hz, Mode, Radio, Callsign, Code and App.
//
// HTTPS where that library uses plain HTTP. The upload code is a credential and
// has no business crossing the network in clear; the sibling NewEntry endpoint
// on the same host already serves TLS.
const hrdlogOnAirURL = "https://robot.hrdlog.net/OnAir.aspx"
// SendHRDLogOnAir publishes the current frequency, mode and rig.
//
// Deliberately fire-and-forget in spirit: it is a status broadcast, so a
// failure is logged and never surfaced as an error the operator must clear —
// but the message is returned so the caller can log WHAT the site said rather
// than only that something went wrong.
func SendHRDLogOnAir(ctx context.Context, client *http.Client, callsign, code string, freqHz int64, mode, radio string) (string, error) {
callsign = strings.ToUpper(strings.TrimSpace(callsign))
code = strings.TrimSpace(code)
if callsign == "" || code == "" {
return "", fmt.Errorf("hrdlog: callsign and upload code required")
}
if freqHz <= 0 {
return "", fmt.Errorf("hrdlog: no frequency to announce")
}
form := url.Values{}
form.Set("Callsign", callsign)
form.Set("Code", code)
form.Set("App", hrdlogApp)
form.Set("Frequency", strconv.FormatInt(freqHz, 10))
form.Set("Mode", strings.TrimSpace(mode))
form.Set("Radio", strings.TrimSpace(radio))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, hrdlogOnAirURL, strings.NewReader(form.Encode()))
if err != nil {
return "", fmt.Errorf("hrdlog on-air: build request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if client == nil {
client = &http.Client{Timeout: 15 * time.Second}
}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("hrdlog on-air: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 32*1024))
msg := strings.TrimSpace(string(body))
if resp.StatusCode != http.StatusOK {
return msg, fmt.Errorf("hrdlog on-air: http %d", resp.StatusCode)
}
return msg, nil
}