feat(oc): OC Control — Open Collector выходы Penny/Alex (эталон Thetis)

Byte 1401 High Priority packet: per-band (HF 160m-6m) и per-slot (XVTR)
RX/TX-маски на 7 пинов + TX pin action (MOX/TUNE/2TON и комбинации).
Только openHPSDR — у Pluto/AD936x такого выхода нет, вкладка Settings
скрывается на этом бэкенде. Настройки — вкладка "OC Control" в SettingsForm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 13:47:04 +03:00
co-authored by Claude Sonnet 5
parent b6d2256416
commit 041bc6ba7a
8 changed files with 547 additions and 26 deletions
+98 -5
View File
@@ -169,6 +169,11 @@ type
FAlexEnabled: Boolean; FAlexEnabled: Boolean;
FAlexConfig: TAlexSettings; // per-band antenna + routing config FAlexConfig: TAlexSettings; // per-band antenna + routing config
FStepAtten: Byte; // ADC0 step attenuator 0-31 dB FStepAtten: Byte; // ADC0 step attenuator 0-31 dB
// ---- OC Control (Open Collector выходы Penny/Alex, byte 1401) ----
FOCConfig: TOCSettings;
FTuning: Boolean; // TUN активен (для TX pin action)
FTwoTone: Boolean; // 2TON активен (для TX pin action)
FXvtrSlot: Integer; // активный слот XVTR (0..7) или -1 = HF-группа
// ---- PureSignal ---- // ---- PureSignal ----
// FPSEnabled — фича вооружена (кнопка PS). FPSTXActive — активная PTT-фаза: // FPSEnabled — фича вооружена (кнопка PS). FPSTXActive — активная PTT-фаза:
// RebuildDDCSpecific включает feedback-DDC0 (ADC0) + sync DDC1 (TX DAC), // RebuildDDCSpecific включает feedback-DDC0 (ADC0) + sync DDC1 (TX DAC),
@@ -261,16 +266,21 @@ type
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double; procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
DriveLevel: Byte = 100); override; DriveLevel: Byte = 100); override;
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte; procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
Transmitting, PAEnabled, AlexEnabled: Boolean); override; Transmitting, PAEnabled, AlexEnabled: Boolean;
Tuning: Boolean = False; TwoTone: Boolean = False); override;
procedure SetStepAtten(dB: Byte); override; procedure SetStepAtten(dB: Byte); override;
procedure SetFreqCalPPM(P: Double); override; procedure SetFreqCalPPM(P: Double); override;
// Частота с поправкой опорника — для частотного слова DDC/DUC. // Частота с поправкой опорника — для частотного слова DDC/DUC.
function CalFreq(Hz: Double): Double; function CalFreq(Hz: Double): Double;
procedure SetAlexConfig(const A: TAlexSettings); override; procedure SetAlexConfig(const A: TAlexSettings); override;
// OC Control (Open Collector выходы) — конфиг применяется мгновенно.
procedure SetOCConfig(const O: TOCSettings); override;
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно // Включение/настройка XVTR-режима. Все параметры применяются мгновенно
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится, // (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
// антенна и Alex routing возвращаются к per-band дефолту. // антенна и Alex routing возвращаются к per-band дефолту. ASlot — активный
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); override; // слот трансвертера (0..7), для per-slot OC-масок; -1 если не применимо.
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte;
ASlot: Integer = -1); override;
procedure SendFullHP; override; procedure SendFullHP; override;
// Отдельная отправка PTT: переключает реле (bit27) и drive ДО, потом PTT. // Отдельная отправка PTT: переключает реле (bit27) и drive ДО, потом PTT.
// Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP. // Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP.
@@ -750,6 +760,10 @@ begin
FPAEnabled := True; FPAEnabled := True;
FAlexEnabled := True; FAlexEnabled := True;
TSettingsManager.DefaultAlex(FAlexConfig); TSettingsManager.DefaultAlex(FAlexConfig);
TSettingsManager.DefaultOC(FOCConfig);
FTuning := False;
FTwoTone := False;
FXvtrSlot := -1;
FSendLock := TCriticalSection.Create; FSendLock := TCriticalSection.Create;
FHPSendLock := TCriticalSection.Create; FHPSendLock := TCriticalSection.Create;
FDUCIQLock := TCriticalSection.Create; FDUCIQLock := TCriticalSection.Create;
@@ -1510,7 +1524,8 @@ begin
if FConnected then SendFullHP; if FConnected then SendFullHP;
end; end;
procedure THPSDRNetwork.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); procedure THPSDRNetwork.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte;
ASlot: Integer);
begin begin
FXvtrEnable := AEnable; FXvtrEnable := AEnable;
FXvtrDisablePA := AEnable and ADisablePA; FXvtrDisablePA := AEnable and ADisablePA;
@@ -1518,9 +1533,79 @@ begin
FXvtrRxAntOverride := ARxAnt FXvtrRxAntOverride := ARxAnt
else else
FXvtrRxAntOverride := 0; FXvtrRxAntOverride := 0;
if AEnable and (ASlot >= 0) then
FXvtrSlot := ASlot
else
FXvtrSlot := -1;
if FConnected then SendFullHP; if FConnected then SendFullHP;
end; end;
procedure THPSDRNetwork.SetOCConfig(const O: TOCSettings);
begin
FOCConfig := O;
if FConnected then SendFullHP;
end;
// Строит 8-битное слово OC (byte 1401 HP-пакета, биты 0..6 = пины 1..7).
// Портировано из Thetis (Penny.UpdateExtCtrl/adjustForTXAction), урезано:
// без PA-override и split-pins VFO A/B (см. project_oc_control память).
// RX: маска банда/слота как есть. TX: маска банда/слота, каждый включённый
// пин дополнительно гейтится своим TX pin action (MOX/TUNE/TWOTONE и их
// комбинации) — тот же tx-флаг остаётся True на всём протяжении TUN/2TON
// (см. TRadioController.SetTune/SetTwoTone), поэтому "чистый MOX" явно
// исключает Tuning/TwoTone.
function CalcOCBits(RXFreqHz, TXFreqHz: Double;
Transmitting, Tuning, TwoTone: Boolean;
XvtrSlot: Integer;
const OC: TOCSettings): Byte;
var
RxMask, TxMask: Byte;
Actions: TOCPinActions;
i: Integer;
Fire: Boolean;
Band: Integer;
begin
Result := 0;
if not OC.Enabled then Exit;
if (XvtrSlot >= 0) and (XvtrSlot < CFG_XVTR_COUNT) then
begin
RxMask := OC.RxMaskXvtr[XvtrSlot];
TxMask := OC.TxMaskXvtr[XvtrSlot];
Actions := OC.ActionXvtr;
end
else
begin
if Transmitting then Band := FreqToBandIndex(TXFreqHz)
else Band := FreqToBandIndex(RXFreqHz);
RxMask := OC.RxMaskHF[Band];
TxMask := OC.TxMaskHF[Band];
Actions := OC.ActionHF;
end;
if not Transmitting then
begin
Result := RxMask;
Exit;
end;
for i := 0 to OC_PIN_COUNT - 1 do
begin
if (TxMask and (1 shl i)) = 0 then Continue;
case Actions[i] of
OCA_MOX: Fire := (not Tuning) and (not TwoTone);
OCA_TUNE: Fire := Tuning;
OCA_TWOTONE: Fire := TwoTone;
OCA_MOX_TUNE: Fire := not TwoTone;
OCA_MOX_TWOTONE: Fire := not Tuning;
OCA_TUNE_TWOTONE: Fire := Tuning or TwoTone;
OCA_MOX_TUNE_TWOTONE: Fire := True;
else Fire := False;
end;
if Fire then Result := Result or (1 shl i);
end;
end;
function THPSDRNetwork.DDCBaseIndex: Integer; function THPSDRNetwork.DDCBaseIndex: Integer;
begin begin
// Для ANGELIA/ORION/ORION2/SATURN DDC начинается с индекса 2 (DDC0/1 — PureSignal) // Для ANGELIA/ORION/ORION2/SATURN DDC начинается с индекса 2 (DDC0/1 — PureSignal)
@@ -1690,7 +1775,8 @@ end;
procedure THPSDRNetwork.UpdateState(RXFreqHz, TXFreqHz: Double; procedure THPSDRNetwork.UpdateState(RXFreqHz, TXFreqHz: Double;
DriveLevel: Byte; DriveLevel: Byte;
Transmitting, PAEnabled, AlexEnabled: Boolean); Transmitting, PAEnabled, AlexEnabled: Boolean;
Tuning: Boolean; TwoTone: Boolean);
begin begin
FCurrentRXFreq := RXFreqHz; FCurrentRXFreq := RXFreqHz;
FCurrentTXFreq := TXFreqHz; FCurrentTXFreq := TXFreqHz;
@@ -1698,6 +1784,8 @@ begin
FIsTransmitting := Transmitting; FIsTransmitting := Transmitting;
FPAEnabled := PAEnabled; FPAEnabled := PAEnabled;
FAlexEnabled := AlexEnabled; FAlexEnabled := AlexEnabled;
FTuning := Tuning;
FTwoTone := TwoTone;
end; end;
function THPSDRNetwork.CalFreq(Hz: Double): Double; function THPSDRNetwork.CalFreq(Hz: Double): Double;
@@ -1847,6 +1935,11 @@ begin
if not FSpeakerAudioEnabled then if not FSpeakerAudioEnabled then
Buf[1400] := Buf[1400] or $02; Buf[1400] := Buf[1400] or $02;
// OC Control — Open Collector outputs (byte 1401)
if FOCConfig.Enabled then
Buf[1401] := CalcOCBits(FCurrentRXFreq, FCurrentTXFreq, FIsTransmitting,
FTuning, FTwoTone, FXvtrSlot, FOCConfig);
// Step attenuators ADC0/ADC1 (bytes 1443/1442) // Step attenuators ADC0/ADC1 (bytes 1443/1442)
// 0 dB = 0, max 31 dB // 0 dB = 0, max 31 dB
Buf[1443] := FStepAtten; // ADC0 attenuation Buf[1443] := FStepAtten; // ADC0 attenuation
+25 -6
View File
@@ -591,6 +591,7 @@ type
// восстанавливаются позже и должны встать на свои звуковухи. // восстанавливаются позже и должны встать на свои звуковухи.
procedure ApplySliceSlotAudio; procedure ApplySliceSlotAudio;
procedure OnAlexSettingsChange(const A: TAlexSettings); procedure OnAlexSettingsChange(const A: TAlexSettings);
procedure OnOCSettingsChange(const O: TOCSettings);
procedure OnXvtrSettingsChange(const X: TXvtrSettings); procedure OnXvtrSettingsChange(const X: TXvtrSettings);
procedure PushXvtrToWeb; procedure PushXvtrToWeb;
procedure PushRatePresetsToWeb; // backend sample-rate пресеты → web procedure PushRatePresetsToWeb; // backend sample-rate пресеты → web
@@ -1234,6 +1235,7 @@ begin
// Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано). // Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано).
TSettingsManager.DefaultTX(FController.FTXSettings); TSettingsManager.DefaultTX(FController.FTXSettings);
TSettingsManager.DefaultAlex(FController.FAlexSettings); TSettingsManager.DefaultAlex(FController.FAlexSettings);
TSettingsManager.DefaultOC(FController.FOCSettings);
TSettingsManager.DefaultXvtr(FController.FXvtrSettings); TSettingsManager.DefaultXvtr(FController.FXvtrSettings);
FController.FCurrentXvtr := -1; FController.FCurrentXvtr := -1;
@@ -4404,7 +4406,7 @@ begin
if FController.FNetwork.Connected and FController.FNetwork.Running then if FController.FNetwork.Connected and FController.FNetwork.Running then
begin begin
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel,
FController.FTransmitting, True, True); FController.FTransmitting, True, True, FController.FTuning, FController.FTwoTone);
FController.FNetwork.SendFullHP; FController.FNetwork.SendFullHP;
end; end;
end; end;
@@ -4426,7 +4428,7 @@ begin
if FController.FNetwork.Connected and FController.FNetwork.Running then if FController.FNetwork.Connected and FController.FNetwork.Running then
begin begin
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel,
FController.FTransmitting, True, True); FController.FTransmitting, True, True, FController.FTuning, FController.FTwoTone);
FController.FNetwork.SendFullHP; FController.FNetwork.SendFullHP;
end; end;
end; end;
@@ -4926,7 +4928,8 @@ begin
if FController.FNetwork.Connected and FController.FNetwork.Running then if FController.FNetwork.Connected and FController.FNetwork.Running then
begin begin
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq),
XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True,
FController.FTuning, FController.FTwoTone);
FController.FNetwork.SendFullHP; FController.FNetwork.SendFullHP;
end; end;
if FController.FCurrentXvtr < 0 then if FController.FCurrentXvtr < 0 then
@@ -7608,7 +7611,7 @@ begin
end; end;
FController.FDriveLevel := CalcDriveByte; FController.FDriveLevel := CalcDriveByte;
if FController.FNetwork.Connected and FController.FNetwork.Running then if FController.FNetwork.Connected and FController.FNetwork.Running then
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True, FController.FTuning, FController.FTwoTone);
end; end;
procedure TMainForm.OnPlutoTxMaxAttChange(MaxAttDb: Double); procedure TMainForm.OnPlutoTxMaxAttChange(MaxAttDb: Double);
@@ -7643,7 +7646,7 @@ begin
end; end;
FController.FDriveLevel := CalcDriveByte; FController.FDriveLevel := CalcDriveByte;
if FController.FNetwork.Connected and FController.FNetwork.Running then if FController.FNetwork.Connected and FController.FNetwork.Running then
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True, FController.FTuning, FController.FTwoTone);
end; end;
procedure TMainForm.OnTXSettingsChange(const T: TTXSettings); procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
@@ -7686,7 +7689,7 @@ begin
if FController.FNetwork.Connected and FController.FNetwork.Running then if FController.FNetwork.Connected and FController.FNetwork.Running then
begin begin
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel, FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FController.FDriveLevel,
FController.FTransmitting, True, True); FController.FTransmitting, True, True, FController.FTuning, FController.FTwoTone);
FController.FNetwork.SendFullHP; FController.FNetwork.SendFullHP;
end; end;
end; end;
@@ -7711,6 +7714,18 @@ begin
end; end;
end; end;
procedure TMainForm.OnOCSettingsChange(const O: TOCSettings);
begin
FController.FOCSettings := O;
if FController.FNetwork.Connected then
FController.FNetwork.SetOCConfig(O);
if FController.FDevConnected then
begin
FController.FSettings.SaveOC(FController.FDevMAC, O);
FController.FSettings.Save;
end;
end;
// =========================================================================== // ===========================================================================
// XVTR (Transverter) integration // XVTR (Transverter) integration
// =========================================================================== // ===========================================================================
@@ -8493,6 +8508,7 @@ begin
SF.OnTXChange := OnTXSettingsChange; SF.OnTXChange := OnTXSettingsChange;
SF.OnAlexChange := OnAlexSettingsChange; SF.OnAlexChange := OnAlexSettingsChange;
SF.OnXvtrChange := OnXvtrSettingsChange; SF.OnXvtrChange := OnXvtrSettingsChange;
SF.OnOCChange := OnOCSettingsChange;
SF.OnWfAGCNFChange := ApplyWfAGCNF; SF.OnWfAGCNFChange := ApplyWfAGCNF;
SF.OnADCChange := ApplyADCSettings; SF.OnADCChange := ApplyADCSettings;
SF.OnWebSettingsChange := ApplyWebSettings; SF.OnWebSettingsChange := ApplyWebSettings;
@@ -8511,6 +8527,9 @@ begin
SF.SetCalibrationBackend(FController.BackendCaps.Kind = bkPluto); SF.SetCalibrationBackend(FController.BackendCaps.Kind = bkPluto);
SF.LoadAnt936x(FController.Ant936x, FController.FXvtrSettings); SF.LoadAnt936x(FController.Ant936x, FController.FXvtrSettings);
SF.LoadXvtrSettings(FController.FXvtrSettings); SF.LoadXvtrSettings(FController.FXvtrSettings);
SF.LoadOCSettings(FController.FOCSettings);
// Вкладка OC Control — только openHPSDR, у Pluto/AD936x нет такого выхода.
SF.SetOCBackend(FController.BackendCaps.Kind = bkPluto);
SF.LoadPASettings(FController.FPAMaxPower, FController.FPABandCal); SF.LoadPASettings(FController.FPAMaxPower, FController.FPABandCal);
SF.LoadCalibration(FController.FCal); SF.LoadCalibration(FController.FCal);
SF.LoadPlutoTxMaxAtt(FController.FPlutoTxMaxAttDb); SF.LoadPlutoTxMaxAtt(FController.FPlutoTxMaxAttDb);
+3 -2
View File
@@ -170,7 +170,8 @@ type
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double; procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
DriveLevel: Byte = 100); override; DriveLevel: Byte = 100); override;
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte; procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
Transmitting, PAEnabled, AlexEnabled: Boolean); override; Transmitting, PAEnabled, AlexEnabled: Boolean;
Tuning: Boolean = False; TwoTone: Boolean = False); override;
procedure SendFullHP; override; procedure SendFullHP; override;
procedure SendPTT(Active: Boolean); override; procedure SendPTT(Active: Boolean); override;
procedure SetAntPorts(RxPort, TxPort: Byte); override; procedure SetAntPorts(RxPort, TxPort: Byte); override;
@@ -1065,7 +1066,7 @@ begin
end; end;
procedure TPlutoBackend.UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte; procedure TPlutoBackend.UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
Transmitting, PAEnabled, AlexEnabled: Boolean); Transmitting, PAEnabled, AlexEnabled: Boolean; Tuning: Boolean; TwoTone: Boolean);
begin begin
FRXFreq := RXFreqHz; FRXFreq := RXFreqHz;
FTXFreq := TXFreqHz; FTXFreq := TXFreqHz;
+13 -3
View File
@@ -158,7 +158,8 @@ type
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double; procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
DriveLevel: Byte = 100); virtual; abstract; DriveLevel: Byte = 100); virtual; abstract;
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte; procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
Transmitting, PAEnabled, AlexEnabled: Boolean); virtual; abstract; Transmitting, PAEnabled, AlexEnabled: Boolean;
Tuning: Boolean = False; TwoTone: Boolean = False); virtual; abstract;
procedure SendFullHP; virtual; abstract; procedure SendFullHP; virtual; abstract;
procedure SendPTT(Active: Boolean); virtual; abstract; procedure SendPTT(Active: Boolean); virtual; abstract;
@@ -189,7 +190,11 @@ type
// (мощность там через DriveLevel/DUC). Db < 0. // (мощность там через DriveLevel/DUC). Db < 0.
procedure SetTxAtten(Db: Double); virtual; procedure SetTxAtten(Db: Double); virtual;
procedure SetAlexConfig(const A: TAlexSettings); virtual; procedure SetAlexConfig(const A: TAlexSettings); virtual;
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); virtual; // OC Control (Open Collector выходы Penny/Alex) — no-op по умолчанию
// (Pluto/AD936x не имеет такого выхода).
procedure SetOCConfig(const O: TOCSettings); virtual;
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte;
ASlot: Integer = -1); virtual;
procedure SendDDCAudio(const LeftRight: array of SmallInt); virtual; procedure SendDDCAudio(const LeftRight: array of SmallInt); virtual;
procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); virtual; procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); virtual;
procedure SetSpeakerAudio(AEnabled: Boolean); virtual; procedure SetSpeakerAudio(AEnabled: Boolean); virtual;
@@ -284,7 +289,12 @@ procedure TRadioBackend.SetAlexConfig(const A: TAlexSettings);
begin begin
end; end;
procedure TRadioBackend.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); procedure TRadioBackend.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte;
ASlot: Integer);
begin
end;
procedure TRadioBackend.SetOCConfig(const O: TOCSettings);
begin begin
end; end;
+12 -7
View File
@@ -407,6 +407,7 @@ type
// ---- Конфиги / устройство (per-MAC) ---- // ---- Конфиги / устройство (per-MAC) ----
FTXSettings: TTXSettings; FTXSettings: TTXSettings;
FAlexSettings: TAlexSettings; FAlexSettings: TAlexSettings;
FOCSettings: TOCSettings; // OC Control (Open Collector, openHPSDR-only)
// Антенные разъёмы AD936x (Pluto/LibreSDR) на диапазон/трансвертер. // Антенные разъёмы AD936x (Pluto/LibreSDR) на диапазон/трансвертер.
FAnt936x: TAD936xAnt; FAnt936x: TAD936xAnt;
FXvtrSettings: TXvtrSettings; FXvtrSettings: TXvtrSettings;
@@ -1187,7 +1188,7 @@ begin
if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
begin begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz),
FDriveLevel, FTransmitting, True, True); FDriveLevel, FTransmitting, True, True, FTuning, FTwoTone);
FNetwork.SendFullHP; FNetwork.SendFullHP;
end; end;
end; end;
@@ -4273,7 +4274,8 @@ begin
En := False; DisablePA := False; RxAnt := 0; En := False; DisablePA := False; RxAnt := 0;
end; end;
if Assigned(FNetwork) and FNetwork.Connected then if Assigned(FNetwork) and FNetwork.Connected then
FNetwork.SetXvtrMode(En, DisablePA, RxAnt); if En then FNetwork.SetXvtrMode(En, DisablePA, RxAnt, FCurrentXvtr)
else FNetwork.SetXvtrMode(En, DisablePA, RxAnt, -1);
end; end;
procedure TRadioController.ActivateXvtr(Idx: Integer); procedure TRadioController.ActivateXvtr(Idx: Integer);
@@ -4903,7 +4905,7 @@ begin
begin begin
FNetwork.ClearDUCIQQueue; FNetwork.ClearDUCIQQueue;
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz),
FDriveLevel, True, True, True); FDriveLevel, True, True, True, FTuning, FTwoTone);
FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT
FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле
// PureSignal: перевести feedback-DDC0/1 в TX-конфигурацию (частоты = TX, // PureSignal: перевести feedback-DDC0/1 в TX-конфигурацию (частоты = TX,
@@ -4982,7 +4984,7 @@ begin
// PureSignal: вернуть DDC0/1 в RX-конфигурацию (убрать feedback-DDC). // PureSignal: вернуть DDC0/1 в RX-конфигурацию (убрать feedback-DDC).
FNetwork.SetPureSignalTX(False); FNetwork.SetPureSignalTX(False);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz),
FDriveLevel, False, True, True); FDriveLevel, False, True, True, FTuning, FTwoTone);
FNetwork.ClearDUCIQQueue; FNetwork.ClearDUCIQQueue;
FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX) FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX)
end; end;
@@ -5023,7 +5025,7 @@ begin
if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
begin begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz),
FDriveLevel, FTransmitting, True, True); FDriveLevel, FTransmitting, True, True, FTuning, FTwoTone);
FNetwork.SendFullHP; FNetwork.SendFullHP;
end; end;
end; end;
@@ -5506,7 +5508,7 @@ begin
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread // Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
// (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1. // (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1.
// DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты. // DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты.
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FDriveLevel, False, True, True); FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FDriveLevel, False, True, True, FTuning, FTwoTone);
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel); FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
FRunning := True; FRunning := True;
FRXStartTime := GetTickCount64; FRXStartTime := GetTickCount64;
@@ -5557,7 +5559,7 @@ begin
// (3.3 UI пересоздаст по персисту при следующем старте). // (3.3 UI пересоздаст по персисту при следующем старте).
for p := 1 to MAX_PANS - 1 do for p := 1 to MAX_PANS - 1 do
RemovePanDDC(p); RemovePanDDC(p);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), 0, False, True, True); FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), 0, False, True, True, FTuning, FTwoTone);
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
FRunning := False; FRunning := False;
FTransmitting := False; FTransmitting := False;
@@ -5744,6 +5746,8 @@ begin
FSettings.LoadAlex(FDevMAC, FAlexSettings); FSettings.LoadAlex(FDevMAC, FAlexSettings);
FSettings.LoadAnt936x(FDevMAC, FAnt936x); FSettings.LoadAnt936x(FDevMAC, FAnt936x);
FNetwork.SetAlexConfig(FAlexSettings); FNetwork.SetAlexConfig(FAlexSettings);
FSettings.LoadOC(FDevMAC, FOCSettings);
FNetwork.SetOCConfig(FOCSettings); // no-op на Pluto (base-класс)
FSettings.LoadXvtr(FDevMAC, FXvtrSettings); FSettings.LoadXvtr(FDevMAC, FXvtrSettings);
FCurrentXvtr := -1; FCurrentXvtr := -1;
ApplyXvtrToNetwork; ApplyXvtrToNetwork;
@@ -5914,6 +5918,7 @@ begin
SaveGlobalSettings; SaveGlobalSettings;
FSettings.SaveAlex(FDevMAC, FAlexSettings); FSettings.SaveAlex(FDevMAC, FAlexSettings);
FSettings.SaveAnt936x(FDevMAC, FAnt936x); FSettings.SaveAnt936x(FDevMAC, FAnt936x);
FSettings.SaveOC(FDevMAC, FOCSettings);
// XVTR LastFreq → актуальная видимая частота в момент остановки. // XVTR LastFreq → актуальная видимая частота в момент остановки.
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA; FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
+108
View File
@@ -13,6 +13,16 @@ const
SETTINGS_FILE = 'hpsdr_settings.json'; SETTINGS_FILE = 'hpsdr_settings.json';
CFG_BAND_COUNT = 12; CFG_BAND_COUNT = 12;
CFG_XVTR_COUNT = 8; // количество XVTR-слотов (трансверторов) CFG_XVTR_COUNT = 8; // количество XVTR-слотов (трансверторов)
OC_PIN_COUNT = 7; // количество Open Collector выходов (Penny/Alex)
// TX pin action (byte-константы, как TXPinActions в Thetis Penny.cs):
// при каком состоянии передачи пин активен.
OCA_MOX = 0; // только обычный TX (не TUNE, не 2TON)
OCA_TUNE = 1; // только TUNE
OCA_TWOTONE = 2; // только 2TON
OCA_MOX_TUNE = 3; // TX или TUNE
OCA_MOX_TWOTONE = 4; // TX или 2TON
OCA_TUNE_TWOTONE = 5; // TUNE или 2TON
OCA_MOX_TUNE_TWOTONE = 6; // любое из трёх (дефолт)
CFG_MODE_MIN = RadioModes.MODE_MIN; CFG_MODE_MIN = RadioModes.MODE_MIN;
CFG_MODE_MAX = RadioModes.MODE_MAX; CFG_MODE_MAX = RadioModes.MODE_MAX;
CFG_PANS_MAX = 4; // потолок панадаптеров (пан 0 + доп.) — = WDSPEngine.MAX_PANS CFG_PANS_MAX = 4; // потолок панадаптеров (пан 0 + доп.) — = WDSPEngine.MAX_PANS
@@ -341,6 +351,26 @@ type
EnableXvtrHf: Boolean; // режим XVTR HF EnableXvtrHf: Boolean; // режим XVTR HF
end; end;
// OC Control (Open Collector выходы Penny/Alex, byte 1401 HP packet).
// Только openHPSDR — у AD936x (Pluto/LibreSDR) такого выхода нет.
// Хранятся в JSON-секции "oc" под MAC. Портировано из Thetis
// (Console/setup.cs UpdateOCBits + Console/HPSDR/Penny.cs), объём сокращён:
// без PA-override/hot-switching внешнего PA (в EWSDR нет такой концепции
// на панели) и без split-pins VFO A/B (у EWSDR мультислайсы).
// HF-группа — 11 диапазонов (0=160m..10=6m, как в TAlexSettings).
// VHF-группа — 8 слотов XVTR (аналог Thetis VHF0-13, но по нашим слотам).
TOCPinActions = array[0..OC_PIN_COUNT-1] of Byte; // OCA_* на пин
TOCSettings = record
Enabled: Boolean;
RxMaskHF: array[0..CFG_BAND_COUNT-1] of Byte; // 7-бит маска, биты 0..6 = пины 1..7
TxMaskHF: array[0..CFG_BAND_COUNT-1] of Byte;
ActionHF: TOCPinActions; // для HF TX-маски
RxMaskXvtr: array[0..CFG_XVTR_COUNT-1] of Byte;
TxMaskXvtr: array[0..CFG_XVTR_COUNT-1] of Byte;
ActionXvtr: TOCPinActions; // для XVTR TX-маски
end;
// Калибровки (вкладка Calibration): детектор мощности/КСВ Alex-моста, // Калибровки (вкладка Calibration): детектор мощности/КСВ Alex-моста,
// напряжение/ток питания, S-метр. Модель детектора задаёт константы моста // напряжение/ток питания, S-метр. Модель детектора задаёт константы моста
// (Vref/bridge_volt/ADC-offset, как в Thetis computeAlexFwdPower). // (Vref/bridge_volt/ADC-offset, как в Thetis computeAlexFwdPower).
@@ -526,6 +556,10 @@ type
class procedure DefaultAlex(out A: TAlexSettings); class procedure DefaultAlex(out A: TAlexSettings);
function LoadAlex(const MAC: array of Byte; out A: TAlexSettings): Boolean; function LoadAlex(const MAC: array of Byte; out A: TAlexSettings): Boolean;
procedure SaveAlex(const MAC: array of Byte; const A: TAlexSettings); procedure SaveAlex(const MAC: array of Byte; const A: TAlexSettings);
// OC Control settings per-device (JSON-секция "oc" под MAC).
class procedure DefaultOC(out O: TOCSettings);
function LoadOC(const MAC: array of Byte; out O: TOCSettings): Boolean;
procedure SaveOC(const MAC: array of Byte; const O: TOCSettings);
// XVTR (Transverter) settings per-device (JSON-секция "xvtr" под MAC). // XVTR (Transverter) settings per-device (JSON-секция "xvtr" под MAC).
class procedure DefaultXvtr(out X: TXvtrSettings); class procedure DefaultXvtr(out X: TXvtrSettings);
function LoadXvtr(const MAC: array of Byte; out X: TXvtrSettings): Boolean; function LoadXvtr(const MAC: array of Byte; out X: TXvtrSettings): Boolean;
@@ -1567,6 +1601,80 @@ begin
A.EnableXvtrHf := JB(O, 'enable_xvtr_hf', False); A.EnableXvtrHf := JB(O, 'enable_xvtr_hf', False);
end; end;
class procedure TSettingsManager.DefaultOC(out O: TOCSettings);
var i: Integer;
begin
FillChar(O, SizeOf(O), 0);
O.Enabled := False;
for i := 0 to CFG_BAND_COUNT - 1 do
begin
O.RxMaskHF[i] := 0;
O.TxMaskHF[i] := 0;
end;
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
O.RxMaskXvtr[i] := 0;
O.TxMaskXvtr[i] := 0;
end;
for i := 0 to OC_PIN_COUNT - 1 do
begin
O.ActionHF[i] := OCA_MOX_TUNE_TWOTONE;
O.ActionXvtr[i] := OCA_MOX_TUNE_TWOTONE;
end;
end;
function TSettingsManager.LoadOC(const MAC: array of Byte;
out O: TOCSettings): Boolean;
var MacStr: string; DevObj, Obj: TJSONObject; i: Integer;
begin
DefaultOC(O);
MacStr := MacToStr(MAC);
Result := FRoot.Find(MacStr) <> nil;
if not Result then Exit;
DevObj := GetDevObj(MacStr);
if DevObj.Find('oc') = nil then begin Result := False; Exit; end;
Obj := EnsureObj(DevObj, 'oc');
O.Enabled := JB(Obj, 'enabled', False);
for i := 0 to CFG_BAND_COUNT - 1 do
begin
O.RxMaskHF[i] := EnsureRange(JI(Obj, 'rx_hf_' + IntToStr(i), 0), 0, 127);
O.TxMaskHF[i] := EnsureRange(JI(Obj, 'tx_hf_' + IntToStr(i), 0), 0, 127);
end;
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
O.RxMaskXvtr[i] := EnsureRange(JI(Obj, 'rx_xvtr_' + IntToStr(i), 0), 0, 127);
O.TxMaskXvtr[i] := EnsureRange(JI(Obj, 'tx_xvtr_' + IntToStr(i), 0), 0, 127);
end;
for i := 0 to OC_PIN_COUNT - 1 do
begin
O.ActionHF[i] := EnsureRange(JI(Obj, 'action_hf_' + IntToStr(i), OCA_MOX_TUNE_TWOTONE), 0, 6);
O.ActionXvtr[i] := EnsureRange(JI(Obj, 'action_xvtr_' + IntToStr(i), OCA_MOX_TUNE_TWOTONE), 0, 6);
end;
end;
procedure TSettingsManager.SaveOC(const MAC: array of Byte;
const O: TOCSettings);
var Obj: TJSONObject; i: Integer;
begin
Obj := EnsureObj(GetDevObj(MacToStr(MAC)), 'oc');
JW(Obj, 'enabled', O.Enabled);
for i := 0 to CFG_BAND_COUNT - 1 do
begin
JW(Obj, 'rx_hf_' + IntToStr(i), Integer(O.RxMaskHF[i]));
JW(Obj, 'tx_hf_' + IntToStr(i), Integer(O.TxMaskHF[i]));
end;
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
JW(Obj, 'rx_xvtr_' + IntToStr(i), Integer(O.RxMaskXvtr[i]));
JW(Obj, 'tx_xvtr_' + IntToStr(i), Integer(O.TxMaskXvtr[i]));
end;
for i := 0 to OC_PIN_COUNT - 1 do
begin
JW(Obj, 'action_hf_' + IntToStr(i), Integer(O.ActionHF[i]));
JW(Obj, 'action_xvtr_' + IntToStr(i), Integer(O.ActionXvtr[i]));
end;
end;
class procedure TSettingsManager.DefaultAnt936x(out A: TAD936xAnt); class procedure TSettingsManager.DefaultAnt936x(out A: TAD936xAnt);
var i: Integer; var i: Integer;
begin begin
+285 -1
View File
@@ -85,6 +85,7 @@ type
const InDev: array of string) of object; const InDev: array of string) of object;
TOnAlexChange = procedure(const A: TAlexSettings) of object; TOnAlexChange = procedure(const A: TAlexSettings) of object;
TOnXvtrChange = procedure(const X: TXvtrSettings) of object; TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
TOnOCChange = procedure(const O: TOCSettings) of object;
{ TSettingsForm } { TSettingsForm }
TSettingsForm = class(TForm) TSettingsForm = class(TForm)
@@ -320,6 +321,26 @@ type
FXvtrConfig: TXvtrSettings; FXvtrConfig: TXvtrSettings;
FOnXvtrChange: TOnXvtrChange; FOnXvtrChange: TOnXvtrChange;
// ---- OC Control tab (Open Collector выходы Penny/Alex, openHPSDR-only) ----
FPageOC: TScrollBox;
FNavOC: TFlatButton;
FOCSubtitle: TLabel;
FOCEnabled: TFlatCheckBox;
// HF-группа: 11 диапазонов (0=160m..10=6m) × 7 пинов, RX/TX-маски
FOCHFRx: array[0..10, 0..OC_PIN_COUNT-1] of TFlatCheckBox;
FOCHFTx: array[0..10, 0..OC_PIN_COUNT-1] of TFlatCheckBox;
FOCHFAction: array[0..OC_PIN_COUNT-1] of TFlatComboBox;
FOCHFGrp: TPanel;
FOCHFActGrp: TPanel;
// VHF-группа = 8 слотов XVTR × 7 пинов, RX/TX-маски
FOCXvtrRx: array[0..CFG_XVTR_COUNT-1, 0..OC_PIN_COUNT-1] of TFlatCheckBox;
FOCXvtrTx: array[0..CFG_XVTR_COUNT-1, 0..OC_PIN_COUNT-1] of TFlatCheckBox;
FOCXvtrAction: array[0..OC_PIN_COUNT-1] of TFlatComboBox;
FOCXvtrGrp: TPanel;
FOCXvtrActGrp: TPanel;
FOCConfig: TOCSettings;
FOnOCChange: TOnOCChange;
// ---- QO-100 tab (редактирует зарезервированный слот QO100_SLOT) ---- // ---- QO-100 tab (редактирует зарезервированный слот QO100_SLOT) ----
FPageQO: TScrollBox; FPageQO: TScrollBox;
FNavQO: TFlatButton; FNavQO: TFlatButton;
@@ -431,6 +452,10 @@ type
procedure FireXvtrChange; procedure FireXvtrChange;
procedure OnXvtrAnyChange(Sender: TObject); procedure OnXvtrAnyChange(Sender: TObject);
procedure UpdateXvtrRowEnabled(Idx: Integer); procedure UpdateXvtrRowEnabled(Idx: Integer);
procedure BuildOCTab;
procedure CollectOCFromUI;
procedure FireOCChange;
procedure OnOCAnyChange(Sender: TObject);
procedure CollectTXFromUI; procedure CollectTXFromUI;
procedure FireTXChange; procedure FireTXChange;
procedure OnTXAnyChange(Sender: TObject); procedure OnTXAnyChange(Sender: TObject);
@@ -547,6 +572,9 @@ type
procedure LoadTXSettings(const T: TTXSettings; BoardType: Integer = 0); procedure LoadTXSettings(const T: TTXSettings; BoardType: Integer = 0);
procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer); procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
procedure LoadXvtrSettings(const X: TXvtrSettings); procedure LoadXvtrSettings(const X: TXvtrSettings);
procedure LoadOCSettings(const O: TOCSettings);
// Вкладка OC Control — только openHPSDR (у Pluto/AD936x нет такого выхода).
procedure SetOCBackend(IsPluto: Boolean);
procedure LoadWfAGCNF(AGC, NF: Boolean); procedure LoadWfAGCNF(AGC, NF: Boolean);
procedure LoadWfRender(Palette: Integer; Gamma: Double; procedure LoadWfRender(Palette: Integer; Gamma: Double;
ResLimit, SpeedDiv: Integer); ResLimit, SpeedDiv: Integer);
@@ -559,6 +587,7 @@ type
property OnSliceCATChange: TOnSliceCATChange read FOnSliceCATChange write FOnSliceCATChange; property OnSliceCATChange: TOnSliceCATChange read FOnSliceCATChange write FOnSliceCATChange;
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange; property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange; property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
property OnOCChange: TOnOCChange read FOnOCChange write FOnOCChange;
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange; property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
property OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange; property OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange;
property OnAnt936xChange: TOnAnt936xChange read FOnAnt936xChange write FOnAnt936xChange; property OnAnt936xChange: TOnAnt936xChange read FOnAnt936xChange write FOnAnt936xChange;
@@ -643,6 +672,10 @@ const
PA_BAND_NAMES: array[0..10] of string = PA_BAND_NAMES: array[0..10] of string =
('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m'); ('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
// TX pin action (OC Control) — имена в UI, значения = OCA_* из Settings.pas.
OC_ACTION_NAMES: array[0..6] of string = (
'MOX', 'TUNE', '2TON', 'MOX+TUNE', 'MOX+2TON', 'TUNE+2TON', 'MOX+TUNE+2TON');
// Раскладка вкладки Calibration — общая для построения (BuildCalibrationTab) // Раскладка вкладки Calibration — общая для построения (BuildCalibrationTab)
// и перекладки под активное железо (RelayoutCalTab). // и перекладки под активное железо (RelayoutCalTab).
CAL_MARGIN = 22; CAL_MARGIN = 22;
@@ -818,7 +851,8 @@ begin
FNavSlices := MakeNavButton('Slices', 398); FNavSlices := MakeNavButton('Slices', 398);
FNavAlex := MakeNavButton('Antenna / Alex', 434); FNavAlex := MakeNavButton('Antenna / Alex', 434);
FNavXvtr := MakeNavButton('Transverter', 470); FNavXvtr := MakeNavButton('Transverter', 470);
FNavQO := MakeNavButton('QO-100', 506); FNavOC := MakeNavButton('OC Control', 506);
FNavQO := MakeNavButton('QO-100', 542);
FContentPanel := TPanel.Create(Self); FContentPanel := TPanel.Create(Self);
FContentPanel.Parent := Self; FContentPanel.Parent := Self;
@@ -838,11 +872,13 @@ begin
FPageSlices := MakeScrollPage; FPageSlices := MakeScrollPage;
FPageAlex := MakeScrollPage; FPageAlex := MakeScrollPage;
FPageXvtr := MakeScrollPage; FPageXvtr := MakeScrollPage;
FPageOC := MakeScrollPage;
FPageQO := MakeScrollPage; FPageQO := MakeScrollPage;
FAlexBoardType := 0; FAlexBoardType := 0;
TSettingsManager.DefaultAlex(FAlexConfig); TSettingsManager.DefaultAlex(FAlexConfig);
TSettingsManager.DefaultXvtr(FXvtrConfig); TSettingsManager.DefaultXvtr(FXvtrConfig);
TSettingsManager.DefaultCalibration(FCalConfig); TSettingsManager.DefaultCalibration(FCalConfig);
TSettingsManager.DefaultOC(FOCConfig);
BuildAudioTab; BuildAudioTab;
BuildDisplayTab; BuildDisplayTab;
@@ -855,6 +891,7 @@ begin
BuildSlicesTab; BuildSlicesTab;
BuildAlexTab; BuildAlexTab;
BuildXvtrTab; BuildXvtrTab;
BuildOCTab;
BuildQOTab; BuildQOTab;
FBtnClose := TFlatButton.Create(Self); FBtnClose := TFlatButton.Create(Self);
@@ -889,6 +926,7 @@ begin
else if Sender = FNavSlices then SelectPage(FPageSlices, FNavSlices) else if Sender = FNavSlices then SelectPage(FPageSlices, FNavSlices)
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex) 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 = FNavOC then SelectPage(FPageOC, FNavOC)
else if Sender = FNavQO then SelectPage(FPageQO, FNavQO); else if Sender = FNavQO then SelectPage(FPageQO, FNavQO);
end; end;
@@ -906,6 +944,7 @@ begin
FPageSlices.Visible := APage = FPageSlices; FPageSlices.Visible := APage = FPageSlices;
FPageAlex.Visible := APage = FPageAlex; FPageAlex.Visible := APage = FPageAlex;
FPageXvtr.Visible := APage = FPageXvtr; FPageXvtr.Visible := APage = FPageXvtr;
FPageOC.Visible := APage = FPageOC;
FPageQO.Visible := APage = FPageQO; FPageQO.Visible := APage = FPageQO;
FNavAudio.Active := ANav = FNavAudio; FNavAudio.Active := ANav = FNavAudio;
@@ -920,6 +959,7 @@ begin
FNavSlices.Active := ANav = FNavSlices; FNavSlices.Active := ANav = FNavSlices;
FNavAlex.Active := ANav = FNavAlex; FNavAlex.Active := ANav = FNavAlex;
FNavXvtr.Active := ANav = FNavXvtr; FNavXvtr.Active := ANav = FNavXvtr;
FNavOC.Active := ANav = FNavOC;
FNavQO.Active := ANav = FNavQO; FNavQO.Active := ANav = FNavQO;
end; end;
@@ -4658,6 +4698,250 @@ begin
end; end;
end; end;
// ===========================================================================
// OC Control tab (Open Collector выходы Penny/Alex, byte 1401 HP-пакета).
// Портировано из Thetis (Console/setup.cs UpdateOCBits + Console/HPSDR/
// Penny.cs), объём сокращён — см. память project_oc_control: без PA-override/
// hot-switching внешнего PA и без split-pins VFO A/B. Только openHPSDR —
// вкладка целиком прячется на Pluto/AD936x (SetOCBackend).
// HF-группа = 11 диапазонов (как Alex/PA), VHF-группа = 8 слотов XVTR.
// ===========================================================================
procedure TSettingsForm.BuildOCTab;
const
MARGIN = 22;
PAD = 12;
HDR1_H = 22;
HDR2_H = 20;
ROW_H = 24;
PIN_W = 24;
BAND_X = PAD;
RX0_X = PAD + 50;
TX0_X = RX0_X + OC_PIN_COUNT * PIN_W + 20;
GRP_W = TX0_X + OC_PIN_COUNT * PIN_W + PAD;
DATA_Y0 = GRP_TITLE_H + HDR1_H + HDR2_H + 4;
HF_GRP_H = GRP_TITLE_H + HDR1_H + HDR2_H + 11 * ROW_H + PAD;
XVTR_GRP_H = GRP_TITLE_H + HDR1_H + HDR2_H + CFG_XVTR_COUNT * ROW_H + PAD;
ACT_ROW_H = 30;
ACT_GRP_W = 260;
ACT_GRP_H = GRP_TITLE_H + PAD + OC_PIN_COUNT * ACT_ROW_H;
HF_BAND_NAMES: array[0..10] of string =
('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
var
Grp, ActGrp: TPanel;
b, p: Integer;
Y: Integer;
Cmb: TFlatComboBox;
function MkChk(AParent: TWinControl; AX, AY: Integer): TFlatCheckBox;
begin
Result := TFlatCheckBox.Create(Self);
Result.Parent := AParent;
Result.Caption := '';
Result.SetBounds(AX, AY, PIN_W, 20);
Result.Font.Color := CLR_TEXT;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.OnChange := OnOCAnyChange;
end;
begin
FLoading := True;
try
with TLabel.Create(Self) do
begin
Parent := FPageOC;
Caption := 'OC Control';
SetBounds(MARGIN, 18, 500, 28);
Font.Name := UI_FONT; Font.Size := 16;
Font.Style := [fsBold]; Font.Color := CLR_TEXT;
end;
FOCSubtitle := TLabel.Create(Self);
FOCSubtitle.Parent := FPageOC;
FOCSubtitle.Caption :=
'Open Collector outputs (Penny/Alex board), per-band and per-transverter. openHPSDR only.';
FOCSubtitle.SetBounds(MARGIN, 48, 680, 20);
FOCSubtitle.Font.Name := UI_FONT;
FOCSubtitle.Font.Size := 9;
FOCSubtitle.Font.Color := CLR_TEXTDIM;
FOCEnabled := TFlatCheckBox.Create(Self);
FOCEnabled.Parent := FPageOC;
FOCEnabled.Caption := 'Enable OC Control';
FOCEnabled.SetBounds(MARGIN, 76, 220, 22);
FOCEnabled.Font.Color := CLR_TEXT;
FOCEnabled.Font.Name := UI_FONT;
FOCEnabled.Font.Size := 9;
FOCEnabled.OnChange := OnOCAnyChange;
// ---- HF band grid ----
Grp := MakeGroupPanel(FPageOC, 'HF Bands', MARGIN, 106, GRP_W, HF_GRP_H);
FOCHFGrp := Grp;
MakeLbl(Grp, 'Band', BAND_X, GRP_TITLE_H + 2, 44);
MakeLbl(Grp, 'RX pins', RX0_X, GRP_TITLE_H + 2, OC_PIN_COUNT * PIN_W);
MakeLbl(Grp, 'TX pins', TX0_X, GRP_TITLE_H + 2, OC_PIN_COUNT * PIN_W);
for p := 0 to OC_PIN_COUNT - 1 do
begin
MakeLbl(Grp, IntToStr(p + 1), RX0_X + p * PIN_W + 5, GRP_TITLE_H + HDR1_H + 2, PIN_W);
MakeLbl(Grp, IntToStr(p + 1), TX0_X + p * PIN_W + 5, GRP_TITLE_H + HDR1_H + 2, PIN_W);
end;
for b := 0 to 10 do
begin
Y := DATA_Y0 + b * ROW_H;
MakeLbl(Grp, HF_BAND_NAMES[b], BAND_X, Y + 2, 44);
for p := 0 to OC_PIN_COUNT - 1 do
begin
FOCHFRx[b, p] := MkChk(Grp, RX0_X + p * PIN_W, Y);
FOCHFTx[b, p] := MkChk(Grp, TX0_X + p * PIN_W, Y);
end;
end;
// ---- HF TX pin action ----
ActGrp := MakeGroupPanel(FPageOC, 'HF TX Pin Action',
MARGIN + GRP_W + 14, 106, ACT_GRP_W, ACT_GRP_H);
FOCHFActGrp := ActGrp;
for p := 0 to OC_PIN_COUNT - 1 do
begin
MakeLbl(ActGrp, 'Pin ' + IntToStr(p + 1), PAD, GRP_TITLE_H + PAD + p * ACT_ROW_H + 5, 44);
Cmb := MakeCombo(ActGrp, PAD + 50, GRP_TITLE_H + PAD + p * ACT_ROW_H, 190, OnOCAnyChange);
for b := 0 to 6 do Cmb.Items.Add(OC_ACTION_NAMES[b]);
Cmb.ItemIndex := OCA_MOX_TUNE_TWOTONE;
FOCHFAction[p] := Cmb;
end;
// ---- XVTR (VHF) slot grid ----
Grp := MakeGroupPanel(FPageOC, 'Transverter Slots', MARGIN, 106 + HF_GRP_H + 14,
GRP_W, XVTR_GRP_H);
FOCXvtrGrp := Grp;
MakeLbl(Grp, 'Slot', BAND_X, GRP_TITLE_H + 2, 44);
MakeLbl(Grp, 'RX pins', RX0_X, GRP_TITLE_H + 2, OC_PIN_COUNT * PIN_W);
MakeLbl(Grp, 'TX pins', TX0_X, GRP_TITLE_H + 2, OC_PIN_COUNT * PIN_W);
for p := 0 to OC_PIN_COUNT - 1 do
begin
MakeLbl(Grp, IntToStr(p + 1), RX0_X + p * PIN_W + 5, GRP_TITLE_H + HDR1_H + 2, PIN_W);
MakeLbl(Grp, IntToStr(p + 1), TX0_X + p * PIN_W + 5, GRP_TITLE_H + HDR1_H + 2, PIN_W);
end;
for b := 0 to CFG_XVTR_COUNT - 1 do
begin
Y := DATA_Y0 + b * ROW_H;
MakeLbl(Grp, 'XVTR' + IntToStr(b + 1), BAND_X, Y + 2, 48);
for p := 0 to OC_PIN_COUNT - 1 do
begin
FOCXvtrRx[b, p] := MkChk(Grp, RX0_X + p * PIN_W, Y);
FOCXvtrTx[b, p] := MkChk(Grp, TX0_X + p * PIN_W, Y);
end;
end;
// ---- XVTR TX pin action ----
ActGrp := MakeGroupPanel(FPageOC, 'Transverter TX Pin Action',
MARGIN + GRP_W + 14, 106 + HF_GRP_H + 14, ACT_GRP_W, ACT_GRP_H);
FOCXvtrActGrp := ActGrp;
for p := 0 to OC_PIN_COUNT - 1 do
begin
MakeLbl(ActGrp, 'Pin ' + IntToStr(p + 1), PAD, GRP_TITLE_H + PAD + p * ACT_ROW_H + 5, 44);
Cmb := MakeCombo(ActGrp, PAD + 50, GRP_TITLE_H + PAD + p * ACT_ROW_H, 190, OnOCAnyChange);
for b := 0 to 6 do Cmb.Items.Add(OC_ACTION_NAMES[b]);
Cmb.ItemIndex := OCA_MOX_TUNE_TWOTONE;
FOCXvtrAction[p] := Cmb;
end;
finally
FLoading := False;
end;
end;
procedure TSettingsForm.CollectOCFromUI;
var b, p: Integer; Mask: Byte;
begin
FOCConfig.Enabled := (FOCEnabled <> nil) and FOCEnabled.Checked;
for b := 0 to 10 do
begin
Mask := 0;
for p := 0 to OC_PIN_COUNT - 1 do
if (FOCHFRx[b, p] <> nil) and FOCHFRx[b, p].Checked then Mask := Mask or (1 shl p);
FOCConfig.RxMaskHF[b] := Mask;
Mask := 0;
for p := 0 to OC_PIN_COUNT - 1 do
if (FOCHFTx[b, p] <> nil) and FOCHFTx[b, p].Checked then Mask := Mask or (1 shl p);
FOCConfig.TxMaskHF[b] := Mask;
end;
for b := 0 to CFG_XVTR_COUNT - 1 do
begin
Mask := 0;
for p := 0 to OC_PIN_COUNT - 1 do
if (FOCXvtrRx[b, p] <> nil) and FOCXvtrRx[b, p].Checked then Mask := Mask or (1 shl p);
FOCConfig.RxMaskXvtr[b] := Mask;
Mask := 0;
for p := 0 to OC_PIN_COUNT - 1 do
if (FOCXvtrTx[b, p] <> nil) and FOCXvtrTx[b, p].Checked then Mask := Mask or (1 shl p);
FOCConfig.TxMaskXvtr[b] := Mask;
end;
for p := 0 to OC_PIN_COUNT - 1 do
begin
if (FOCHFAction[p] <> nil) and (FOCHFAction[p].ItemIndex >= 0) then
FOCConfig.ActionHF[p] := Byte(FOCHFAction[p].ItemIndex);
if (FOCXvtrAction[p] <> nil) and (FOCXvtrAction[p].ItemIndex >= 0) then
FOCConfig.ActionXvtr[p] := Byte(FOCXvtrAction[p].ItemIndex);
end;
end;
procedure TSettingsForm.FireOCChange;
begin
if FLoading then Exit;
CollectOCFromUI;
if Assigned(FOnOCChange) then
FOnOCChange(FOCConfig);
end;
procedure TSettingsForm.OnOCAnyChange(Sender: TObject);
begin
FireOCChange;
end;
procedure TSettingsForm.LoadOCSettings(const O: TOCSettings);
var b, p: Integer;
begin
FLoading := True;
try
FOCConfig := O;
if FOCEnabled <> nil then FOCEnabled.Checked := O.Enabled;
for b := 0 to 10 do
for p := 0 to OC_PIN_COUNT - 1 do
begin
if FOCHFRx[b, p] <> nil then
FOCHFRx[b, p].Checked := (O.RxMaskHF[b] and (1 shl p)) <> 0;
if FOCHFTx[b, p] <> nil then
FOCHFTx[b, p].Checked := (O.TxMaskHF[b] and (1 shl p)) <> 0;
end;
for b := 0 to CFG_XVTR_COUNT - 1 do
for p := 0 to OC_PIN_COUNT - 1 do
begin
if FOCXvtrRx[b, p] <> nil then
FOCXvtrRx[b, p].Checked := (O.RxMaskXvtr[b] and (1 shl p)) <> 0;
if FOCXvtrTx[b, p] <> nil then
FOCXvtrTx[b, p].Checked := (O.TxMaskXvtr[b] and (1 shl p)) <> 0;
end;
for p := 0 to OC_PIN_COUNT - 1 do
begin
if FOCHFAction[p] <> nil then FOCHFAction[p].ItemIndex := EnsureRange(O.ActionHF[p], 0, 6);
if FOCXvtrAction[p] <> nil then FOCXvtrAction[p].ItemIndex := EnsureRange(O.ActionXvtr[p], 0, 6);
end;
finally
FLoading := False;
end;
end;
procedure TSettingsForm.SetOCBackend(IsPluto: Boolean);
// У Pluto/AD936x нет Open Collector выхода — вкладка целиком скрывается,
// а не показывает пустое содержимое (в отличие от Antenna, где Pluto имеет
// свою таблицу разъёмов).
begin
FNavOC.Visible := not IsPluto;
if IsPluto and (FPageOC.Visible) then
SelectPage(FPageAudio, FNavAudio);
end;
// ── QO-100 page ───────────────────────────────────────────────────────────── // ── QO-100 page ─────────────────────────────────────────────────────────────
procedure TSettingsForm.BuildQOTab; procedure TSettingsForm.BuildQOTab;
+3 -2
View File
@@ -17,9 +17,9 @@
<UseVersionInfo Value="True"/> <UseVersionInfo Value="True"/>
<AutoIncrementBuild Value="True"/> <AutoIncrementBuild Value="True"/>
<MinorVersionNr Value="9"/> <MinorVersionNr Value="9"/>
<BuildNr Value="286"/> <BuildNr Value="287"/>
</VersionInfo> </VersionInfo>
<MacroValues Count="119"> <MacroValues Count="120">
<Macro1 Name="LCLWidgetType" Value="qt6"/> <Macro1 Name="LCLWidgetType" Value="qt6"/>
<Macro2 Name="LCLWidgetType" Value="qt6"/> <Macro2 Name="LCLWidgetType" Value="qt6"/>
<Macro3 Name="LCLWidgetType" Value="qt6"/> <Macro3 Name="LCLWidgetType" Value="qt6"/>
@@ -139,6 +139,7 @@
<Macro117 Name="LCLWidgetType" Value="qt6"/> <Macro117 Name="LCLWidgetType" Value="qt6"/>
<Macro118 Name="LCLWidgetType" Value="qt6"/> <Macro118 Name="LCLWidgetType" Value="qt6"/>
<Macro119 Name="LCLWidgetType" Value="qt6"/> <Macro119 Name="LCLWidgetType" Value="qt6"/>
<Macro120 Name="LCLWidgetType" Value="qt6"/>
</MacroValues> </MacroValues>
<BuildModes> <BuildModes>
<Item Name="Debug" Default="True"/> <Item Name="Debug" Default="True"/>