fix(tci): subscribe to the radio's meters; fix the new-spot count; explain an SMTP refusal

TCI meters: the S-meter came only from RX_SMETER and the transmit meters from
TX_POWER / TX_SWR — commands ExpertSDR3 does not send. The protocol's answer is
a subscription (RX_SENSORS_ENABLE / TX_SENSORS_ENABLE, §4.4 of the TCI PDF),
after which the radio pushes RX_CHANNEL_SENSORS and TX_SENSORS. Nobody had
asked, so the console's meters sat empty in RX and in TX while everything else
worked.

Cluster: the held-spot counter looked for the row it froze on. A station spotted
again REPLACES its row, so that row vanishes in the ordinary course of things
and the count fell through to 'everything is new' — 4, 5, then 500. Counted by
timestamp now, which survives both the replacement and the ring buffer.

SMTP: '535 5.7.139 basic authentication is disabled' is a policy, not a typo.
The message now says so, and says what actually helps, without claiming a policy
when the server simply rejected the password.
This commit is contained in:
2026-08-28 15:29:44 +02:00
parent 3e794f57e0
commit fd7ae77a61
6 changed files with 132 additions and 12 deletions
+19 -8
View File
@@ -627,16 +627,27 @@ export function ClusterGrid({ rows, spotStatus, onSpotClick, onSpotSelect, heade
const [held, setHeld] = useState<ClusterSpot[] | null>(null);
const shown = held ?? rows;
// How many arrived since the freeze. Counted by finding the frozen top row in
// the live list rather than by comparing lengths: the list is a ring buffer,
// so once it is full the length stops growing and a length comparison would
// report nothing new for the rest of the evening.
const spotID = (r: ClusterSpot) => `${(r as any).received_at}-${r.dx_call}-${(r as any).source_id}`;
// How many arrived since the freeze — counted by TIME, not by finding the
// frozen top row again.
//
// Looking for that row was wrong in the ordinary case: a station spotted again
// REPLACES its row (that is the de-dupe), so the row we froze on disappears
// from the live list the moment somebody re-spots it — and the count fell
// through to "everything is new", jumping from 4 to the buffer cap. Reported
// as "it shows 4, 5 new spots and then 500 all at once".
//
// A timestamp survives both the replacement and the ring buffer, which was the
// reason the length was not used either.
const spotTime = (r: ClusterSpot) => Date.parse(String((r as any).received_at ?? '')) || 0;
const waiting = useMemo(() => {
if (!held || held.length === 0) return 0;
const top = spotID(held[0]);
const i = rows.findIndex((r) => spotID(r) === top);
return i < 0 ? rows.length : i; // fell out of the buffer: everything is new
const since = spotTime(held[0]);
if (!since) return 0; // no usable timestamp — say nothing rather than a number
let n = 0;
for (const r of rows) {
if (spotTime(r) > since) n++;
}
return n;
}, [held, rows]);
const onBodyScroll = (e: { top: number }) => {