Fix TX sequencing: PTT after Alex relay, SEQ error on startup

- Separate FPTTActive from FIsTransmitting so PTT bit in HP packet
  can be sent as a second packet after Alex bit27 (T/R relay) is set,
  matching Thetis behaviour: relay switches first, then PTT=1
- On TX stop: PTT=0 first, then Alex switches back to RX
- Add SendPTT(Active) method to THPSDRNetwork
- Reset FSeqHP to 0 in SetRunAndFreq(Run=True) to eliminate HP SEQ
  ERROR caused by intermediate SendFullHP calls before Run=1 packet
- RebuildXvtrButtons: use dynamic BtnH/RowH/MaxBottom instead of
  hardcoded px values so XVTR buttons scale correctly on HiDPI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 22:09:54 +03:00
co-authored by Claude Sonnet 4.6
parent 81714d982e
commit 09d250ae45
2 changed files with 51 additions and 18 deletions
+23 -8
View File
@@ -139,6 +139,7 @@ type
FCurrentTXFreq: Double; FCurrentTXFreq: Double;
FCurrentDrive: Byte; FCurrentDrive: Byte;
FIsTransmitting: Boolean; FIsTransmitting: Boolean;
FPTTActive: Boolean; // фактический PTT в HP-пакете (отдельно от Alex T/R)
FPAEnabled: Boolean; FPAEnabled: Boolean;
FAlexEnabled: Boolean; FAlexEnabled: Boolean;
FAlexConfig: TAlexSettings; // per-band antenna + routing config FAlexConfig: TAlexSettings; // per-band antenna + routing config
@@ -215,6 +216,9 @@ type
procedure SetXvtrMode(AEnable, AUseRxDDCIn, ADisablePA: Boolean; procedure SetXvtrMode(AEnable, AUseRxDDCIn, ADisablePA: Boolean;
ARxAnt: Byte); ARxAnt: Byte);
procedure SendFullHP; procedure SendFullHP;
// Отдельная отправка PTT: переключает реле (bit27) и drive ДО, потом PTT.
// Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP.
procedure SendPTT(Active: Boolean);
procedure SendDDCAudio(const LeftRight: array of SmallInt); procedure SendDDCAudio(const LeftRight: array of SmallInt);
procedure SendDUCIQ(const IData, QData: array of Integer); procedure SendDUCIQ(const IData, QData: array of Integer);
procedure ClearDUCIQQueue; procedure ClearDUCIQQueue;
@@ -946,13 +950,14 @@ begin
FSocket := DoCreateSocket; FSocket := DoCreateSocket;
if FSocket = SOCK_INVALID then Exit; if FSocket = SOCK_INVALID then Exit;
FConnected := True; FConnected := True;
FSeqGeneral := 0; FPTTActive := False;
FSeqDDCSpec := 0; FSeqGeneral := 0;
FSeqDUCSpec := 0; FSeqDDCSpec := 0;
FSeqHP := 0; FSeqDUCSpec := 0;
FSeqAudio := 0; FSeqHP := 0;
FSeqDUCIQ := 0; FSeqAudio := 0;
FSeqDUCIQ := 0;
ResetDUCIQQueue; ResetDUCIQQueue;
StartThreads; StartThreads;
@@ -970,6 +975,7 @@ begin
StopThreads; StopThreads;
DoCloseSocket(FSocket); DoCloseSocket(FSocket);
FConnected := False; FConnected := False;
FPTTActive := False;
FDevice.Valid := False; FDevice.Valid := False;
FCachedDDCValid := False; FCachedDDCValid := False;
FCachedDUCValid := False; FCachedDUCValid := False;
@@ -1365,7 +1371,7 @@ begin
if FRunning then if FRunning then
begin begin
Buf[4] := HP_RUN; Buf[4] := HP_RUN;
if FIsTransmitting then Buf[4] := Buf[4] or HP_PTT0; if FPTTActive then Buf[4] := Buf[4] or HP_PTT0;
end; end;
// DDC base: ORION/ORION2/ANGELIA/SATURN начинают с DDC2 // DDC base: ORION/ORION2/ANGELIA/SATURN начинают с DDC2
@@ -1424,6 +1430,9 @@ end;
procedure THPSDRNetwork.SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double; procedure THPSDRNetwork.SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
DriveLevel: Byte); DriveLevel: Byte);
begin begin
// При Run=1 аппаратура сбрасывает счётчик HP-последовательности.
// Синхронизируем свой счётчик чтобы не получить SEQ ERROR на первом пакете.
if Run then FSeqHP := 0;
FRunning := Run; FRunning := Run;
FCurrentRXFreq := DDC0FreqHz; FCurrentRXFreq := DDC0FreqHz;
FCurrentTXFreq := DUCFreqHz; FCurrentTXFreq := DUCFreqHz;
@@ -1431,6 +1440,12 @@ begin
SendFullHP; SendFullHP;
end; end;
procedure THPSDRNetwork.SendPTT(Active: Boolean);
begin
FPTTActive := Active;
if FConnected then SendFullHP;
end;
procedure THPSDRNetwork.SendDDCAudio(const LeftRight: array of SmallInt); procedure THPSDRNetwork.SendDDCAudio(const LeftRight: array of SmallInt);
var var
Pkt: TDDCAudioPacket; Pkt: TDDCAudioPacket;
+28 -10
View File
@@ -4571,7 +4571,8 @@ begin
FNetwork.ClearDUCIQQueue; FNetwork.ClearDUCIQQueue;
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
True, True, True); True, True, True);
FNetwork.SendFullHP; FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT
FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле
end; end;
if FWDSPReady then if FWDSPReady then
begin begin
@@ -4615,10 +4616,11 @@ begin
end; end;
if (not FTransmitting) and FNetwork.Connected and FNetwork.Running then if (not FTransmitting) and FNetwork.Connected and FNetwork.Running then
begin begin
FNetwork.SendPTT(False); // 1й пакет: PTT=0 — сначала снимаем PTT
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
FTransmitting, True, True); False, True, True);
FNetwork.ClearDUCIQQueue; FNetwork.ClearDUCIQQueue;
FNetwork.SendFullHP; FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX), drive=0
end; end;
end; end;
@@ -5632,11 +5634,9 @@ begin
end; end;
procedure TMainForm.RebuildXvtrButtons; procedure TMainForm.RebuildXvtrButtons;
const
BTN_H_LOC = 24;
var var
i, Pos, XvtrRows: Integer; i, Pos, XvtrRows: Integer;
W: Integer; W, BtnH, RowH, MaxBottom: Integer;
B: TFlatButton; B: TFlatButton;
Cap: string; Cap: string;
begin begin
@@ -5648,6 +5648,21 @@ begin
BtnXvtrBand[i] := nil; BtnXvtrBand[i] := nil;
end; end;
if PanelBands = nil then Exit; if PanelBands = nil then Exit;
// Измеряем фактический bottom последнего ряда обычных кнопок BAND
// (уже отмасштабированы LCL при показе формы)
MaxBottom := 0;
BtnH := 0;
for i := 0 to BAND_COUNT - 1 do
if BtnBand[i] <> nil then
begin
if BtnBand[i].Top + BtnBand[i].Height > MaxBottom then
MaxBottom := BtnBand[i].Top + BtnBand[i].Height;
BtnH := BtnBand[i].Height;
end;
if BtnH = 0 then BtnH := MulDiv(24, Screen.PixelsPerInch, 96);
RowH := BtnH + 3; // шаг ряда: высота кнопки + 3px зазор (как 27=24+3 при 1x)
W := (PanelBands.Width - 6) div 6; W := (PanelBands.Width - 6) div 6;
Pos := 0; // позиция среди enabled слотов Pos := 0; // позиция среди enabled слотов
for i := 0 to CFG_XVTR_COUNT - 1 do for i := 0 to CFG_XVTR_COUNT - 1 do
@@ -5660,20 +5675,23 @@ begin
B.Parent := PanelBands; B.Parent := PanelBands;
B.Caption := Cap; B.Caption := Cap;
B.Left := 2 + (Pos mod 6) * W; B.Left := 2 + (Pos mod 6) * W;
B.Top := 16 + (2 + (Pos div 6)) * 27; B.Top := MaxBottom + 4 + (Pos div 6) * RowH;
B.Width := W - 2; B.Width := W - 2;
B.Height := BTN_H_LOC; B.Height := BtnH;
B.OnClick := BtnXvtrBandClick; B.OnClick := BtnXvtrBandClick;
B.Tag := -(i + 1); // отрицательное значение = XVTR индекс B.Tag := -(i + 1); // отрицательное значение = XVTR индекс
BtnXvtrBand[i] := B; BtnXvtrBand[i] := B;
StyleButton(B, i = FCurrentXvtr); StyleButton(B, i = FCurrentXvtr);
Inc(Pos); Inc(Pos);
end; end;
// Динамически подгоняем высоту PanelBands под реальное число XVTR-рядов // Динамически подгоняем высоту PanelBands под реальное содержимое
if Pos = 0 then XvtrRows := 0 if Pos = 0 then XvtrRows := 0
else if Pos <= 6 then XvtrRows := 1 else if Pos <= 6 then XvtrRows := 1
else XvtrRows := 2; else XvtrRows := 2;
PanelBands.Height := 74 + XvtrRows * 27; if XvtrRows = 0 then
PanelBands.Height := MaxBottom + 6
else
PanelBands.Height := MaxBottom + 4 + XvtrRows * RowH + 6;
RelayoutBelowBands; RelayoutBelowBands;
end; end;