mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat: QO-100 mode — separate RX/TX LO + dedicated settings page
QO-100 reuses the transverter path (VFO/band-stack/beacon-lock untouched) with a split LO for the geostationary transponder: - TXvtrEntry += FullDuplexTx + TxLOOffset (persisted). Reserve the last XVTR slot (QO100_SLOT) for QO-100; migration restores the template when the slot is unset or has empty offsets (fixes old configs showing zeros). - XvtrTranslateTX: TX LO = visible - TxLOOffset (no LNB LOError) for QO-100; identical to RX translate for normal transverters. Switched all TX-side translate calls (controller + MainForm). - SetDuplex wired (live KeepRXDuringTX); auto display-duplex on QO-100 entry. - New "QO-100" settings page (LNB LO / transponder offset / downlink edges / RX gain / TX ceiling + live RX-IF/TX readout); QO slot hidden from the transverter table. Reuses OnXvtrChange — no MainForm wiring needed. - doc/PLUTO_INTEGRATION_PLAN.md: phase 5 done. Builds clean (headless + qt6 GUI). Not yet verified on hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+176
-7
@@ -243,6 +243,18 @@ type
|
||||
FXvtrConfig: TXvtrSettings;
|
||||
FOnXvtrChange: TOnXvtrChange;
|
||||
|
||||
// ---- QO-100 tab (редактирует зарезервированный слот QO100_SLOT) ----
|
||||
FPageQO: TScrollBox;
|
||||
FNavQO: TFlatButton;
|
||||
FQoEn: TFlatCheckBox;
|
||||
FQoLnbLO: TFlatFloatSpinEdit; // МГц — LNB LO (= LOOffset, RX)
|
||||
FQoTxLO: TFlatFloatSpinEdit; // МГц — транспондер-offset (= TxLOOffset)
|
||||
FQoBegin: TFlatFloatSpinEdit; // МГц — downlink begin
|
||||
FQoEnd: TFlatFloatSpinEdit; // МГц — downlink end
|
||||
FQoRXGain: TFlatFloatSpinEdit; // dB
|
||||
FQoPower: TFlatSpinEdit; // 0..100 TX drive ceiling
|
||||
FQoInfo: TLabel; // расчёт RX IF / TX freq
|
||||
|
||||
// ---- CAT tab controls ----
|
||||
FCATSerialEn: array[0..3] of TFlatCheckBox;
|
||||
FCATSerialPort: array[0..3] of TFlatEdit;
|
||||
@@ -304,6 +316,11 @@ type
|
||||
procedure OnAlexAnyChange(Sender: TObject);
|
||||
procedure OnAlexRxOnlyChange(Sender: TObject);
|
||||
procedure BuildXvtrTab;
|
||||
procedure BuildQOTab;
|
||||
procedure CollectQOFromUI; // QO-виджеты → FXvtrConfig[QO100_SLOT]
|
||||
procedure LoadQOFromConfig; // FXvtrConfig[QO100_SLOT] → QO-виджеты
|
||||
procedure UpdateQOInfo; // пересчёт RX IF / TX freq в инфо-метке
|
||||
procedure OnQOAnyChange(Sender: TObject);
|
||||
procedure CollectXvtrFromUI;
|
||||
procedure FireXvtrChange;
|
||||
procedure OnXvtrAnyChange(Sender: TObject);
|
||||
@@ -636,6 +653,7 @@ begin
|
||||
FNavCAT := MakeNavButton('CAT', 326);
|
||||
FNavAlex := MakeNavButton('Antenna / Alex', 362);
|
||||
FNavXvtr := MakeNavButton('Transverter', 398);
|
||||
FNavQO := MakeNavButton('QO-100', 434);
|
||||
|
||||
FContentPanel := TPanel.Create(Self);
|
||||
FContentPanel.Parent := Self;
|
||||
@@ -653,6 +671,7 @@ begin
|
||||
FPageCAT := MakeScrollPage;
|
||||
FPageAlex := MakeScrollPage;
|
||||
FPageXvtr := MakeScrollPage;
|
||||
FPageQO := MakeScrollPage;
|
||||
FAlexBoardType := 0;
|
||||
TSettingsManager.DefaultAlex(FAlexConfig);
|
||||
TSettingsManager.DefaultXvtr(FXvtrConfig);
|
||||
@@ -666,6 +685,7 @@ begin
|
||||
BuildCATTab;
|
||||
BuildAlexTab;
|
||||
BuildXvtrTab;
|
||||
BuildQOTab;
|
||||
|
||||
FBtnClose := TFlatButton.Create(Self);
|
||||
FBtnClose.Parent := Self;
|
||||
@@ -696,7 +716,8 @@ begin
|
||||
else if Sender = FNavAdvanced then SelectPage(FPageAdvanced, FNavAdvanced)
|
||||
else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT)
|
||||
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex)
|
||||
else if Sender = FNavXvtr then SelectPage(FPageXvtr, FNavXvtr);
|
||||
else if Sender = FNavXvtr then SelectPage(FPageXvtr, FNavXvtr)
|
||||
else if Sender = FNavQO then SelectPage(FPageQO, FNavQO);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.SelectPage(APage: TWinControl; ANav: TFlatButton);
|
||||
@@ -711,6 +732,7 @@ begin
|
||||
FPageCAT.Visible := APage = FPageCAT;
|
||||
FPageAlex.Visible := APage = FPageAlex;
|
||||
FPageXvtr.Visible := APage = FPageXvtr;
|
||||
FPageQO.Visible := APage = FPageQO;
|
||||
|
||||
FNavAudio.Active := ANav = FNavAudio;
|
||||
FNavDisplay.Active := ANav = FNavDisplay;
|
||||
@@ -722,6 +744,7 @@ begin
|
||||
FNavCAT.Active := ANav = FNavCAT;
|
||||
FNavAlex.Active := ANav = FNavAlex;
|
||||
FNavXvtr.Active := ANav = FNavXvtr;
|
||||
FNavQO.Active := ANav = FNavQO;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -3228,10 +3251,10 @@ begin
|
||||
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
|
||||
L.WordWrap := True;
|
||||
|
||||
// --- Group "Slots" ---
|
||||
// --- Group "Slots" --- (последний слот зарезервирован под QO-100 — не строим)
|
||||
GrpY := 100;
|
||||
Grp := MakeGroupPanel(FPageXvtr, 'Slots',
|
||||
MARGIN, GrpY, GRP_W, GRP_TITLE_H + HDR_H + CFG_XVTR_COUNT * ROW_H + 14);
|
||||
MARGIN, GrpY, GRP_W, GRP_TITLE_H + HDR_H + QO100_SLOT * ROW_H + 14);
|
||||
|
||||
// Заголовки колонок (под GRP_TITLE_H + небольшой отступ)
|
||||
Y := GRP_TITLE_H + 6;
|
||||
@@ -3247,8 +3270,8 @@ begin
|
||||
MakeHdr(Grp, 'Disable' + LineEnding + 'PA', PA_X, Y, PA_W);
|
||||
MakeHdr(Grp, 'RX' + LineEnding + 'Antenna', ANT_X, Y, ANT_W);
|
||||
|
||||
// Ряды слотов
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
// Ряды слотов (QO100_SLOT исключён — у него отдельная страница QO-100)
|
||||
for i := 0 to QO100_SLOT - 1 do
|
||||
begin
|
||||
Y := GRP_TITLE_H + HDR_H + i * ROW_H + 2;
|
||||
// Чекбокс EN центрируем по 24px ширине EN-колонки
|
||||
@@ -3276,7 +3299,7 @@ begin
|
||||
end;
|
||||
|
||||
// --- Group "Options" ---
|
||||
GrpY := GrpY + GRP_TITLE_H + HDR_H + CFG_XVTR_COUNT * ROW_H + 14 + 18;
|
||||
GrpY := GrpY + GRP_TITLE_H + HDR_H + QO100_SLOT * ROW_H + 14 + 18;
|
||||
GrpOpts := MakeGroupPanel(FPageXvtr, 'Options',
|
||||
MARGIN, GrpY, GRP_W, GRP_TITLE_H + 36);
|
||||
FChkXvtrTunPwr := TFlatCheckBox.Create(Self);
|
||||
@@ -3355,7 +3378,8 @@ procedure TSettingsForm.FireXvtrChange;
|
||||
var i: Integer;
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
CollectXvtrFromUI;
|
||||
CollectXvtrFromUI; // слоты 0..QO100_SLOT-1 (виджеты QO-слота не созданы → skip)
|
||||
CollectQOFromUI; // QO100_SLOT — из QO-страницы
|
||||
// Перерисовать enabled-состояние строк (если изменилась галка En)
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
UpdateXvtrRowEnabled(i);
|
||||
@@ -3400,9 +3424,154 @@ begin
|
||||
end;
|
||||
end;
|
||||
if FChkXvtrTunPwr <> nil then FChkXvtrTunPwr.Checked := X.UseXVTRTunePower;
|
||||
LoadQOFromConfig; // QO100_SLOT → отдельная страница QO-100
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
// ── QO-100 page ─────────────────────────────────────────────────────────────
|
||||
|
||||
procedure TSettingsForm.BuildQOTab;
|
||||
const
|
||||
MARGIN = 22;
|
||||
LBL_X = 14; LBL_W = 230;
|
||||
ED_X = 250; ED_W = 130;
|
||||
var
|
||||
Grp: TPanel;
|
||||
L: TLabel;
|
||||
Y: Integer;
|
||||
|
||||
function QoFloat(AY: Integer; AMin, AMax, AStep: Double; ADec: Integer): TFlatFloatSpinEdit;
|
||||
begin
|
||||
Result := TFlatFloatSpinEdit.Create(Self);
|
||||
Result.Parent := Grp;
|
||||
Result.SetBounds(ED_X, AY, ED_W, BTN_H);
|
||||
Result.MinValue := AMin; Result.MaxValue := AMax;
|
||||
Result.Increment := AStep; Result.DecimalPlaces := ADec;
|
||||
Result.Color := CLR_INPUT; Result.Font.Color := CLR_INPUT_TEXT;
|
||||
Result.Font.Name := UI_FONT; Result.Font.Size := 9;
|
||||
Result.OnChange := OnQOAnyChange;
|
||||
end;
|
||||
|
||||
procedure QoLabel(const Cap: string; AY: Integer);
|
||||
begin
|
||||
L := TLabel.Create(Self);
|
||||
L.Parent := Grp; L.Caption := Cap;
|
||||
L.SetBounds(LBL_X, AY + 4, LBL_W, 18);
|
||||
L.Font.Color := CLR_TEXTDIM; L.Font.Name := UI_FONT; L.Font.Size := 9;
|
||||
end;
|
||||
|
||||
begin
|
||||
FLoading := True;
|
||||
try
|
||||
L := TLabel.Create(Self);
|
||||
L.Parent := FPageQO; L.Caption := 'QO-100 (Es''hail-2 NB transponder)';
|
||||
L.SetBounds(MARGIN, 18, 600, 28);
|
||||
L.Font.Name := UI_FONT; L.Font.Size := 16;
|
||||
L.Font.Style := [fsBold]; L.Font.Color := CLR_TEXT;
|
||||
|
||||
L := TLabel.Create(Self);
|
||||
L.Parent := FPageQO;
|
||||
L.Caption := 'Geostationary transponder: RX downlink (~10489.5 MHz) via LNB, ' +
|
||||
'TX uplink (~2400 MHz) direct. VFO is displayed in downlink terms; ' +
|
||||
'RX and TX use separate LOs. Drift is corrected by the beacon lock.';
|
||||
L.SetBounds(MARGIN, 50, 700, 34);
|
||||
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
|
||||
L.WordWrap := True;
|
||||
|
||||
Grp := MakeGroupPanel(FPageQO, 'Transponder', MARGIN, 100, 560, GRP_TITLE_H + 7 * 30 + 16);
|
||||
|
||||
Y := GRP_TITLE_H + 8;
|
||||
FQoEn := TFlatCheckBox.Create(Self);
|
||||
FQoEn.Parent := Grp; FQoEn.Caption := 'Enable QO-100 band';
|
||||
FQoEn.SetBounds(LBL_X, Y, 300, 20);
|
||||
FQoEn.Font.Color := CLR_TEXT; FQoEn.Font.Name := UI_FONT; FQoEn.Font.Size := 9;
|
||||
FQoEn.OnChange := OnQOAnyChange;
|
||||
|
||||
Inc(Y, 32); QoLabel('LNB LO (MHz)', Y);
|
||||
FQoLnbLO := QoFloat(Y, 0.0, 50000.0, 0.001, 6);
|
||||
FQoLnbLO.Value := 9750.0;
|
||||
|
||||
Inc(Y, 30); QoLabel('Transponder offset = downlink - uplink (MHz)', Y);
|
||||
FQoTxLO := QoFloat(Y, 0.0, 50000.0, 0.001, 6);
|
||||
FQoTxLO.Value := 8089.5;
|
||||
|
||||
Inc(Y, 30); QoLabel('Downlink begin (MHz)', Y);
|
||||
FQoBegin := QoFloat(Y, 0.0, 50000.0, 0.001, 3);
|
||||
FQoBegin.Value := 10489.5;
|
||||
|
||||
Inc(Y, 30); QoLabel('Downlink end (MHz)', Y);
|
||||
FQoEnd := QoFloat(Y, 0.0, 50000.0, 0.001, 3);
|
||||
FQoEnd.Value := 10490.0;
|
||||
|
||||
Inc(Y, 30); QoLabel('RX gain offset (dB)', Y);
|
||||
FQoRXGain := QoFloat(Y, -60.0, 60.0, 0.1, 1);
|
||||
|
||||
Inc(Y, 30); QoLabel('TX drive ceiling (%)', Y);
|
||||
FQoPower := TFlatSpinEdit.Create(Self);
|
||||
FQoPower.Parent := Grp; FQoPower.SetBounds(ED_X, Y, ED_W, BTN_H);
|
||||
FQoPower.MinValue := 0; FQoPower.MaxValue := 100; FQoPower.Value := 100;
|
||||
FQoPower.Color := CLR_INPUT; FQoPower.Font.Color := CLR_INPUT_TEXT;
|
||||
FQoPower.Font.Name := UI_FONT; FQoPower.Font.Size := 9;
|
||||
FQoPower.OnChange := OnQOAnyChange;
|
||||
|
||||
FQoInfo := TLabel.Create(Self);
|
||||
FQoInfo.Parent := FPageQO;
|
||||
FQoInfo.SetBounds(MARGIN, 100 + GRP_TITLE_H + 7 * 30 + 28, 700, 52);
|
||||
FQoInfo.Font.Color := CLR_TEXTDIM; FQoInfo.Font.Name := 'Courier New';
|
||||
FQoInfo.Font.Size := 9; FQoInfo.WordWrap := True;
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.UpdateQOInfo;
|
||||
var dlMid, rxIf, txF: Double;
|
||||
begin
|
||||
if FQoInfo = nil then Exit;
|
||||
dlMid := (FQoBegin.Value + FQoEnd.Value) / 2.0; // МГц
|
||||
rxIf := dlMid - FQoLnbLO.Value; // RX IF в SDR
|
||||
txF := dlMid - FQoTxLO.Value; // TX uplink
|
||||
FQoInfo.Caption := Format(
|
||||
'At downlink %.3f MHz: RX IF = %.3f MHz TX = %.3f MHz',
|
||||
[dlMid, rxIf, txF]);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.CollectQOFromUI;
|
||||
begin
|
||||
if FQoEn = nil then Exit;
|
||||
FXvtrConfig.Entries[QO100_SLOT].Enabled := FQoEn.Checked;
|
||||
FXvtrConfig.Entries[QO100_SLOT].FullDuplexTx := True; // слот всегда QO-100
|
||||
FXvtrConfig.Entries[QO100_SLOT].ButtonText := 'QO-100';
|
||||
FXvtrConfig.Entries[QO100_SLOT].LOOffset := FQoLnbLO.Value * 1.0e6;
|
||||
FXvtrConfig.Entries[QO100_SLOT].TxLOOffset := FQoTxLO.Value * 1.0e6;
|
||||
FXvtrConfig.Entries[QO100_SLOT].FreqBegin := FQoBegin.Value * 1.0e6;
|
||||
FXvtrConfig.Entries[QO100_SLOT].FreqEnd := FQoEnd.Value * 1.0e6;
|
||||
FXvtrConfig.Entries[QO100_SLOT].RXGain := FQoRXGain.Value;
|
||||
FXvtrConfig.Entries[QO100_SLOT].TXPower := FQoPower.Value;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.LoadQOFromConfig;
|
||||
var E: TXvtrEntry;
|
||||
begin
|
||||
if FQoEn = nil then Exit;
|
||||
E := FXvtrConfig.Entries[QO100_SLOT];
|
||||
FQoEn.Checked := E.Enabled;
|
||||
FQoLnbLO.Value := E.LOOffset / 1.0e6;
|
||||
FQoTxLO.Value := E.TxLOOffset / 1.0e6;
|
||||
FQoBegin.Value := E.FreqBegin / 1.0e6;
|
||||
FQoEnd.Value := E.FreqEnd / 1.0e6;
|
||||
FQoRXGain.Value := E.RXGain;
|
||||
FQoPower.Value := EnsureRange(E.TXPower, 0, 100);
|
||||
UpdateQOInfo;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.OnQOAnyChange(Sender: TObject);
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
UpdateQOInfo;
|
||||
FireXvtrChange;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user