feat(motor antenna): amber at the order, not at the answer
The transmit gag starts when the move is commanded; the indicator waited for a status poll to say the elements were travelling, so the two disagreed by a second or more — and on an automatic follow (band change with tracking on) there was no sign at all until a poll landed. The backend now announces the order as it goes out, the interface shows it at once and re-reads the antenna immediately, and the real status takes over the moment it arrives. The WSJT-X "grey out stations already worked" switch is renamed "Mark stations already worked": with the colour now chosen by the operator the old name described the wrong thing, but the switch still answers a question the palette cannot — WHETHER dupes should be marked at all. On a well-filled log they are most of a period, and a window where nearly every line is coloured has stopped saying anything.
This commit is contained in:
+28
-7
@@ -3100,6 +3100,12 @@ export default function App() {
|
||||
const pokeUbStatus = useCallback(async () => {
|
||||
try { const s: any = await GetUltrabeamStatus(); if (s) setUbStatus(s); } catch { /* transient */ }
|
||||
}, []);
|
||||
// A move was just ORDERED — from a button here or by the follow loop on a
|
||||
// band change. Held for as long as it takes the antenna to answer, and no
|
||||
// longer: the real status takes over the moment it arrives.
|
||||
const [motorCmdAt, setMotorCmdAt] = useState(0);
|
||||
const motorMoving = !!ubStatus.moving || (motorCmdAt > 0 && Date.now() - motorCmdAt < 2500);
|
||||
|
||||
// Three seconds while it sits still, half a second while it moves.
|
||||
//
|
||||
// The moving flag gags the transmitter, and this poll is the last of three
|
||||
@@ -3113,9 +3119,24 @@ export default function App() {
|
||||
try { const s: any = await GetUltrabeamStatus(); if (alive) setUbStatus(s); } catch {}
|
||||
};
|
||||
tick();
|
||||
const id = window.setInterval(tick, ubStatus.moving ? 500 : 3000);
|
||||
const id = window.setInterval(tick, motorMoving ? 400 : 3000);
|
||||
return () => { alive = false; window.clearInterval(id); };
|
||||
}, [ubStatus.moving]);
|
||||
}, [motorMoving]);
|
||||
|
||||
// The backend says it at the instant the order goes out — see
|
||||
// noteMotorMoveCommanded. Read at once as well, so the antenna's own answer
|
||||
// replaces the assumption as soon as there is one.
|
||||
useEffect(() => {
|
||||
const off = EventsOn('motorant:move', () => {
|
||||
setMotorCmdAt(Date.now());
|
||||
pokeUbStatus();
|
||||
window.setTimeout(pokeUbStatus, 350);
|
||||
// And once the window is over, so "assumed moving" stops being assumed
|
||||
// even if nothing else re-renders.
|
||||
window.setTimeout(() => setMotorCmdAt((v) => (Date.now() - v >= 2500 ? 0 : v)), 2600);
|
||||
});
|
||||
return () => { off?.(); };
|
||||
}, [pokeUbStatus]);
|
||||
|
||||
// Poll the Antenna Genius switch for active antenna per port + the list.
|
||||
// Re-read the enabled flag each tick so toggling it in Settings makes the
|
||||
@@ -6859,9 +6880,9 @@ export default function App() {
|
||||
{/* Motorized-antenna pattern (Normal / 180° reverse / Bidirectional), next to the azimuth. */}
|
||||
{ubStatus.enabled && (
|
||||
<div className="inline-flex items-center rounded-full border border-success-border bg-success-muted overflow-hidden text-[10px] font-semibold ml-1"
|
||||
title={ubStatus.connected ? (ubStatus.moving ? 'Antenna: moving…' : 'Antenna pattern') : 'Antenna: connecting…'}>
|
||||
title={ubStatus.connected ? (motorMoving ? 'Antenna: moving…' : 'Antenna pattern') : 'Antenna: connecting…'}>
|
||||
<button type="button" className="pl-1.5 pr-0.5 flex items-center" onClick={() => { setSettingsSection('antenna'); setShowSettings(true); }} title="Antenna settings">
|
||||
<span className={cn('size-2 rounded-full', ubStatus.connected ? (ubStatus.moving ? 'bg-warning' : 'bg-success') : 'bg-muted-foreground/40')} />
|
||||
<span className={cn('size-2 rounded-full', ubStatus.connected ? (motorMoving ? 'bg-warning' : 'bg-success') : 'bg-muted-foreground/40')} />
|
||||
</button>
|
||||
{([{ d: 0, l: 'N', t: 'Normal' }, { d: 1, l: '180°', t: 'Reverse (180°)' }, { d: 2, l: 'Bi', t: 'Bidirectional' }]).map((o) => (
|
||||
<button key={o.d} type="button" disabled={!ubStatus.connected} title={o.t}
|
||||
@@ -6958,8 +6979,8 @@ export default function App() {
|
||||
{/* Amber while the elements travel: on a SteppIR that is also
|
||||
when transmitting is a bad idea, so it is worth seeing from
|
||||
the icon without opening the widget. */}
|
||||
{ubStatus.moving && <span className="absolute -top-0.5 -right-0.5 size-2 rounded-full bg-warning animate-pulse" />}
|
||||
{!ubStatus.moving && showMotorAnt && ubStatus.connected && <span className="absolute -top-0.5 -right-0.5 size-2 rounded-full bg-success" />}
|
||||
{motorMoving && <span className="absolute -top-0.5 -right-0.5 size-2 rounded-full bg-warning animate-pulse" />}
|
||||
{!motorMoving && showMotorAnt && ubStatus.connected && <span className="absolute -top-0.5 -right-0.5 size-2 rounded-full bg-success" />}
|
||||
</button>
|
||||
)}
|
||||
{agEnabled && (
|
||||
@@ -7762,7 +7783,7 @@ export default function App() {
|
||||
{showMotorAnt && ubStatus.enabled && (
|
||||
<div className="w-[230px] shrink-0 min-h-0" style={{ order: wOrder('motorant') }}>
|
||||
<MotorAntennaWidget
|
||||
ant={ubStatus}
|
||||
ant={{ ...ubStatus, moving: motorMoving }}
|
||||
refetch={pokeUbStatus}
|
||||
t={t}
|
||||
essentialsOnly
|
||||
|
||||
Reference in New Issue
Block a user