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 @@ +