mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Improve radio status bar
This commit is contained in:
+19
-1
@@ -392,6 +392,12 @@ function PhaseWordToFreq(PhaseWord: LongWord): Double;
|
|||||||
// Returns -1.0 if the board has no supply voltage measurement.
|
// Returns -1.0 if the board has no supply voltage measurement.
|
||||||
function ADCToSupplyVolts(RawADC: Word; BoardType: Integer): Double;
|
function ADCToSupplyVolts(RawADC: Word; BoardType: Integer): Double;
|
||||||
|
|
||||||
|
// True when the board type reports supply voltage in HP Status.
|
||||||
|
function BoardSupportsSupplyVoltage(BoardType: Integer): Boolean;
|
||||||
|
|
||||||
|
// True when the board type reports supply current in HP Status.
|
||||||
|
function BoardSupportsSupplyCurrent(BoardType: Integer): Boolean;
|
||||||
|
|
||||||
// Convert raw ADC value to supply current in Amps (Board 5 / Orion MkII only)
|
// Convert raw ADC value to supply current in Amps (Board 5 / Orion MkII only)
|
||||||
// Uses UserADC1 (bytes 55-56). Returns -1 for unsupported boards.
|
// Uses UserADC1 (bytes 55-56). Returns -1 for unsupported boards.
|
||||||
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer): Double;
|
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer): Double;
|
||||||
@@ -448,16 +454,28 @@ begin
|
|||||||
// The PCB voltage divider (from Thetis source) must also be applied.
|
// The PCB voltage divider (from Thetis source) must also be applied.
|
||||||
// Board 0 (Atlas): no supply voltage measurement.
|
// Board 0 (Atlas): no supply voltage measurement.
|
||||||
// Boards 1,2,3 (Hermes/Angelia): Vref=3.3V, PCB divider R1=4.7kΩ/R2=0.82kΩ.
|
// Boards 1,2,3 (Hermes/Angelia): Vref=3.3V, PCB divider R1=4.7kΩ/R2=0.82kΩ.
|
||||||
|
// Board 4 (Orion): protocol reports supply voltage with a 5.0V constant.
|
||||||
// Board 5 (Orion MkII): Vref=5.0V, PCB divider R1=22kΩ/R2=1.1kΩ.
|
// Board 5 (Orion MkII): Vref=5.0V, PCB divider R1=22kΩ/R2=1.1kΩ.
|
||||||
// NOTE: for board 5 caller must pass UserADC0, not supply_volts bytes.
|
// NOTE: for board 5 caller must pass UserADC0, not supply_volts bytes.
|
||||||
// Board 4 (Orion) and others: not available (return -1).
|
// Other boards: not available (return -1).
|
||||||
case BoardType of
|
case BoardType of
|
||||||
1, 2, 3: Result := (RawADC / 4095.0) * 3.3 * ((4.7 + 0.82) / 0.82);
|
1, 2, 3: Result := (RawADC / 4095.0) * 3.3 * ((4.7 + 0.82) / 0.82);
|
||||||
|
4: Result := (RawADC / 4095.0) * 5.0;
|
||||||
5: Result := (RawADC / 4095.0) * 5.0 * ((22.0 + 1.0) / 1.1);
|
5: Result := (RawADC / 4095.0) * 5.0 * ((22.0 + 1.0) / 1.1);
|
||||||
else Result := -1.0;
|
else Result := -1.0;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function BoardSupportsSupplyVoltage(BoardType: Integer): Boolean;
|
||||||
|
begin
|
||||||
|
Result := BoardType in [1, 2, 3, 4, 5];
|
||||||
|
end;
|
||||||
|
|
||||||
|
function BoardSupportsSupplyCurrent(BoardType: Integer): Boolean;
|
||||||
|
begin
|
||||||
|
Result := BoardType = 5;
|
||||||
|
end;
|
||||||
|
|
||||||
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer): Double;
|
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer): Double;
|
||||||
begin
|
begin
|
||||||
// Board 5 (Orion MkII): UserADC1, Vref=5V → mV = adc/4095*5000.
|
// Board 5 (Orion MkII): UserADC1, Vref=5V → mV = adc/4095*5000.
|
||||||
|
|||||||
+243
-101
@@ -241,6 +241,12 @@ type
|
|||||||
FActiveDDC: Integer; // DDC index для текущей платы (0 или 2)
|
FActiveDDC: Integer; // DDC index для текущей платы (0 или 2)
|
||||||
FLastDDCSeq: LongWord; // последний seq (пишется из сетевого потока)
|
FLastDDCSeq: LongWord; // последний seq (пишется из сетевого потока)
|
||||||
FLastDDCIndex: Integer; // последний DDC index
|
FLastDDCIndex: Integer; // последний DDC index
|
||||||
|
FDDCLastSeq: array[0..6] of LongWord;
|
||||||
|
FDDCSeqValid: array[0..6] of Boolean;
|
||||||
|
FSeqErrorCount: LongWord;
|
||||||
|
FLastSeqErrorDDC: Integer;
|
||||||
|
FLastSeqErrorDelta: Int64;
|
||||||
|
FSeqOkStreak: Integer;
|
||||||
FLastFwdW: Double;
|
FLastFwdW: Double;
|
||||||
FLastSWR: Double;
|
FLastSWR: Double;
|
||||||
// Эти значения приходят с HP Status пакетами (50..200 раз/с). UI обновляется
|
// Эти значения приходят с HP Status пакетами (50..200 раз/с). UI обновляется
|
||||||
@@ -427,7 +433,9 @@ type
|
|||||||
PbWaterfall: TPaintBox;
|
PbWaterfall: TPaintBox;
|
||||||
|
|
||||||
// ---- Status bar ----
|
// ---- Status bar ----
|
||||||
StatusBar1: TStatusBar;
|
StatusPanel: TPanel;
|
||||||
|
StatusCells: array[0..7] of TPanel;
|
||||||
|
StatusLabels: array[0..7] of TLabel;
|
||||||
|
|
||||||
// ---- Helpers ----
|
// ---- Helpers ----
|
||||||
procedure BuildUI;
|
procedure BuildUI;
|
||||||
@@ -435,6 +443,9 @@ type
|
|||||||
procedure SetLightTheme(V: Boolean);
|
procedure SetLightTheme(V: Boolean);
|
||||||
procedure StyleButton(B: TFlatButton; Active: Boolean = False);
|
procedure StyleButton(B: TFlatButton; Active: Boolean = False);
|
||||||
procedure StyleSpanButton(B: TFlatButton; Active: Boolean = False);
|
procedure StyleSpanButton(B: TFlatButton; Active: Boolean = False);
|
||||||
|
procedure SetStatusText(Index: Integer; const Text: string);
|
||||||
|
procedure SetRadioOfflineStatus(const ConnText: string; ClearDevice: Boolean = False);
|
||||||
|
procedure ApplyStatusTheme(const T: TAppTheme);
|
||||||
procedure PositionSpanOverlayButtons;
|
procedure PositionSpanOverlayButtons;
|
||||||
procedure UpdateVfoDisplay;
|
procedure UpdateVfoDisplay;
|
||||||
function FormatFreq(Hz: Double): string;
|
function FormatFreq(Hz: Double): string;
|
||||||
@@ -1299,6 +1310,12 @@ begin
|
|||||||
FActiveDDC := 0;
|
FActiveDDC := 0;
|
||||||
FLastDDCSeq := 0;
|
FLastDDCSeq := 0;
|
||||||
FLastDDCIndex := 0;
|
FLastDDCIndex := 0;
|
||||||
|
FillChar(FDDCLastSeq, SizeOf(FDDCLastSeq), 0);
|
||||||
|
FillChar(FDDCSeqValid, SizeOf(FDDCSeqValid), 0);
|
||||||
|
FSeqErrorCount := 0;
|
||||||
|
FLastSeqErrorDDC := -1;
|
||||||
|
FLastSeqErrorDelta:= 0;
|
||||||
|
FSeqOkStreak := 0;
|
||||||
FLastFwdW := 0;
|
FLastFwdW := 0;
|
||||||
FLastSWR := 1;
|
FLastSWR := 1;
|
||||||
FLastSupplyV:= -1;
|
FLastSupplyV:= -1;
|
||||||
@@ -1507,6 +1524,30 @@ var
|
|||||||
L.Font.Size := 7;
|
L.Font.Size := 7;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure MakeStatusCell(Index, CellWidth: Integer; const Cap: string);
|
||||||
|
begin
|
||||||
|
StatusCells[Index] := TPanel.Create(Self);
|
||||||
|
StatusCells[Index].Parent := StatusPanel;
|
||||||
|
StatusCells[Index].BevelOuter := bvNone;
|
||||||
|
StatusCells[Index].Align := alLeft;
|
||||||
|
StatusCells[Index].Width := CellWidth;
|
||||||
|
StatusCells[Index].BorderSpacing.Left := 1;
|
||||||
|
StatusCells[Index].BorderSpacing.Top := 1;
|
||||||
|
StatusCells[Index].BorderSpacing.Bottom := 1;
|
||||||
|
|
||||||
|
StatusLabels[Index] := TLabel.Create(Self);
|
||||||
|
StatusLabels[Index].Parent := StatusCells[Index];
|
||||||
|
StatusLabels[Index].AutoSize := False;
|
||||||
|
StatusLabels[Index].Align := alClient;
|
||||||
|
StatusLabels[Index].BorderSpacing.Left := 8;
|
||||||
|
StatusLabels[Index].BorderSpacing.Right := 8;
|
||||||
|
StatusLabels[Index].Alignment := taLeftJustify;
|
||||||
|
StatusLabels[Index].Layout := tlCenter;
|
||||||
|
StatusLabels[Index].Caption := Cap;
|
||||||
|
StatusLabels[Index].Font.Name := 'Courier New';
|
||||||
|
StatusLabels[Index].Font.Size := 8;
|
||||||
|
end;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
// ---- Toolbar ----
|
// ---- Toolbar ----
|
||||||
PanelToolbar := TPanel.Create(Self);
|
PanelToolbar := TPanel.Create(Self);
|
||||||
@@ -1537,19 +1578,23 @@ begin
|
|||||||
// ---- Separator line ----
|
// ---- Separator line ----
|
||||||
|
|
||||||
// ---- Status bar ----
|
// ---- Status bar ----
|
||||||
StatusBar1 := TStatusBar.Create(Self);
|
StatusPanel := TPanel.Create(Self);
|
||||||
StatusBar1.Parent := Self;
|
StatusPanel.Parent := Self;
|
||||||
StatusBar1.Align := alBottom;
|
StatusPanel.Align := alBottom;
|
||||||
StatusBar1.SimplePanel := False;
|
StatusPanel.Height := 26;
|
||||||
with StatusBar1.Panels do
|
StatusPanel.BevelOuter := bvNone;
|
||||||
begin
|
|
||||||
Add; Items[0].Width := 150; Items[0].Text := 'IP: --';
|
// alLeft controls are arranged by Z-order in LCL; create them in reverse
|
||||||
Add; Items[1].Width := 120; Items[1].Text := 'Board: --';
|
// visual order so the final bar reads left-to-right as intended.
|
||||||
Add; Items[2].Width := 200; Items[2].Text := 'Supply: --';
|
MakeStatusCell(6, 130, 'SEQ --');
|
||||||
Add; Items[3].Width := 250; Items[3].Text := 'RX: waiting...';
|
MakeStatusCell(5, 180, 'TX idle');
|
||||||
Add; Items[4].Width := 200; Items[4].Text := 'Not connected';
|
MakeStatusCell(4, 190, 'RX idle');
|
||||||
Add; Items[5].Width := 280; Items[5].Text := 'TX: --';
|
MakeStatusCell(7, 90, 'PLL --');
|
||||||
end;
|
MakeStatusCell(3, 150, 'Supply --');
|
||||||
|
MakeStatusCell(1, 120, 'IP --');
|
||||||
|
MakeStatusCell(0, 220, 'Board --');
|
||||||
|
MakeStatusCell(2, 170, 'Disconnected');
|
||||||
|
StatusCells[6].Align := alClient;
|
||||||
|
|
||||||
// ---- Left panel ----
|
// ---- Left panel ----
|
||||||
PanelLeft := TPanel.Create(Self);
|
PanelLeft := TPanel.Create(Self);
|
||||||
@@ -2493,6 +2538,7 @@ begin
|
|||||||
PbWaterfall.Color := T.BG;
|
PbWaterfall.Color := T.BG;
|
||||||
if PanelSMeterRight <> nil then PanelSMeterRight.Color := T.Panel;
|
if PanelSMeterRight <> nil then PanelSMeterRight.Color := T.Panel;
|
||||||
if PbSMeterRight <> nil then PbSMeterRight.Color := T.Panel;
|
if PbSMeterRight <> nil then PbSMeterRight.Color := T.Panel;
|
||||||
|
ApplyStatusTheme(T);
|
||||||
|
|
||||||
// Метки
|
// Метки
|
||||||
DL(LblAGCTop, False);
|
DL(LblAGCTop, False);
|
||||||
@@ -2555,6 +2601,62 @@ begin
|
|||||||
UpdateVfoDisplay;
|
UpdateVfoDisplay;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.SetStatusText(Index: Integer; const Text: string);
|
||||||
|
begin
|
||||||
|
if (Index < Low(StatusLabels)) or (Index > High(StatusLabels)) then Exit;
|
||||||
|
if StatusLabels[Index] = nil then Exit;
|
||||||
|
if StatusLabels[Index].Caption <> Text then
|
||||||
|
StatusLabels[Index].Caption := Text;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.SetRadioOfflineStatus(const ConnText: string; ClearDevice: Boolean);
|
||||||
|
begin
|
||||||
|
SetStatusText(2, ConnText);
|
||||||
|
SetStatusText(3, 'Supply --');
|
||||||
|
SetStatusText(4, 'RX idle');
|
||||||
|
SetStatusText(5, 'TX idle');
|
||||||
|
SetStatusText(6, 'SEQ --');
|
||||||
|
SetStatusText(7, 'PLL --');
|
||||||
|
FillChar(FDDCSeqValid, SizeOf(FDDCSeqValid), 0);
|
||||||
|
FSeqErrorCount := 0;
|
||||||
|
FLastSeqErrorDDC := -1;
|
||||||
|
FLastSeqErrorDelta := 0;
|
||||||
|
FSeqOkStreak := 0;
|
||||||
|
if ClearDevice then
|
||||||
|
begin
|
||||||
|
SetStatusText(0, 'Board --');
|
||||||
|
SetStatusText(1, 'IP --');
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.ApplyStatusTheme(const T: TAppTheme);
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
begin
|
||||||
|
if StatusPanel = nil then Exit;
|
||||||
|
StatusPanel.Color := T.Border;
|
||||||
|
for I := Low(StatusCells) to High(StatusCells) do
|
||||||
|
begin
|
||||||
|
if StatusCells[I] <> nil then
|
||||||
|
begin
|
||||||
|
StatusCells[I].Color := T.Panel;
|
||||||
|
StatusCells[I].Font.Color := T.Text;
|
||||||
|
end;
|
||||||
|
if StatusLabels[I] <> nil then
|
||||||
|
begin
|
||||||
|
if I in [4, 5] then
|
||||||
|
StatusLabels[I].Font.Color := T.TextDim
|
||||||
|
else if I = 6 then
|
||||||
|
StatusLabels[I].Font.Color := T.Amber
|
||||||
|
else if I = 2 then
|
||||||
|
StatusLabels[I].Font.Color := T.TbStartText
|
||||||
|
else
|
||||||
|
StatusLabels[I].Font.Color := T.Text;
|
||||||
|
StatusLabels[I].Color := T.Panel;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.SetLightTheme(V: Boolean);
|
procedure TMainForm.SetLightTheme(V: Boolean);
|
||||||
begin
|
begin
|
||||||
if FLightTheme = V then Exit;
|
if FLightTheme = V then Exit;
|
||||||
@@ -2878,22 +2980,17 @@ begin
|
|||||||
// HP Status пакеты идут ~150 раз/с, при таком rate цифры дёргаются.
|
// HP Status пакеты идут ~150 раз/с, при таком rate цифры дёргаются.
|
||||||
UpdateTXMeters;
|
UpdateTXMeters;
|
||||||
|
|
||||||
// Диагностика TX mic-пути — в отдельной панели [5], чтобы её не перетирали
|
|
||||||
// другие источники (DDC seq, audio-логи и т.п.).
|
|
||||||
if FWDSPReady then
|
if FWDSPReady then
|
||||||
begin
|
begin
|
||||||
if FTransmitting then
|
if FTransmitting then
|
||||||
begin
|
begin
|
||||||
if FDSPEngine.TXMicSource = txmsSoundCard then
|
if FDSPEngine.TXMicSource = txmsSoundCard then
|
||||||
StatusBar1.Panels[5].Text :=
|
SetStatusText(5, 'TX sound card')
|
||||||
Format('TX sc: avail=%d ring=%d',
|
|
||||||
[FAudioIn.Available, FDSPEngine.TXMicRingAvail])
|
|
||||||
else
|
else
|
||||||
StatusBar1.Panels[5].Text :=
|
SetStatusText(5, 'TX radio mic');
|
||||||
Format('TX hw: ring=%d', [FDSPEngine.TXMicRingAvail]);
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
StatusBar1.Panels[5].Text := 'TX: idle';
|
SetStatusText(5, 'TX idle');
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -2929,7 +3026,7 @@ begin
|
|||||||
StyleButton(BtnStartStop, False);
|
StyleButton(BtnStartStop, False);
|
||||||
FRunning := False;
|
FRunning := False;
|
||||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
||||||
StatusBar1.Panels[4].Text := 'ТРАНСИВЕР НЕДОСТУПЕН';
|
SetRadioOfflineStatus('Hardware timeout');
|
||||||
BtnMOX.Enabled := False;
|
BtnMOX.Enabled := False;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
@@ -2940,7 +3037,7 @@ begin
|
|||||||
StyleButton(BtnStartStop, False);
|
StyleButton(BtnStartStop, False);
|
||||||
FRunning := False;
|
FRunning := False;
|
||||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
||||||
StatusBar1.Panels[4].Text := 'ПОТЕРЯ СВЯЗИ С ТРАНСИВЕРОМ';
|
SetRadioOfflineStatus('Connection lost');
|
||||||
BtnMOX.Enabled := False;
|
BtnMOX.Enabled := False;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
@@ -2948,10 +3045,12 @@ begin
|
|||||||
|
|
||||||
if FRunning then
|
if FRunning then
|
||||||
begin
|
begin
|
||||||
// Обновляем StatusBar из таймера — без Synchronize в сетевом потоке
|
// Обновляем статус из таймера — без Synchronize в сетевом потоке.
|
||||||
StatusBar1.Panels[3].Text :=
|
SetStatusText(4, 'RX running');
|
||||||
Format('RX DDC%d | seq=%d | pkts=%d',
|
if FSeqErrorCount > 0 then
|
||||||
[FLastDDCIndex, FLastDDCSeq, FRXPacketCount]);
|
SetStatusText(6, Format('SEQ ERR DDC%d', [FLastSeqErrorDDC]))
|
||||||
|
else
|
||||||
|
SetStatusText(6, 'SEQ OK');
|
||||||
|
|
||||||
if FWDSPReady then
|
if FWDSPReady then
|
||||||
begin
|
begin
|
||||||
@@ -3082,7 +3181,7 @@ begin
|
|||||||
if Assigned(FDeviceDialog) then
|
if Assigned(FDeviceDialog) then
|
||||||
FDeviceDialog.AddDiscovered(Dev.IPAddress, Entry, Dev.BoardType);
|
FDeviceDialog.AddDiscovered(Dev.IPAddress, Entry, Dev.BoardType);
|
||||||
|
|
||||||
StatusBar1.Panels[4].Text := Format('Found: %s', [Dev.IPAddress]);
|
SetStatusText(2, Format('Found: %s', [Dev.IPAddress]));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnHPStatusCB(const Status: THighPriorityStatus);
|
procedure TMainForm.OnHPStatusCB(const Status: THighPriorityStatus);
|
||||||
@@ -3101,22 +3200,27 @@ begin
|
|||||||
ExcPwr := (Status.ExciterPwr0Hi shl 8) or Status.ExciterPwr0Lo;
|
ExcPwr := (Status.ExciterPwr0Hi shl 8) or Status.ExciterPwr0Lo;
|
||||||
FwdPwr := (Status.FwdPwrAlex0Hi shl 8) or Status.FwdPwrAlex0Lo;
|
FwdPwr := (Status.FwdPwrAlex0Hi shl 8) or Status.FwdPwrAlex0Lo;
|
||||||
RevPwr := (Status.RevPwrAlex0Hi shl 8) or Status.RevPwrAlex0Lo;
|
RevPwr := (Status.RevPwrAlex0Hi shl 8) or Status.RevPwrAlex0Lo;
|
||||||
|
SupplyV := -1.0;
|
||||||
|
SupplyA := -1.0;
|
||||||
|
|
||||||
// Board 5 (Orion MkII): voltage on UserADC0 (bytes 57-58),
|
// Board 5 (Orion MkII): voltage on UserADC0 (bytes 57-58),
|
||||||
// current on UserADC1 (bytes 55-56) — matching Thetis behaviour.
|
// current on UserADC1 (bytes 55-56) — matching Thetis behaviour.
|
||||||
if FNetwork.Device.BoardType = 5 then
|
// Other supported boards report voltage in SupplyVolts bytes and no current.
|
||||||
|
if BoardSupportsSupplyVoltage(FNetwork.Device.BoardType) then
|
||||||
begin
|
begin
|
||||||
SupplyV := ADCToSupplyVolts(
|
if FNetwork.Device.BoardType = 5 then
|
||||||
(Status.UserADC0Hi shl 8) or Status.UserADC0Lo,
|
SupplyV := ADCToSupplyVolts(
|
||||||
FNetwork.Device.BoardType);
|
(Status.UserADC0Hi shl 8) or Status.UserADC0Lo,
|
||||||
SupplyA := ADCToSupplyCurrent(
|
FNetwork.Device.BoardType)
|
||||||
(Status.UserADC1Hi shl 8) or Status.UserADC1Lo,
|
else
|
||||||
FNetwork.Device.BoardType);
|
SupplyV := ADCToSupplyVolts(
|
||||||
end else
|
(Status.SupplyVoltsHi shl 8) or Status.SupplyVoltsLo,
|
||||||
begin
|
FNetwork.Device.BoardType);
|
||||||
SupplyV := ADCToSupplyVolts(
|
|
||||||
(Status.SupplyVoltsHi shl 8) or Status.SupplyVoltsLo,
|
if BoardSupportsSupplyCurrent(FNetwork.Device.BoardType) then
|
||||||
FNetwork.Device.BoardType);
|
SupplyA := ADCToSupplyCurrent(
|
||||||
SupplyA := -1.0;
|
(Status.UserADC1Hi shl 8) or Status.UserADC1Lo,
|
||||||
|
FNetwork.Device.BoardType);
|
||||||
end;
|
end;
|
||||||
// FLastSMeter обновляется только из WDSP (GetSMeterDBm) в SpectrumTimerTick —
|
// FLastSMeter обновляется только из WDSP (GetSMeterDBm) в SpectrumTimerTick —
|
||||||
// ExciterPwr это мощность TX, не уровень принятого сигнала.
|
// ExciterPwr это мощность TX, не уровень принятого сигнала.
|
||||||
@@ -3188,14 +3292,17 @@ begin
|
|||||||
FLastSupplyV := SupplyV // первое значение — без сглаживания
|
FLastSupplyV := SupplyV // первое значение — без сглаживания
|
||||||
else
|
else
|
||||||
FLastSupplyV := ALPHA_SUPPLY * SupplyV + (1.0 - ALPHA_SUPPLY) * FLastSupplyV;
|
FLastSupplyV := ALPHA_SUPPLY * SupplyV + (1.0 - ALPHA_SUPPLY) * FLastSupplyV;
|
||||||
end;
|
end else
|
||||||
|
FLastSupplyV := -1.0;
|
||||||
|
|
||||||
if SupplyA >= 0 then
|
if SupplyA >= 0 then
|
||||||
begin
|
begin
|
||||||
if FLastSupplyA < 0 then
|
if FLastSupplyA < 0 then
|
||||||
FLastSupplyA := SupplyA
|
FLastSupplyA := SupplyA
|
||||||
else
|
else
|
||||||
FLastSupplyA := ALPHA_SUPPLY * SupplyA + (1.0 - ALPHA_SUPPLY) * FLastSupplyA;
|
FLastSupplyA := ALPHA_SUPPLY * SupplyA + (1.0 - ALPHA_SUPPLY) * FLastSupplyA;
|
||||||
end;
|
end else
|
||||||
|
FLastSupplyA := -1.0;
|
||||||
FLastPLLLock := PLLLock;
|
FLastPLLLock := PLLLock;
|
||||||
|
|
||||||
// Hardware PTT (foot switch / mic PTT) — обнаружение фронта (нужно делать
|
// Hardware PTT (foot switch / mic PTT) — обнаружение фронта (нужно делать
|
||||||
@@ -3229,33 +3336,84 @@ end;
|
|||||||
|
|
||||||
procedure TMainForm.UpdateTXMeters;
|
procedure TMainForm.UpdateTXMeters;
|
||||||
// Вызывается из MeterTimerTick (10 Гц). Использует FLastFwdW/FLastSWR/
|
// Вызывается из MeterTimerTick (10 Гц). Использует FLastFwdW/FLastSWR/
|
||||||
// FLastSupplyV/FLastPLLLock — они обновляются на rate HP Status (~150/с)
|
// FLastSupplyV/FLastSupplyA обновляются на rate HP Status (~150/с)
|
||||||
// в DoUpdateStatus.
|
// в DoUpdateStatus.
|
||||||
var
|
|
||||||
PLLStr: string;
|
|
||||||
begin
|
begin
|
||||||
if FLastPLLLock then PLLStr := 'PLL OK' else PLLStr := 'PLL?';
|
if not FRunning then
|
||||||
|
begin
|
||||||
|
SetStatusText(3, 'Supply --');
|
||||||
|
SetStatusText(7, 'PLL --');
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if FLastPLLLock then
|
||||||
|
SetStatusText(7, 'PLL OK')
|
||||||
|
else
|
||||||
|
SetStatusText(7, 'PLL?');
|
||||||
|
|
||||||
|
if not BoardSupportsSupplyVoltage(FNetwork.Device.BoardType) then
|
||||||
|
begin
|
||||||
|
SetStatusText(3, 'Supply n/a');
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
if FLastSupplyV >= 0 then
|
if FLastSupplyV >= 0 then
|
||||||
begin
|
begin
|
||||||
if FLastSupplyA >= 0 then
|
if FLastSupplyA >= 0 then
|
||||||
StatusBar1.Panels[2].Text :=
|
SetStatusText(3,
|
||||||
Format('Supply: %.1fV / %.1fA | %s', [FLastSupplyV, FLastSupplyA, PLLStr])
|
Format('Supply %.1fV %.1fA', [FLastSupplyV, FLastSupplyA]))
|
||||||
else
|
else
|
||||||
StatusBar1.Panels[2].Text :=
|
SetStatusText(3,
|
||||||
Format('Supply: %.1fV | %s', [FLastSupplyV, PLLStr]);
|
Format('Supply %.1fV', [FLastSupplyV]));
|
||||||
end else
|
end else
|
||||||
StatusBar1.Panels[2].Text := PLLStr;
|
SetStatusText(3, 'Supply --');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnDDCIQCB(DDCIndex: Integer; const Data: TDDCIQPacket);
|
procedure TMainForm.OnDDCIQCB(DDCIndex: Integer; const Data: TDDCIQPacket);
|
||||||
var
|
var
|
||||||
SamplesPerFrame: Integer;
|
SamplesPerFrame: Integer;
|
||||||
|
Seq, ExpectedSeq: LongWord;
|
||||||
|
Delta: Int64;
|
||||||
begin
|
begin
|
||||||
// Вызывается из сетевого потока — UI не трогаем напрямую
|
// Вызывается из сетевого потока — UI не трогаем напрямую
|
||||||
|
|
||||||
// Счётчик принятых пакетов
|
// Счётчик принятых пакетов
|
||||||
Inc(FRXPacketCount);
|
Inc(FRXPacketCount);
|
||||||
FRXLastPktTime := GetTickCount64; // фиксируем время последнего пакета
|
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
|
// 1. Подаём IQ данные в DSP — только с активного DDC
|
||||||
if (DDCIndex = FActiveDDC) and FWDSPReady then
|
if (DDCIndex = FActiveDDC) and FWDSPReady then
|
||||||
@@ -3266,16 +3424,14 @@ begin
|
|||||||
FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame);
|
FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 2. Запоминаем последний seq — StatusBar обновит таймер (без Synchronize в горячем пути)
|
// 2. Запоминаем последний seq — статус обновит таймер (без Synchronize в горячем пути)
|
||||||
FLastDDCSeq := (LongWord(Data.Seq[0]) shl 24) or (LongWord(Data.Seq[1]) shl 16)
|
FLastDDCSeq := Seq;
|
||||||
or (LongWord(Data.Seq[2]) shl 8) or LongWord(Data.Seq[3]);
|
|
||||||
FLastDDCIndex := DDCIndex;
|
FLastDDCIndex := DDCIndex;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord);
|
procedure TMainForm.DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord);
|
||||||
begin
|
begin
|
||||||
StatusBar1.Panels[3].Text :=
|
SetStatusText(4, 'RX running');
|
||||||
Format('RX DDC%d | seq=%d | pkts=%d', [DDCIdx, Seq, FRXPacketCount]);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnMicPacketCB(const Data: TMicDataPacket);
|
procedure TMainForm.OnMicPacketCB(const Data: TMicDataPacket);
|
||||||
@@ -3494,15 +3650,15 @@ procedure TMainForm.DoUpdateDDCSeqOrNoDevice(DDCIdx: Integer; Seq: LongWord);
|
|||||||
begin
|
begin
|
||||||
if DDCIdx = -1 then
|
if DDCIdx = -1 then
|
||||||
begin
|
begin
|
||||||
if Assigned(FDeviceDialog) then
|
if Assigned(FDeviceDialog) then
|
||||||
begin
|
begin
|
||||||
FDeviceDialog.ClearDiscovered;
|
FDeviceDialog.ClearDiscovered;
|
||||||
FDeviceDialog.AddDiscovered('', '-- no device found --');
|
FDeviceDialog.AddDiscovered('', '-- no device found --');
|
||||||
end;
|
end;
|
||||||
StatusBar1.Panels[4].Text := 'No hardware found';
|
SetStatusText(2, 'No hardware found');
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
StatusBar1.Panels[3].Text := Format('DDC%d seq=%d', [DDCIdx, Seq]);
|
SetStatusText(4, 'RX running');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.RecreateDSPEngine(ASampleRate: Integer);
|
procedure TMainForm.RecreateDSPEngine(ASampleRate: Integer);
|
||||||
@@ -3546,7 +3702,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
BtnStartStop.Enabled := False;
|
BtnStartStop.Enabled := False;
|
||||||
StatusBar1.Panels[3].Text := 'Creating WDSP wisdom...';
|
SetStatusText(2, 'Creating WDSP wisdom...');
|
||||||
|
|
||||||
BuildThread := TWisdomBuildThread.Create(WisdomDir);
|
BuildThread := TWisdomBuildThread.Create(WisdomDir);
|
||||||
try
|
try
|
||||||
@@ -3577,9 +3733,9 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
if Result then
|
if Result then
|
||||||
StatusBar1.Panels[3].Text := 'WDSP wisdom ready'
|
SetStatusText(2, 'DSP ready')
|
||||||
else
|
else
|
||||||
StatusBar1.Panels[3].Text := 'WDSP wisdom missing';
|
SetStatusText(2, 'WDSP wisdom missing');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.BtnDiscoverClick(Sender: TObject);
|
procedure TMainForm.BtnDiscoverClick(Sender: TObject);
|
||||||
@@ -3601,7 +3757,7 @@ procedure TMainForm.BtnDiscoverFromDialog(Sender: TObject);
|
|||||||
begin
|
begin
|
||||||
SetLength(FDevices, 0);
|
SetLength(FDevices, 0);
|
||||||
FDeviceCount := 0;
|
FDeviceCount := 0;
|
||||||
StatusBar1.Panels[4].Text := 'Discovering...';
|
SetStatusText(2, 'Discovering...');
|
||||||
FNetwork.DirectIP := '';
|
FNetwork.DirectIP := '';
|
||||||
TDiscoverThread.Create(FNetwork, Self);
|
TDiscoverThread.Create(FNetwork, Self);
|
||||||
end;
|
end;
|
||||||
@@ -3632,7 +3788,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
FRXPacketCount := 0;
|
FRXPacketCount := 0;
|
||||||
FActiveDDC := 0;
|
FActiveDDC := 0;
|
||||||
StatusBar1.Panels[3].Text := 'RX: waiting...';
|
|
||||||
// Сохраняем текущую частоту/настройки чтобы следующий START восстановил их
|
// Сохраняем текущую частоту/настройки чтобы следующий START восстановил их
|
||||||
if FDevConnected then
|
if FDevConnected then
|
||||||
begin
|
begin
|
||||||
@@ -3652,9 +3807,7 @@ begin
|
|||||||
FNetwork.Disconnect;
|
FNetwork.Disconnect;
|
||||||
BtnStartStop.Caption := 'START';
|
BtnStartStop.Caption := 'START';
|
||||||
StyleButton(BtnStartStop, False);
|
StyleButton(BtnStartStop, False);
|
||||||
StatusBar1.Panels[4].Text := 'Not connected';
|
SetRadioOfflineStatus('Disconnected', True);
|
||||||
StatusBar1.Panels[0].Text := 'IP: --';
|
|
||||||
StatusBar1.Panels[1].Text := 'Board: --';
|
|
||||||
BtnMOX.Enabled := False;
|
BtnMOX.Enabled := False;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
@@ -3727,7 +3880,7 @@ begin
|
|||||||
// --- Открываем WDSP асинхронно чтобы не блокировать UI ---
|
// --- Открываем WDSP асинхронно чтобы не блокировать UI ---
|
||||||
FPendingDev := Dev;
|
FPendingDev := Dev;
|
||||||
BtnStartStop.Enabled := False;
|
BtnStartStop.Enabled := False;
|
||||||
StatusBar1.Panels[3].Text := 'Opening WDSP...';
|
SetStatusText(2, 'Opening DSP...');
|
||||||
if FWDSPReady then
|
if FWDSPReady then
|
||||||
// WDSP уже открыт — сразу подключаемся
|
// WDSP уже открыт — сразу подключаемся
|
||||||
DoConnectDevice(Dev)
|
DoConnectDevice(Dev)
|
||||||
@@ -3742,7 +3895,7 @@ begin
|
|||||||
FWDSPReady := Success;
|
FWDSPReady := Success;
|
||||||
if not Success then
|
if not Success then
|
||||||
begin
|
begin
|
||||||
StatusBar1.Panels[3].Text := 'WDSP open failed';
|
SetStatusText(2, 'DSP failed');
|
||||||
BtnStartStop.Enabled := True;
|
BtnStartStop.Enabled := True;
|
||||||
BtnStartStop.Caption := 'START';
|
BtnStartStop.Caption := 'START';
|
||||||
StyleButton(BtnStartStop, False);
|
StyleButton(BtnStartStop, False);
|
||||||
@@ -3889,8 +4042,8 @@ begin
|
|||||||
and FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then
|
and FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then
|
||||||
ActivateXvtrBand(G_Settings.LastXvtr);
|
ActivateXvtrBand(G_Settings.LastXvtr);
|
||||||
|
|
||||||
StatusBar1.Panels[0].Text := 'IP: ' + FNetwork.Device.IPAddress;
|
SetStatusText(1, 'IP: ' + FNetwork.Device.IPAddress);
|
||||||
StatusBar1.Panels[1].Text := 'Board: ' + BoardTypeName(FNetwork.Device.BoardType);
|
SetStatusText(0, 'Board: ' + BoardTypeName(FNetwork.Device.BoardType));
|
||||||
|
|
||||||
// General packet
|
// General packet
|
||||||
FillChar(GenPkt, SizeOf(GenPkt), 0);
|
FillChar(GenPkt, SizeOf(GenPkt), 0);
|
||||||
@@ -3937,6 +4090,13 @@ begin
|
|||||||
FRunning := True;
|
FRunning := True;
|
||||||
FRXStartTime := GetTickCount64;
|
FRXStartTime := GetTickCount64;
|
||||||
FRXLastPktTime := 0;
|
FRXLastPktTime := 0;
|
||||||
|
FRXPacketCount := 0;
|
||||||
|
FillChar(FDDCSeqValid, SizeOf(FDDCSeqValid), 0);
|
||||||
|
FSeqErrorCount := 0;
|
||||||
|
FLastSeqErrorDDC := -1;
|
||||||
|
FLastSeqErrorDelta := 0;
|
||||||
|
FSeqOkStreak := 0;
|
||||||
|
SetStatusText(6, 'SEQ OK');
|
||||||
|
|
||||||
// DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах.
|
// DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах.
|
||||||
// DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FTXSettings.
|
// DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FTXSettings.
|
||||||
@@ -3952,7 +4112,7 @@ begin
|
|||||||
|
|
||||||
BtnStartStop.Caption := 'STOP';
|
BtnStartStop.Caption := 'STOP';
|
||||||
StyleButton(BtnStartStop, True);
|
StyleButton(BtnStartStop, True);
|
||||||
StatusBar1.Panels[4].Text := 'RUNNING';
|
SetStatusText(2, 'Running');
|
||||||
BtnMOX.Enabled := True;
|
BtnMOX.Enabled := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -6678,13 +6838,8 @@ begin
|
|||||||
FAudioOut.DeviceIndex := DevIndex;
|
FAudioOut.DeviceIndex := DevIndex;
|
||||||
FAudioOutDevName := DevName;
|
FAudioOutDevName := DevName;
|
||||||
try
|
try
|
||||||
if not FAudioOut.Open then
|
FAudioOut.Open;
|
||||||
StatusBar1.Panels[3].Text := 'Audio out: ' + FAudioOut.LastError
|
|
||||||
else
|
|
||||||
StatusBar1.Panels[3].Text := 'Audio out: ' + IfThen(DevName = '', 'Default', DevName);
|
|
||||||
except
|
except
|
||||||
on E: Exception do
|
|
||||||
StatusBar1.Panels[3].Text := 'Audio out err: ' + E.Message;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -6697,17 +6852,10 @@ begin
|
|||||||
if DevIndex >= 0 then
|
if DevIndex >= 0 then
|
||||||
begin
|
begin
|
||||||
try
|
try
|
||||||
if not FAudioIn.Open then
|
FAudioIn.Open;
|
||||||
StatusBar1.Panels[3].Text := 'Mic: ' + FAudioIn.LastError
|
|
||||||
else
|
|
||||||
StatusBar1.Panels[3].Text := 'Mic: ' + DevName;
|
|
||||||
except
|
except
|
||||||
on E: Exception do
|
|
||||||
StatusBar1.Panels[3].Text := 'Mic err: ' + E.Message;
|
|
||||||
end;
|
end;
|
||||||
end
|
end;
|
||||||
else
|
|
||||||
StatusBar1.Panels[3].Text := 'Mic: none';
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.ApplyVisibility(ShowSpectrum, ShowWaterfall: Boolean);
|
procedure TMainForm.ApplyVisibility(ShowSpectrum, ShowWaterfall: Boolean);
|
||||||
@@ -6830,15 +6978,9 @@ begin
|
|||||||
// Вызывается один раз через 200 мс после старта формы
|
// Вызывается один раз через 200 мс после старта формы
|
||||||
FAfterShowTimer.Enabled := False;
|
FAfterShowTimer.Enabled := False;
|
||||||
|
|
||||||
// Открываем PortAudio — теперь FPC уже перехватывает сигналы
|
|
||||||
try
|
try
|
||||||
if not FAudioOut.Open then
|
FAudioOut.Open;
|
||||||
StatusBar1.Panels[3].Text := 'Audio: ' + FAudioOut.LastError
|
|
||||||
else
|
|
||||||
StatusBar1.Panels[3].Text := 'Audio OK (PortAudio)';
|
|
||||||
except
|
except
|
||||||
on E: Exception do
|
|
||||||
StatusBar1.Panels[3].Text := 'Audio: ' + E.Message;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
EnsureWDSPWisdom;
|
EnsureWDSPWisdom;
|
||||||
|
|||||||
Reference in New Issue
Block a user