mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat(dxcluster): споты DX-кластера на панадаптере
Telnet-клиент DX-кластера, база спотов и подписи позывных прямо на спектре на своих частотах. Разложено на три слоя, как бэндплан и лупа маяка. DXClusterClient.pas — один рабочий поток: резолв, неблокирующий connect с select квантами по 200 мс (Stop не ждёт таймаут соединения), логин позывным, чтение строк, реконнект с backoff. Приглашения логина И пароля ловятся в незавершённом хвосте буфера — типичный telnet-prompt приходит без CR/LF. Ошибки recv отличаются от таймаута кванта (EAGAIN/EINTR/WSAETIMEDOUT), иначе на ECONNRESET поток крутился бы в пустом цикле вместо реконнекта. Отправка дописывает частичный send. LCL-free. DXSpotStore.pas — потокобезопасная база: дедуп по позывному, TTL, потолок записей, монотонный Version. Единственная точка обмена потока с UI: никакого Synchronize, UI сам замечает правки по Version, как оверлеи — по ключам кэша. DXSpotOverlay.pas — рендер по модели BandPlanOverlay/VfoOverlay: кэшируется только полоса подписей (W × BandH) в key-color битмап, пересборка строго по dirty-ключу, на кадр — один keyed-композит. Штрихи от полосы до низа спектра рисует вызывающая сторона теми же примитивами, что и прочие маркеры: CPU — RawVLine внутри RawBegin/RawEnd, GL — DrawLine по готовому списку X/цвет. GL берёт тот же битмап текстурой и заливает её только при смене RenderVersion. Пересекающиеся подписи раскладываются лесенкой, цвет гаснет с возрастом, свой позывной выделен; палитра парная под тёмную и светлую тему. DXClusterForm.pas — окно списка: споты, лог соединения, строка команды кластеру (диалект set/filter у всех свой — не угадываем). Двойной клик или Enter = QSY. Данные тянутся поллингом по Version/LogVersion. Интеграция: кнопка DX в тулбаре (ЛКМ — подписи на спектре, ПКМ — окно), клик по подписи спота = QSY с автовыбором моды по комментарию кластера, страница SETUP → DX Cluster с персистом в секции "dxcluster". Правки SETUP прилетают посимвольно, поэтому запись конфига, TTL стора и переподключение откладываются до паузы в наборе — иначе набор позывного стоил бы шесть реконнектов, а промежуточный TTL «3» необратимо выбросил бы споты. Частота спота кладётся как есть и сравнивается с GetViewWindow: на QO-100 кластеры постят downlink 10489.xxx, что совпадает со шкалой пана само собой. Побочно в общих юнитах: WebUtils.SockSetRcvTimeout, FlatMemo.OnChange, FlatListBox.OnKeyDown, BlendBitmapKey вынесен в interface VfoOverlay (одна копия дворд-блендера на проект). Проверено на локальном фейковом кластере: логин и пароль по prompt без CR/LF, разбор спотов (включая QO-100), уход в RETRY по RST, Stop за 200 мс на висящем connect. На железе рендер не проверялся. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ uses
|
||||
Classes, SysUtils, Graphics, Controls, Math, Types,
|
||||
OpenGLContextEx, GL,
|
||||
AppTheme, AlertOverlay, SpectrumView, VfoOverlay, BandPlanOverlay,
|
||||
DXSpotOverlay,
|
||||
WaterfallView, WaterfallViewOpengl;
|
||||
|
||||
type
|
||||
@@ -36,6 +37,7 @@ type
|
||||
FSampleOverlayDirty: Boolean;
|
||||
FVfoOverlayDirty: Boolean;
|
||||
FBandOverlayDirty: Boolean;
|
||||
FDXOverlayDirty: Boolean;
|
||||
FGridLabelTex: TGLTextureCache;
|
||||
FAGCLabelTex: TGLTextureCache;
|
||||
FAGCHangLabelTex: TGLTextureCache;
|
||||
@@ -44,6 +46,7 @@ type
|
||||
FSampleOverlayTex: TGLTextureCache;
|
||||
FVfoOverlayTex: TGLTextureCache;
|
||||
FBandOverlayTex: TGLTextureCache;
|
||||
FDXOverlayTex: TGLTextureCache;
|
||||
// Текстуры флагов слайсов (B+), привязка по указателю оверлея.
|
||||
FSliceTex: array of record
|
||||
Overlay: TVfoOverlay;
|
||||
@@ -72,6 +75,8 @@ type
|
||||
FLastBandW: Integer;
|
||||
FLastBandCenter: Double;
|
||||
FLastBandSpan: Double;
|
||||
FLastDXW: Integer;
|
||||
FLastDXRender: Int64;
|
||||
FGLSpectrumW: Integer;
|
||||
FGLSpectrumH: Integer;
|
||||
FSpY: array of Integer;
|
||||
@@ -93,6 +98,7 @@ type
|
||||
procedure DrawSpectrumCurve(W, H: Integer; DBmax, InvRange: Double);
|
||||
procedure DrawMarker(W, H: Integer);
|
||||
procedure DrawBeaconMarkersGL(W, H: Integer);
|
||||
procedure DrawDXSpotTicksGL(W, H: Integer);
|
||||
procedure DrawCircleGL(CX, CY: Integer; R: Single; C: TColor);
|
||||
procedure DrawIMDMarkersGL(W, H: Integer; DBmax, InvRange: Double);
|
||||
procedure DrawADCOverlay(W, H: Integer);
|
||||
@@ -152,8 +158,10 @@ begin
|
||||
FSampleOverlayDirty := True;
|
||||
FVfoOverlayDirty := True;
|
||||
FBandOverlayDirty := True;
|
||||
FDXOverlayDirty := True;
|
||||
FLastBandW := -MaxInt;
|
||||
FLastBandCenter := -1; FLastBandSpan := -1;
|
||||
FLastDXW := -MaxInt; FLastDXRender := -1;
|
||||
FLastAGCY := -MaxInt;
|
||||
FLastAGCHangY := -MaxInt;
|
||||
FLastMarkerX := -MaxInt;
|
||||
@@ -173,6 +181,7 @@ begin
|
||||
DeleteTexture(FSampleOverlayTex);
|
||||
DeleteTexture(FVfoOverlayTex);
|
||||
DeleteTexture(FBandOverlayTex);
|
||||
DeleteTexture(FDXOverlayTex);
|
||||
for i := 0 to High(FSliceTex) do DeleteTexture(FSliceTex[i].Tex);
|
||||
for i := 0 to High(FLetterTex) do DeleteTexture(FLetterTex[i]);
|
||||
for i := 0 to High(FIMDLabelTex) do DeleteTexture(FIMDLabelTex[i]);
|
||||
@@ -454,6 +463,7 @@ begin
|
||||
Zap(FADCOverlayTex);
|
||||
Zap(FSampleOverlayTex);
|
||||
Zap(FVfoOverlayTex);
|
||||
Zap(FDXOverlayTex);
|
||||
Zap(FBandOverlayTex);
|
||||
for i := 0 to High(FSliceTex) do Zap(FSliceTex[i].Tex);
|
||||
for i := 0 to High(FLetterTex) do Zap(FLetterTex[i]);
|
||||
@@ -469,6 +479,7 @@ begin
|
||||
FSampleOverlayDirty := True;
|
||||
FVfoOverlayDirty := True;
|
||||
FBandOverlayDirty := True;
|
||||
FDXOverlayDirty := True;
|
||||
FGLSpectrumW := 0;
|
||||
FGLSpectrumH := 0;
|
||||
// Водопад — свой контекст/текстуры (история + маркер). При reparent (pop-out)
|
||||
@@ -820,6 +831,25 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSpectrumViewOpenGL.DrawDXSpotTicksGL(W, H: Integer);
|
||||
// GL-двойник DrawDXSpotTicksRaw: те же X и цвета из раскладки оверлея, только
|
||||
// линиями GL. EnsureRendered здесь и держит раскладку свежей — текстуру полосы
|
||||
// DrawCachedOverlays перезальёт по RenderVersion, кто бы ни пересобрал кэш.
|
||||
var
|
||||
i, X, Y0: Integer;
|
||||
Col: TColor;
|
||||
begin
|
||||
if (FDXSpotOverlay = nil) or (not FDXSpotOverlay.Active) then Exit;
|
||||
FDXSpotOverlay.EnsureRendered(W);
|
||||
Y0 := FDXSpotOverlay.BandHeight;
|
||||
for i := 0 to FDXSpotOverlay.TickCount - 1 do
|
||||
begin
|
||||
FDXSpotOverlay.Tick(i, X, Col);
|
||||
if (X < 0) or (X >= W) then Continue;
|
||||
DrawLine(X, Y0, X, H, Col, 1, True); // пунктир — как в CPU-пути
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSpectrumViewOpenGL.DrawCircleGL(CX, CY: Integer; R: Single; C: TColor);
|
||||
// Залитый кружок (triangle fan, 20 сегментов) — маркер пика 2TON/IMD.
|
||||
var
|
||||
@@ -947,6 +977,28 @@ begin
|
||||
DrawTexture(FBandOverlayTex, 0, H - FBandOverlayTex.H);
|
||||
end;
|
||||
|
||||
// Полоса подписей DX-спотов — вверху спектра. Тот же битмап, что в CPU-пути,
|
||||
// заливается текстурой ТОЛЬКО при dirty (новый спот меняет CacheDirty
|
||||
// оверлея, вид — центр/спан/ширину).
|
||||
if Assigned(FDXSpotOverlay) and FDXSpotOverlay.Active then
|
||||
begin
|
||||
if FOverlayDirty or FDXOverlayDirty or FDXOverlayTex.Dirty or
|
||||
(FLastDXW <> W) or (FLastDXRender <> FDXSpotOverlay.RenderVersion) then
|
||||
begin
|
||||
B := TBitmap.Create;
|
||||
try
|
||||
FDXSpotOverlay.DrawOverlayBitmap(B, W);
|
||||
UploadBitmap(FDXOverlayTex, B, True, DXSPOT_ALPHA);
|
||||
finally
|
||||
B.Free;
|
||||
end;
|
||||
FLastDXW := W;
|
||||
FLastDXRender := FDXSpotOverlay.RenderVersion;
|
||||
FDXOverlayDirty := False;
|
||||
end;
|
||||
DrawTexture(FDXOverlayTex, 0, 0);
|
||||
end;
|
||||
|
||||
if Assigned(FSampleRateOverlay) then
|
||||
begin
|
||||
OW := Min(FSampleRateOverlay.Width, W - FSampleRateOverlay.Left);
|
||||
@@ -1098,6 +1150,7 @@ begin
|
||||
DrawSpectrumCurve(W, H, DBmax, InvRange);
|
||||
DrawMarker(W, H);
|
||||
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkersGL(W, H);
|
||||
DrawDXSpotTicksGL(W, H);
|
||||
// 2TON: кружки с уровнями на пиках тонов/IMD3 (активирует MainForm при
|
||||
// 2TON+TX+DUP — реальный сигнал после PA виден на RX-спектре)
|
||||
if FIMDActive then DrawIMDMarkersGL(W, H, DBmax, InvRange);
|
||||
|
||||
Reference in New Issue
Block a user