From 239ff54942a33253405a73cff47e119b1cd6ea3c Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Fri, 5 Jun 2026 17:39:22 +0300 Subject: [PATCH] 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 --- MainForm.pas | 144 +++++++++++--------------------------------- RadioController.pas | 75 +++++++++++++++++++++++ 2 files changed, 111 insertions(+), 108 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index 02b3d45..d933ca7 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -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 — теперь потоки эмулятора слушают на своих портах. diff --git a/RadioController.pas b/RadioController.pas index ef455a8..069c824 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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 берётся со звуковой карты).