diff --git a/HPSDRNetwork.pas b/HPSDRNetwork.pas
index 8de2900..6a7680a 100644
--- a/HPSDRNetwork.pas
+++ b/HPSDRNetwork.pas
@@ -169,6 +169,11 @@ type
FAlexEnabled: Boolean;
FAlexConfig: TAlexSettings; // per-band antenna + routing config
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 ----
// FPSEnabled — фича вооружена (кнопка PS). FPSTXActive — активная PTT-фаза:
// RebuildDDCSpecific включает feedback-DDC0 (ADC0) + sync DDC1 (TX DAC),
@@ -261,16 +266,21 @@ type
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
DriveLevel: Byte = 100); override;
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 SetFreqCalPPM(P: Double); override;
// Частота с поправкой опорника — для частотного слова DDC/DUC.
function CalFreq(Hz: Double): Double;
procedure SetAlexConfig(const A: TAlexSettings); override;
+ // OC Control (Open Collector выходы) — конфиг применяется мгновенно.
+ procedure SetOCConfig(const O: TOCSettings); override;
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
- // антенна и Alex routing возвращаются к per-band дефолту.
- procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); override;
+ // антенна и Alex routing возвращаются к per-band дефолту. ASlot — активный
+ // слот трансвертера (0..7), для per-slot OC-масок; -1 если не применимо.
+ procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte;
+ ASlot: Integer = -1); override;
procedure SendFullHP; override;
// Отдельная отправка PTT: переключает реле (bit27) и drive ДО, потом PTT.
// Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP.
@@ -750,6 +760,10 @@ begin
FPAEnabled := True;
FAlexEnabled := True;
TSettingsManager.DefaultAlex(FAlexConfig);
+ TSettingsManager.DefaultOC(FOCConfig);
+ FTuning := False;
+ FTwoTone := False;
+ FXvtrSlot := -1;
FSendLock := TCriticalSection.Create;
FHPSendLock := TCriticalSection.Create;
FDUCIQLock := TCriticalSection.Create;
@@ -1510,7 +1524,8 @@ begin
if FConnected then SendFullHP;
end;
-procedure THPSDRNetwork.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte);
+procedure THPSDRNetwork.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte;
+ ASlot: Integer);
begin
FXvtrEnable := AEnable;
FXvtrDisablePA := AEnable and ADisablePA;
@@ -1518,9 +1533,79 @@ begin
FXvtrRxAntOverride := ARxAnt
else
FXvtrRxAntOverride := 0;
+ if AEnable and (ASlot >= 0) then
+ FXvtrSlot := ASlot
+ else
+ FXvtrSlot := -1;
if FConnected then SendFullHP;
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;
begin
// Для ANGELIA/ORION/ORION2/SATURN DDC начинается с индекса 2 (DDC0/1 — PureSignal)
@@ -1690,7 +1775,8 @@ end;
procedure THPSDRNetwork.UpdateState(RXFreqHz, TXFreqHz: Double;
DriveLevel: Byte;
- Transmitting, PAEnabled, AlexEnabled: Boolean);
+ Transmitting, PAEnabled, AlexEnabled: Boolean;
+ Tuning: Boolean; TwoTone: Boolean);
begin
FCurrentRXFreq := RXFreqHz;
FCurrentTXFreq := TXFreqHz;
@@ -1698,6 +1784,8 @@ begin
FIsTransmitting := Transmitting;
FPAEnabled := PAEnabled;
FAlexEnabled := AlexEnabled;
+ FTuning := Tuning;
+ FTwoTone := TwoTone;
end;
function THPSDRNetwork.CalFreq(Hz: Double): Double;
@@ -1847,6 +1935,11 @@ begin
if not FSpeakerAudioEnabled then
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)
// 0 dB = 0, max 31 dB
Buf[1443] := FStepAtten; // ADC0 attenuation
diff --git a/MainForm.pas b/MainForm.pas
index 5fc2b96..5c317bb 100644
--- a/MainForm.pas
+++ b/MainForm.pas
@@ -591,6 +591,7 @@ type
// восстанавливаются позже и должны встать на свои звуковухи.
procedure ApplySliceSlotAudio;
procedure OnAlexSettingsChange(const A: TAlexSettings);
+ procedure OnOCSettingsChange(const O: TOCSettings);
procedure OnXvtrSettingsChange(const X: TXvtrSettings);
procedure PushXvtrToWeb;
procedure PushRatePresetsToWeb; // backend sample-rate пресеты → web
@@ -1234,6 +1235,7 @@ begin
// Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано).
TSettingsManager.DefaultTX(FController.FTXSettings);
TSettingsManager.DefaultAlex(FController.FAlexSettings);
+ TSettingsManager.DefaultOC(FController.FOCSettings);
TSettingsManager.DefaultXvtr(FController.FXvtrSettings);
FController.FCurrentXvtr := -1;
@@ -4404,7 +4406,7 @@ begin
if FController.FNetwork.Connected and FController.FNetwork.Running then
begin
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;
end;
end;
@@ -4426,7 +4428,7 @@ begin
if FController.FNetwork.Connected and FController.FNetwork.Running then
begin
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;
end;
end;
@@ -4926,7 +4928,8 @@ begin
if FController.FNetwork.Connected and FController.FNetwork.Running then
begin
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;
end;
if FController.FCurrentXvtr < 0 then
@@ -7608,7 +7611,7 @@ begin
end;
FController.FDriveLevel := CalcDriveByte;
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;
procedure TMainForm.OnPlutoTxMaxAttChange(MaxAttDb: Double);
@@ -7643,7 +7646,7 @@ begin
end;
FController.FDriveLevel := CalcDriveByte;
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;
procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
@@ -7686,7 +7689,7 @@ begin
if FController.FNetwork.Connected and FController.FNetwork.Running then
begin
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;
end;
end;
@@ -7711,6 +7714,18 @@ begin
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
// ===========================================================================
@@ -8493,6 +8508,7 @@ begin
SF.OnTXChange := OnTXSettingsChange;
SF.OnAlexChange := OnAlexSettingsChange;
SF.OnXvtrChange := OnXvtrSettingsChange;
+ SF.OnOCChange := OnOCSettingsChange;
SF.OnWfAGCNFChange := ApplyWfAGCNF;
SF.OnADCChange := ApplyADCSettings;
SF.OnWebSettingsChange := ApplyWebSettings;
@@ -8511,6 +8527,9 @@ begin
SF.SetCalibrationBackend(FController.BackendCaps.Kind = bkPluto);
SF.LoadAnt936x(FController.Ant936x, 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.LoadCalibration(FController.FCal);
SF.LoadPlutoTxMaxAtt(FController.FPlutoTxMaxAttDb);
diff --git a/PlutoBackend.pas b/PlutoBackend.pas
index 681a24c..e0ffe01 100644
--- a/PlutoBackend.pas
+++ b/PlutoBackend.pas
@@ -170,7 +170,8 @@ type
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
DriveLevel: Byte = 100); override;
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 SendPTT(Active: Boolean); override;
procedure SetAntPorts(RxPort, TxPort: Byte); override;
@@ -1065,7 +1066,7 @@ begin
end;
procedure TPlutoBackend.UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
- Transmitting, PAEnabled, AlexEnabled: Boolean);
+ Transmitting, PAEnabled, AlexEnabled: Boolean; Tuning: Boolean; TwoTone: Boolean);
begin
FRXFreq := RXFreqHz;
FTXFreq := TXFreqHz;
diff --git a/RadioBackend.pas b/RadioBackend.pas
index 57733cf..1790b51 100644
--- a/RadioBackend.pas
+++ b/RadioBackend.pas
@@ -158,7 +158,8 @@ type
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
DriveLevel: Byte = 100); virtual; abstract;
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 SendPTT(Active: Boolean); virtual; abstract;
@@ -189,7 +190,11 @@ type
// (мощность там через DriveLevel/DUC). Db < 0.
procedure SetTxAtten(Db: Double); 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 SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); virtual;
procedure SetSpeakerAudio(AEnabled: Boolean); virtual;
@@ -284,7 +289,12 @@ procedure TRadioBackend.SetAlexConfig(const A: TAlexSettings);
begin
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
end;
diff --git a/RadioController.pas b/RadioController.pas
index ba9b1d2..ce6d25c 100644
--- a/RadioController.pas
+++ b/RadioController.pas
@@ -407,6 +407,7 @@ type
// ---- Конфиги / устройство (per-MAC) ----
FTXSettings: TTXSettings;
FAlexSettings: TAlexSettings;
+ FOCSettings: TOCSettings; // OC Control (Open Collector, openHPSDR-only)
// Антенные разъёмы AD936x (Pluto/LibreSDR) на диапазон/трансвертер.
FAnt936x: TAD936xAnt;
FXvtrSettings: TXvtrSettings;
@@ -1187,7 +1188,7 @@ begin
if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz),
- FDriveLevel, FTransmitting, True, True);
+ FDriveLevel, FTransmitting, True, True, FTuning, FTwoTone);
FNetwork.SendFullHP;
end;
end;
@@ -4273,7 +4274,8 @@ begin
En := False; DisablePA := False; RxAnt := 0;
end;
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;
procedure TRadioController.ActivateXvtr(Idx: Integer);
@@ -4903,7 +4905,7 @@ begin
begin
FNetwork.ClearDUCIQQueue;
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.SendPTT(True); // 2й пакет: PTT=1 после переключения реле
// PureSignal: перевести feedback-DDC0/1 в TX-конфигурацию (частоты = TX,
@@ -4982,7 +4984,7 @@ begin
// PureSignal: вернуть DDC0/1 в RX-конфигурацию (убрать feedback-DDC).
FNetwork.SetPureSignalTX(False);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz),
- FDriveLevel, False, True, True);
+ FDriveLevel, False, True, True, FTuning, FTwoTone);
FNetwork.ClearDUCIQQueue;
FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX)
end;
@@ -5023,7 +5025,7 @@ begin
if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz),
- FDriveLevel, FTransmitting, True, True);
+ FDriveLevel, FTransmitting, True, True, FTuning, FTwoTone);
FNetwork.SendFullHP;
end;
end;
@@ -5506,7 +5508,7 @@ begin
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
// (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1.
// 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);
FRunning := True;
FRXStartTime := GetTickCount64;
@@ -5557,7 +5559,7 @@ begin
// (3.3 UI пересоздаст по персисту при следующем старте).
for p := 1 to MAX_PANS - 1 do
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);
FRunning := False;
FTransmitting := False;
@@ -5744,6 +5746,8 @@ begin
FSettings.LoadAlex(FDevMAC, FAlexSettings);
FSettings.LoadAnt936x(FDevMAC, FAnt936x);
FNetwork.SetAlexConfig(FAlexSettings);
+ FSettings.LoadOC(FDevMAC, FOCSettings);
+ FNetwork.SetOCConfig(FOCSettings); // no-op на Pluto (base-класс)
FSettings.LoadXvtr(FDevMAC, FXvtrSettings);
FCurrentXvtr := -1;
ApplyXvtrToNetwork;
@@ -5914,6 +5918,7 @@ begin
SaveGlobalSettings;
FSettings.SaveAlex(FDevMAC, FAlexSettings);
FSettings.SaveAnt936x(FDevMAC, FAnt936x);
+ FSettings.SaveOC(FDevMAC, FOCSettings);
// XVTR LastFreq → актуальная видимая частота в момент остановки.
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
diff --git a/Settings.pas b/Settings.pas
index fa8020a..4cc721a 100644
--- a/Settings.pas
+++ b/Settings.pas
@@ -13,6 +13,16 @@ const
SETTINGS_FILE = 'hpsdr_settings.json';
CFG_BAND_COUNT = 12;
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_MAX = RadioModes.MODE_MAX;
CFG_PANS_MAX = 4; // потолок панадаптеров (пан 0 + доп.) — = WDSPEngine.MAX_PANS
@@ -341,6 +351,26 @@ type
EnableXvtrHf: Boolean; // режим XVTR HF
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-моста,
// напряжение/ток питания, S-метр. Модель детектора задаёт константы моста
// (Vref/bridge_volt/ADC-offset, как в Thetis computeAlexFwdPower).
@@ -526,6 +556,10 @@ type
class procedure DefaultAlex(out A: TAlexSettings);
function LoadAlex(const MAC: array of Byte; out A: TAlexSettings): Boolean;
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).
class procedure DefaultXvtr(out X: TXvtrSettings);
function LoadXvtr(const MAC: array of Byte; out X: TXvtrSettings): Boolean;
@@ -1567,6 +1601,80 @@ begin
A.EnableXvtrHf := JB(O, 'enable_xvtr_hf', False);
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);
var i: Integer;
begin
diff --git a/SettingsForm.pas b/SettingsForm.pas
index e7f8254..d539012 100644
--- a/SettingsForm.pas
+++ b/SettingsForm.pas
@@ -85,6 +85,7 @@ type
const InDev: array of string) of object;
TOnAlexChange = procedure(const A: TAlexSettings) of object;
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
+ TOnOCChange = procedure(const O: TOCSettings) of object;
{ TSettingsForm }
TSettingsForm = class(TForm)
@@ -320,6 +321,26 @@ type
FXvtrConfig: TXvtrSettings;
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) ----
FPageQO: TScrollBox;
FNavQO: TFlatButton;
@@ -431,6 +452,10 @@ type
procedure FireXvtrChange;
procedure OnXvtrAnyChange(Sender: TObject);
procedure UpdateXvtrRowEnabled(Idx: Integer);
+ procedure BuildOCTab;
+ procedure CollectOCFromUI;
+ procedure FireOCChange;
+ procedure OnOCAnyChange(Sender: TObject);
procedure CollectTXFromUI;
procedure FireTXChange;
procedure OnTXAnyChange(Sender: TObject);
@@ -547,6 +572,9 @@ type
procedure LoadTXSettings(const T: TTXSettings; BoardType: Integer = 0);
procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
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 LoadWfRender(Palette: Integer; Gamma: Double;
ResLimit, SpeedDiv: Integer);
@@ -559,6 +587,7 @@ type
property OnSliceCATChange: TOnSliceCATChange read FOnSliceCATChange write FOnSliceCATChange;
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
+ property OnOCChange: TOnOCChange read FOnOCChange write FOnOCChange;
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
property OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange;
property OnAnt936xChange: TOnAnt936xChange read FOnAnt936xChange write FOnAnt936xChange;
@@ -643,6 +672,10 @@ const
PA_BAND_NAMES: array[0..10] of string =
('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)
// и перекладки под активное железо (RelayoutCalTab).
CAL_MARGIN = 22;
@@ -818,7 +851,8 @@ begin
FNavSlices := MakeNavButton('Slices', 398);
FNavAlex := MakeNavButton('Antenna / Alex', 434);
FNavXvtr := MakeNavButton('Transverter', 470);
- FNavQO := MakeNavButton('QO-100', 506);
+ FNavOC := MakeNavButton('OC Control', 506);
+ FNavQO := MakeNavButton('QO-100', 542);
FContentPanel := TPanel.Create(Self);
FContentPanel.Parent := Self;
@@ -838,11 +872,13 @@ begin
FPageSlices := MakeScrollPage;
FPageAlex := MakeScrollPage;
FPageXvtr := MakeScrollPage;
+ FPageOC := MakeScrollPage;
FPageQO := MakeScrollPage;
FAlexBoardType := 0;
TSettingsManager.DefaultAlex(FAlexConfig);
TSettingsManager.DefaultXvtr(FXvtrConfig);
TSettingsManager.DefaultCalibration(FCalConfig);
+ TSettingsManager.DefaultOC(FOCConfig);
BuildAudioTab;
BuildDisplayTab;
@@ -855,6 +891,7 @@ begin
BuildSlicesTab;
BuildAlexTab;
BuildXvtrTab;
+ BuildOCTab;
BuildQOTab;
FBtnClose := TFlatButton.Create(Self);
@@ -889,6 +926,7 @@ begin
else if Sender = FNavSlices then SelectPage(FPageSlices, FNavSlices)
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex)
else if Sender = FNavXvtr then SelectPage(FPageXvtr, FNavXvtr)
+ else if Sender = FNavOC then SelectPage(FPageOC, FNavOC)
else if Sender = FNavQO then SelectPage(FPageQO, FNavQO);
end;
@@ -906,6 +944,7 @@ begin
FPageSlices.Visible := APage = FPageSlices;
FPageAlex.Visible := APage = FPageAlex;
FPageXvtr.Visible := APage = FPageXvtr;
+ FPageOC.Visible := APage = FPageOC;
FPageQO.Visible := APage = FPageQO;
FNavAudio.Active := ANav = FNavAudio;
@@ -920,6 +959,7 @@ begin
FNavSlices.Active := ANav = FNavSlices;
FNavAlex.Active := ANav = FNavAlex;
FNavXvtr.Active := ANav = FNavXvtr;
+ FNavOC.Active := ANav = FNavOC;
FNavQO.Active := ANav = FNavQO;
end;
@@ -4658,6 +4698,250 @@ begin
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 ─────────────────────────────────────────────────────────────
procedure TSettingsForm.BuildQOTab;
diff --git a/ewsdr.lpi b/ewsdr.lpi
index 20314b0..d457312 100644
--- a/ewsdr.lpi
+++ b/ewsdr.lpi
@@ -17,9 +17,9 @@
-
+
-
+
@@ -139,6 +139,7 @@
+