fix: Ultrabeam was not synchronizing after click on a spot.
This commit is contained in:
@@ -7762,9 +7762,54 @@ func (a *App) SetCATFrequency(hz int64) error {
|
||||
if err != nil {
|
||||
applog.Printf("cat: SetFrequency(%d Hz) dispatch error: %v", hz, err)
|
||||
}
|
||||
// Re-tune the Ultrabeam right away on a DELIBERATE freq set (spot click, band
|
||||
// button, memory recall) instead of waiting for the ~1.5 s follow poll + the
|
||||
// CAT echo — the reason it used to only follow after you nudged the VFO.
|
||||
go a.ultrabeamFollowNow(hz)
|
||||
return err
|
||||
}
|
||||
|
||||
// ultrabeamFollowNow re-tunes the Ultrabeam to freqHz at once (best-effort),
|
||||
// honouring the same enabled/follow/in-range/step-deadband rules as the follow
|
||||
// loop. Called from SetCATFrequency so a spot click moves the antenna instantly.
|
||||
func (a *App) ultrabeamFollowNow(freqHz int64) {
|
||||
c := a.ultrabeam
|
||||
if c == nil || freqHz <= 0 {
|
||||
return
|
||||
}
|
||||
s, err := a.GetUltrabeamSettings()
|
||||
if err != nil || !s.Enabled || !s.Follow {
|
||||
return
|
||||
}
|
||||
step := s.StepKHz
|
||||
if step <= 0 {
|
||||
step = 50
|
||||
}
|
||||
st, err := c.GetStatus()
|
||||
if err != nil || st == nil || !st.Connected {
|
||||
return
|
||||
}
|
||||
if st.FreqMin > 0 && st.FreqMax > 0 {
|
||||
mhz := freqHz / 1_000_000
|
||||
if mhz < int64(st.FreqMin) || mhz > int64(st.FreqMax) {
|
||||
return // outside the antenna's tunable range
|
||||
}
|
||||
}
|
||||
khz := int(freqHz / 1000)
|
||||
diff := khz - st.Frequency
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
if st.Frequency > 0 && diff < step {
|
||||
return // within the deadband — don't chase a tiny QSY
|
||||
}
|
||||
if err := c.SetFrequency(khz, st.Direction); err != nil {
|
||||
applog.Printf("ultrabeam: immediate re-tune to %d kHz failed: %v", khz, err)
|
||||
} else {
|
||||
applog.Printf("ultrabeam: re-tuned on freq set → %d kHz (dir %d)", khz, st.Direction)
|
||||
}
|
||||
}
|
||||
|
||||
// SetCATMode sets the rig's mode. ADIF mode names (SSB / CW / FT8 / …) are
|
||||
// translated to backend-specific values by the backend itself.
|
||||
func (a *App) SetCATMode(mode string) error {
|
||||
|
||||
@@ -292,6 +292,7 @@ const QSO_FIELD_WEIGHT: Record<string, number> = {
|
||||
|
||||
// QSOBoxView renders the confirmation box with per-QSO values.
|
||||
function QSOBoxView({ box, values }: { box: QSOBox; values: Record<string, string> }) {
|
||||
const fg = box.fg || '#14243a'; // text colour (field labels use it at 0.6 opacity)
|
||||
const avail = box.w - 56;
|
||||
const total = box.fields.reduce((s, f) => s + (QSO_FIELD_WEIGHT[f] ?? 1), 0) || 1;
|
||||
let cursor = 28;
|
||||
@@ -304,24 +305,24 @@ function QSOBoxView({ box, values }: { box: QSOBox; values: Record<string, strin
|
||||
return (
|
||||
<>
|
||||
<rect width={box.w} height={box.h} rx={box.radius} fill={box.bg} opacity={box.bg_opacity} />
|
||||
<text x={28} y={22} fontSize={34} fontWeight={700} fill="#1b2a3d"
|
||||
<text x={28} y={22} fontSize={34} fontWeight={700} fill={fg}
|
||||
fontFamily="'Segoe UI', Arial, sans-serif" dominantBaseline="text-before-edge">
|
||||
{box.title}
|
||||
</text>
|
||||
{cols.map(({ f, x }) => (
|
||||
<g key={f} transform={`translate(${Math.round(x)} ${box.h * 0.42})`}>
|
||||
<text fontSize={19} fill="#6b7a8c" letterSpacing={1.5}
|
||||
<text fontSize={19} fill={fg} opacity={0.6} letterSpacing={1.5}
|
||||
fontFamily="'Segoe UI', Arial, sans-serif" dominantBaseline="text-before-edge">
|
||||
{(QSO_FIELD_LABELS[f] ?? f).toUpperCase()}
|
||||
</text>
|
||||
<text y={26} fontSize={28} fontWeight={700} fill="#14243a"
|
||||
<text y={26} fontSize={28} fontWeight={700} fill={fg}
|
||||
fontFamily="'Segoe UI', Arial, sans-serif" dominantBaseline="text-before-edge">
|
||||
{values[f] ?? ''}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
{box.footer && (
|
||||
<text x={28} y={box.h - 18} fontSize={24} fontStyle="italic" fill="#3c4d63"
|
||||
<text x={28} y={box.h - 18} fontSize={24} fontStyle="italic" fill={fg} opacity={0.85}
|
||||
fontFamily="'Segoe UI', Arial, sans-serif">
|
||||
{box.footer}
|
||||
</text>
|
||||
|
||||
@@ -182,6 +182,16 @@ export function EditorPanel({ template, sel, presets, fontFamilies, onPatchEleme
|
||||
onChange={(ev) => onPatchBox({ bg: ev.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Label className="text-xs text-muted-foreground">Text color</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="color" className="h-7 w-9 cursor-pointer rounded border border-border bg-transparent p-0"
|
||||
value={/^#[0-9a-fA-F]{6}$/.test(box.fg ?? '') ? (box.fg as string) : '#14243a'}
|
||||
onChange={(ev) => onPatchBox({ fg: ev.target.value })} />
|
||||
<Input className="h-7 w-32 font-mono text-xs" value={box.fg ?? ''} placeholder="#14243a"
|
||||
onChange={(ev) => onPatchBox({ fg: ev.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<NumberField label="Corner radius" value={box.radius ?? 0} min={0} max={80}
|
||||
onChange={(radius) => onPatchBox({ radius })} />
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
|
||||
@@ -118,6 +118,7 @@ export interface QSOBox {
|
||||
h: number;
|
||||
bg: string;
|
||||
bg_opacity: number;
|
||||
fg?: string; // text colour (default dark); set it when using a dark background
|
||||
radius: number;
|
||||
title: string;
|
||||
fields: string[];
|
||||
|
||||
@@ -194,6 +194,7 @@ type QSOBox struct {
|
||||
H float64 `json:"h"`
|
||||
BG string `json:"bg"`
|
||||
BGOpacity float64 `json:"bg_opacity"`
|
||||
FG string `json:"fg,omitempty"` // text colour (default dark)
|
||||
Radius float64 `json:"radius"`
|
||||
Title string `json:"title"`
|
||||
Fields []string `json:"fields"`
|
||||
|
||||
Reference in New Issue
Block a user