Phase 5 (batch 24): move OnDDCIQ + seq telemetry into the controller

The DDC IQ receive handler (sequence-error tracking + feeding the active
DDC into WDSP) moves verbatim into TRadioController.OnDDCIQ, along with
the 12 RX/seq telemetry fields it owns (FRXPacketCount, FRXStartTime,
FRXLastPktTime, FActiveDDC, FLastDDCSeq, FLastDDCIndex, FDDCLastSeq,
FDDCSeqValid, FSeqErrorCount, FLastSeqErrorDDC, FLastSeqErrorDelta,
FSeqOkStreak). FNetwork.OnDDCIQ is wired to the controller method; all
MainForm readers (meter timer RX-running/stall detection, status-bar SEQ
text, web seq text) and the START/STOP/connect resets now go through
FController. Behaviour unchanged; backend-agnostic (a future Pluto
backend synthesises the same TDDCIQPacket).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-05 17:39:22 +03:00
co-authored by Claude Opus 4.8
parent 3d2d76452c
commit 239ff54942
2 changed files with 111 additions and 108 deletions
+36 -108
View File
@@ -149,18 +149,7 @@ type
FSMeterPeak: Double; // верхняя граница светлой зоны
FSMeterMin: Double; // нижняя граница светлой зоны
FSMeterAvg: Double; // сглаженное среднее (EMA)
FRXPacketCount: LongWord; // счётчик принятых IQ пакетов
FRXStartTime: QWord; // GetTickCount64 момента нажатия START
FRXLastPktTime: QWord; // GetTickCount64 последнего принятого пакета
FActiveDDC: Integer; // DDC index для текущей платы (0 или 2)
FLastDDCSeq: LongWord; // последний seq (пишется из сетевого потока)
FLastDDCIndex: Integer; // последний DDC index
FDDCLastSeq: array[0..6] of LongWord;
FDDCSeqValid: array[0..6] of Boolean;
FSeqErrorCount: LongWord;
FLastSeqErrorDDC: Integer;
FLastSeqErrorDelta: Int64;
FSeqOkStreak: Integer;
// RX/seq телеметрия переехала в TRadioController (пишется из OnDDCIQ).
FLastFwdW: Double;
FLastSWR: Double;
// Эти значения приходят с HP Status пакетами (50..200 раз/с). UI обновляется
@@ -370,7 +359,6 @@ type
// Network callbacks
procedure OnDeviceFound(const Dev: THPSDRDevice);
procedure OnHPStatusCB(const Status: THighPriorityStatus);
procedure OnDDCIQCB(DDCIndex: Integer; const Data: TDDCIQPacket);
procedure OnWidebandCB(ADCIndex: Integer; const Samples: array of SmallInt;
Count: Integer);
procedure OnWDSPOpenDone(Success: Boolean);
@@ -1068,16 +1056,16 @@ begin
FSMeterPeak := -130;
FSMeterMin := -130;
FSMeterAvg := -130;
FRXPacketCount := 0;
FActiveDDC := 0;
FLastDDCSeq := 0;
FLastDDCIndex := 0;
FillChar(FDDCLastSeq, SizeOf(FDDCLastSeq), 0);
FillChar(FDDCSeqValid, SizeOf(FDDCSeqValid), 0);
FSeqErrorCount := 0;
FLastSeqErrorDDC := -1;
FLastSeqErrorDelta:= 0;
FSeqOkStreak := 0;
FController.FRXPacketCount := 0;
FController.FActiveDDC := 0;
FController.FLastDDCSeq := 0;
FController.FLastDDCIndex := 0;
FillChar(FController.FDDCLastSeq, SizeOf(FController.FDDCLastSeq), 0);
FillChar(FController.FDDCSeqValid, SizeOf(FController.FDDCSeqValid), 0);
FController.FSeqErrorCount := 0;
FController.FLastSeqErrorDDC := -1;
FController.FLastSeqErrorDelta:= 0;
FController.FSeqOkStreak := 0;
FLastFwdW := 0;
FLastSWR := 1;
FLastSupplyV:= -1;
@@ -1155,7 +1143,7 @@ begin
FController.CreateEngines(FController.FSampleRate);
FController.FNetwork.OnDeviceFound := OnDeviceFound;
FController.FNetwork.OnHPStatus := OnHPStatusCB;
FController.FNetwork.OnDDCIQ := OnDDCIQCB;
FController.FNetwork.OnDDCIQ := FController.OnDDCIQ;
FController.FNetwork.OnMicPacket := FController.OnMicPacket;
FController.FNetwork.OnWideband := OnWidebandCB;
// Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано).
@@ -2699,11 +2687,11 @@ begin
FSpecView.ADCOverloadVisible := False;
if PbSpectrum <> nil then
PbSpectrum.Invalidate;
FillChar(FDDCSeqValid, SizeOf(FDDCSeqValid), 0);
FSeqErrorCount := 0;
FLastSeqErrorDDC := -1;
FLastSeqErrorDelta := 0;
FSeqOkStreak := 0;
FillChar(FController.FDDCSeqValid, SizeOf(FController.FDDCSeqValid), 0);
FController.FSeqErrorCount := 0;
FController.FLastSeqErrorDDC := -1;
FController.FLastSeqErrorDelta := 0;
FController.FSeqOkStreak := 0;
if ClearDevice then
begin
SetStatusText(0, 'Board --');
@@ -3054,9 +3042,9 @@ begin
if FController.FRunning and FController.FNetwork.Running then
begin
NowTick := GetTickCount64;
ElapsedTick := NowTick - FRXStartTime;
ElapsedTick := NowTick - FController.FRXStartTime;
// Ждём первый пакет 5 секунд после старта
if (FRXLastPktTime = 0) and (ElapsedTick > 5000) then
if (FController.FRXLastPktTime = 0) and (ElapsedTick > 5000) then
begin
BtnStartStop.Caption := 'START';
StyleButton(BtnStartStop, False);
@@ -3068,7 +3056,7 @@ begin
Exit;
end;
// После первого пакета — следим чтобы поток не прерывался более 3 сек
if (FRXLastPktTime > 0) and ((NowTick - FRXLastPktTime) > 3000) then
if (FController.FRXLastPktTime > 0) and ((NowTick - FController.FRXLastPktTime) > 3000) then
begin
BtnStartStop.Caption := 'START';
StyleButton(BtnStartStop, False);
@@ -3085,8 +3073,8 @@ begin
begin
// Обновляем статус из таймера — без Synchronize в сетевом потоке.
SetStatusText(4, 'RX running');
if FSeqErrorCount > 0 then
SetStatusText(6, Format('SEQ ERR DDC%d', [FLastSeqErrorDDC]))
if FController.FSeqErrorCount > 0 then
SetStatusText(6, Format('SEQ ERR DDC%d', [FController.FLastSeqErrorDDC]))
else
SetStatusText(6, 'SEQ OK');
@@ -3229,8 +3217,8 @@ begin
else
WebTXText := 'TX idle';
if FSeqErrorCount > 0 then
WebSeqText := Format('SEQ ERR DDC%d', [FLastSeqErrorDDC])
if FController.FSeqErrorCount > 0 then
WebSeqText := Format('SEQ ERR DDC%d', [FController.FLastSeqErrorDDC])
else if FController.FRunning then
WebSeqText := 'SEQ OK'
else
@@ -3490,66 +3478,6 @@ begin
SetStatusText(3, 'Supply --');
end;
procedure TMainForm.OnDDCIQCB(DDCIndex: Integer; const Data: TDDCIQPacket);
var
SamplesPerFrame: Integer;
Seq, ExpectedSeq: LongWord;
Delta: Int64;
begin
// Вызывается из сетевого потока — UI не трогаем напрямую
// Счётчик принятых пакетов
Inc(FRXPacketCount);
FRXLastPktTime := GetTickCount64; // фиксируем время последнего пакета
Seq := (LongWord(Data.Seq[0]) shl 24) or (LongWord(Data.Seq[1]) shl 16)
or (LongWord(Data.Seq[2]) shl 8) or LongWord(Data.Seq[3]);
if (DDCIndex >= Low(FDDCLastSeq)) and (DDCIndex <= High(FDDCLastSeq)) then
begin
if Seq <> 0 then
begin
ExpectedSeq := FDDCLastSeq[DDCIndex] + 1;
if FDDCSeqValid[DDCIndex] and (Seq <> ExpectedSeq) then
begin
Inc(FSeqErrorCount);
FLastSeqErrorDDC := DDCIndex;
Delta := Int64(Seq) - Int64(ExpectedSeq);
FLastSeqErrorDelta := Delta;
FSeqOkStreak := 0;
end
else if FDDCSeqValid[DDCIndex] then
begin
if FSeqErrorCount > 0 then
begin
Inc(FSeqOkStreak);
if FSeqOkStreak >= 20 then
begin
FSeqErrorCount := 0;
FLastSeqErrorDDC := -1;
FLastSeqErrorDelta := 0;
FSeqOkStreak := 0;
end;
end;
end;
end;
FDDCLastSeq[DDCIndex] := Seq;
FDDCSeqValid[DDCIndex] := True;
end;
// 1. Подаём IQ данные в DSP — только с активного DDC
if (DDCIndex = FActiveDDC) and FController.FWDSPReady then
begin
SamplesPerFrame := (Integer(Data.SamplesPerFrame[0]) shl 8) or
Integer(Data.SamplesPerFrame[1]);
if SamplesPerFrame <= 0 then SamplesPerFrame := 238; // fallback
FController.FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame);
end;
// 2. Запоминаем последний seq — статус обновит таймер (без Synchronize в горячем пути)
FLastDDCSeq := Seq;
FLastDDCIndex := DDCIndex;
end;
procedure TMainForm.OnWidebandCB(ADCIndex: Integer;
const Samples: array of SmallInt; Count: Integer);
begin
@@ -3888,8 +3816,8 @@ begin
if FController.FWDSPReady then FController.FDSPEngine.SetTXRun(False);
StyleButton(BtnMOX, False);
end;
FRXPacketCount := 0;
FActiveDDC := 0;
FController.FRXPacketCount := 0;
FController.FActiveDDC := 0;
// Сохраняем текущую частоту/настройки чтобы следующий START восстановил их
if FController.FDevConnected then
begin
@@ -4187,9 +4115,9 @@ begin
FController.FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32);
if FController.FNetwork.Device.BoardType in [3, 4, 5] then
FActiveDDC := 2
FController.FActiveDDC := 2
else
FActiveDDC := 0;
FController.FActiveDDC := 0;
// Синхронизируем samplerate WDSP если нужно
if FController.FWDSPReady and (FController.FDSPEngine.SampleRate <> FController.FSampleRate) then
@@ -4218,14 +4146,14 @@ begin
FController.FNetwork.SetRunAndFreq(True, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), FController.FDriveLevel);
FController.FRunning := True;
InhibitSleep;
FRXStartTime := GetTickCount64;
FRXLastPktTime := 0;
FRXPacketCount := 0;
FillChar(FDDCSeqValid, SizeOf(FDDCSeqValid), 0);
FSeqErrorCount := 0;
FLastSeqErrorDDC := -1;
FLastSeqErrorDelta := 0;
FSeqOkStreak := 0;
FController.FRXStartTime := GetTickCount64;
FController.FRXLastPktTime := 0;
FController.FRXPacketCount := 0;
FillChar(FController.FDDCSeqValid, SizeOf(FController.FDDCSeqValid), 0);
FController.FSeqErrorCount := 0;
FController.FLastSeqErrorDDC := -1;
FController.FLastSeqErrorDelta := 0;
FController.FSeqOkStreak := 0;
SetStatusText(6, 'SEQ OK');
// DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах.
+75
View File
@@ -200,6 +200,20 @@ type
FLastSMeter, FLastFwdW, FLastSWR, FLastSupplyV, FLastSupplyA: Double;
FLastPLLLock: Boolean;
// ---- RX/seq телеметрия data-path (пишется из сетевого колбэка OnDDCIQ) ----
FRXPacketCount: LongWord; // счётчик принятых IQ пакетов
FRXStartTime: QWord; // GetTickCount64 момента START
FRXLastPktTime: QWord; // GetTickCount64 последнего принятого пакета
FActiveDDC: Integer; // DDC index для текущей платы (0 или 2)
FLastDDCSeq: LongWord; // последний seq (из сетевого потока)
FLastDDCIndex: Integer; // последний DDC index
FDDCLastSeq: array[0..6] of LongWord;
FDDCSeqValid: array[0..6] of Boolean;
FSeqErrorCount: LongWord;
FLastSeqErrorDDC: Integer;
FLastSeqErrorDelta: Int64;
FSeqOkStreak: Integer;
constructor Create;
destructor Destroy; override;
@@ -215,6 +229,7 @@ type
// вызывающим (GUI или демон) после CreateEngines.
procedure OnMicPacket(const Data: TMicDataPacket);
function PullSoundCardMic(MaxN: Integer): Integer;
procedure OnDDCIQ(DDCIndex: Integer; const Data: TDDCIQPacket);
// Маршалинг команды в поток контроллера (GUI: TThread.Synchronize).
procedure Invoke(M: TThreadMethod);
@@ -424,6 +439,66 @@ begin
end;
end;
procedure TRadioController.OnDDCIQ(DDCIndex: Integer; const Data: TDDCIQPacket);
// Сетевой поток: трекинг seq + подача IQ активного DDC в WDSP. UI не трогаем —
// статус рисует таймер по полям ниже. Backend-агностичен (Pluto синтезирует
// тот же TDDCIQPacket).
var
SamplesPerFrame: Integer;
Seq, ExpectedSeq: LongWord;
Delta: Int64;
begin
Inc(FRXPacketCount);
FRXLastPktTime := GetTickCount64;
Seq := (LongWord(Data.Seq[0]) shl 24) or (LongWord(Data.Seq[1]) shl 16)
or (LongWord(Data.Seq[2]) shl 8) or LongWord(Data.Seq[3]);
if (DDCIndex >= Low(FDDCLastSeq)) and (DDCIndex <= High(FDDCLastSeq)) then
begin
if Seq <> 0 then
begin
ExpectedSeq := FDDCLastSeq[DDCIndex] + 1;
if FDDCSeqValid[DDCIndex] and (Seq <> ExpectedSeq) then
begin
Inc(FSeqErrorCount);
FLastSeqErrorDDC := DDCIndex;
Delta := Int64(Seq) - Int64(ExpectedSeq);
FLastSeqErrorDelta := Delta;
FSeqOkStreak := 0;
end
else if FDDCSeqValid[DDCIndex] then
begin
if FSeqErrorCount > 0 then
begin
Inc(FSeqOkStreak);
if FSeqOkStreak >= 20 then
begin
FSeqErrorCount := 0;
FLastSeqErrorDDC := -1;
FLastSeqErrorDelta := 0;
FSeqOkStreak := 0;
end;
end;
end;
end;
FDDCLastSeq[DDCIndex] := Seq;
FDDCSeqValid[DDCIndex] := True;
end;
// Подаём IQ в DSP — только с активного DDC
if (DDCIndex = FActiveDDC) and FWDSPReady then
begin
SamplesPerFrame := (Integer(Data.SamplesPerFrame[0]) shl 8) or
Integer(Data.SamplesPerFrame[1]);
if SamplesPerFrame <= 0 then SamplesPerFrame := 238; // fallback
FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame);
end;
// Запоминаем последний seq — статус обновит таймер (без Synchronize в hot path)
FLastDDCSeq := Seq;
FLastDDCIndex := DDCIndex;
end;
procedure TRadioController.OnMicPacket(const Data: TMicDataPacket);
// HW mic-стрим уходит в WDSP только если выбран источник Radio; иначе сэмплы
// игнорируются (mic берётся со звуковой карты).