From 682025e99b37988edbfe4612700473ac380f6cc2 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Tue, 9 Jun 2026 14:29:56 +0300 Subject: [PATCH] Phase 4: extract CAT into its own adapter (CATAdapter.pas) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CAT becomes a peer adapter over the controller, alongside WebAdapter. TCATAdapter owns the CAT engine + serial/TCP transports and builds the TCATContext: getters read FController directly (CAT thread), setters/commands call controller commands marshaled via FController.Invoke. No MainForm or WebAdapter dependency — the historical CAT->WebAdapter.OnXxx coupling (Mode/AGC/Band/etc.) is gone; CAT talks only to the controller. MainForm keeps CAT *settings* (FCATLastGlobal + OnCATSettingsChange + SettingsForm wiring), now calling FCATAdapter.ApplySettings on change/connect. Removed InitCATEngine, CATApplySettings, all CATGet*/CATSet*/CATDo*/SyncCAT* and the FCATEngine/FCATSerial/FCATTcp/FCATSyncFreq fields. Fixes a latent bug: CATSetFilterIdx routed a filter index through the web BW handler as a negative-encoded value, but that branch was dead since batch 21 — CAT now calls FController.SetFilterIdx directly. Adds StoreVfoA (store VFO A without retune when B is active) and uses the now-real SetBand/BandUp/BandDown/TuneActiveBy commands. Co-Authored-By: Claude Opus 4.8 --- CATAdapter.pas | 324 ++++++++++++++++++++++++++++++++++++++++++++ MainForm.pas | 243 ++------------------------------- RadioController.pas | 22 +++ 3 files changed, 355 insertions(+), 234 deletions(-) create mode 100644 CATAdapter.pas diff --git a/CATAdapter.pas b/CATAdapter.pas new file mode 100644 index 0000000..8eae4aa --- /dev/null +++ b/CATAdapter.pas @@ -0,0 +1,324 @@ +unit CATAdapter; + +// ----------------------------------------------------------------------------- +// TCATAdapter — адаптер CAT (Kenwood TS-2000) поверх TRadioController. +// +// Равноправный клиент контроллера наряду с WebAdapter и будущей Arduino-панелью. +// Владеет CAT-движком (TCATEngine) + транспортами (serial/TCP) и строит TCATContext: +// • геттеры читают состояние FController напрямую (CAT-поток, read-only); +// • сеттеры/команды зовут команды контроллера, маршалинг через FController.Invoke +// (GUI = TThread.Synchronize; демон подставит свою очередь). +// Никаких обращений к MainForm/WebAdapter — CAT говорит только с контроллером. +// +// Конфиг serial/TCP применяется снаружи через ApplySettings (UI-настройки CAT +// остаются в MainForm/SettingsForm и зовут ApplySettings при изменении/connect). +// ----------------------------------------------------------------------------- + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + RadioController, CATEngine, CATSerial, CATTcp, Settings, WDSPEngine; + +type + TCATAdapter = class + private + FController: TRadioController; + FEngine: TCATEngine; + FSerial: TCATSerialManager; + FTcp: TCATTcpServer; + // Параметры команды для маршалинга в поток контроллера (через Invoke). + FSyncFreq: Double; + FSyncInt: Integer; + FSyncBool: Boolean; + + // ── Геттеры (CAT-поток, read-only) ── + function GetVfoA: Double; + function GetVfoB: Double; + function GetMode: Integer; + function GetActiveVfo: Integer; + function GetAGCMode: Integer; + function GetVolume: Integer; + function GetDriveLevel: Integer; + function GetFilterIdx: Integer; + function GetFilterBW: Integer; + function GetNRMode: Integer; + function GetNBMode: Integer; + function GetSNB: Boolean; + function GetANF: Boolean; + function GetTX: Boolean; + function GetRunning: Boolean; + function GetSMeter: Double; + function GetBand: Integer; + + // ── Сеттеры/команды (из CAT-потока → Invoke в поток контроллера) ── + procedure SetVfoA(V: Double); + procedure SetVfoB(V: Double); + procedure SetMode(V: Integer); + procedure SetActiveVfo(V: Integer); + procedure SetAGCMode(V: Integer); + procedure SetVolume(V: Integer); + procedure SetDriveLevel(V: Integer); + procedure SetFilterIdx(V: Integer); + procedure SetNRMode(V: Integer); + procedure SetNBMode(V: Integer); + procedure SetSNB(V: Boolean); + procedure SetANF(V: Boolean); + procedure SetTransmitting(V: Boolean); + procedure DoBandUp; + procedure DoBandDown; + procedure DoTuneUp; + procedure DoTuneDown; + procedure DoBandByIndex(Idx: Integer); + + // ── Sync-методы (выполняются в потоке контроллера) ── + procedure SyncVfoA; + procedure SyncVfoB; + procedure SyncMode; + procedure SyncActiveVfo; + procedure SyncAGC; + procedure SyncVolume; + procedure SyncDrive; + procedure SyncFilterIdx; + procedure SyncNR; + procedure SyncNB; + procedure SyncSNB; + procedure SyncANF; + procedure SyncMOX; + procedure SyncBandUp; + procedure SyncBandDown; + procedure SyncTuneUp; + procedure SyncTuneDown; + procedure SyncBandByIndex; + public + constructor Create(AController: TRadioController); + destructor Destroy; override; + // Применить serial/TCP-конфиг из глобальных настроек (вызывает UI на change/connect). + procedure ApplySettings(const G: TGlobalSettings); + end; + +implementation + +constructor TCATAdapter.Create(AController: TRadioController); +var + Ctx: TCATContext; +begin + inherited Create; + FController := AController; + + FillChar(Ctx, SizeOf(Ctx), 0); + Ctx.GetVfoA := @GetVfoA; + Ctx.GetVfoB := @GetVfoB; + Ctx.GetMode := @GetMode; + Ctx.GetActiveVfo := @GetActiveVfo; + Ctx.GetAGCMode := @GetAGCMode; + Ctx.GetVolume := @GetVolume; + Ctx.GetDriveLevel := @GetDriveLevel; + Ctx.GetFilterIdx := @GetFilterIdx; + Ctx.GetFilterBW := @GetFilterBW; + Ctx.GetNRMode := @GetNRMode; + Ctx.GetNBMode := @GetNBMode; + Ctx.GetSNBEnabled := @GetSNB; + Ctx.GetANFEnabled := @GetANF; + Ctx.GetTransmitting := @GetTX; + Ctx.GetRunning := @GetRunning; + Ctx.GetSMeter := @GetSMeter; + Ctx.GetCurrentBand := @GetBand; + Ctx.SetVfoA := @SetVfoA; + Ctx.SetVfoB := @SetVfoB; + Ctx.SetMode := @SetMode; + Ctx.SetActiveVfo := @SetActiveVfo; + Ctx.SetAGCMode := @SetAGCMode; + Ctx.SetVolume := @SetVolume; + Ctx.SetDriveLevel := @SetDriveLevel; + Ctx.SetFilterIdx := @SetFilterIdx; + Ctx.SetNRMode := @SetNRMode; + Ctx.SetNBMode := @SetNBMode; + Ctx.SetSNBEnabled := @SetSNB; + Ctx.SetANFEnabled := @SetANF; + Ctx.SetTransmitting := @SetTransmitting; + Ctx.DoBandUp := @DoBandUp; + Ctx.DoBandDown := @DoBandDown; + Ctx.DoTuneUp := @DoTuneUp; + Ctx.DoTuneDown := @DoTuneDown; + Ctx.DoBandByIndex := @DoBandByIndex; + + FEngine := TCATEngine.Create(Ctx); + FSerial := TCATSerialManager.Create(FEngine); + FTcp := TCATTcpServer.Create(FEngine); +end; + +destructor TCATAdapter.Destroy; +begin + if Assigned(FTcp) then begin FTcp.Stop; FreeAndNil(FTcp); end; + if Assigned(FSerial) then begin FSerial.StopAll; FreeAndNil(FSerial); end; + FreeAndNil(FEngine); + inherited Destroy; +end; + +procedure TCATAdapter.ApplySettings(const G: TGlobalSettings); +var + Cfgs: array[0..3] of TCATSerialConfig; + i: Integer; +begin + for i := 0 to 3 do + begin + Cfgs[i].Enabled := G.CATSerialEnabled[i]; + Cfgs[i].PortName := G.CATSerialPort[i]; + Cfgs[i].BaudRate := G.CATSerialBaud[i]; + Cfgs[i].DataBits := G.CATSerialDataBits[i]; + Cfgs[i].StopBits := G.CATSerialStopBits[i]; + case G.CATSerialParity[i] of + 1: Cfgs[i].Parity := cspOdd; + 2: Cfgs[i].Parity := cspEven; + else Cfgs[i].Parity := cspNone; + end; + end; + FSerial.ApplyConfig(Cfgs); + FTcp.Stop; + if G.CATTcpEnabled then + begin + FTcp.Port := G.CATTcpPort; + FTcp.Start; + end; +end; + +// ── Геттеры ────────────────────────────────────────────────────────────────── +function TCATAdapter.GetVfoA: Double; begin Result := FController.FVfoA; end; +function TCATAdapter.GetVfoB: Double; begin Result := FController.FVfoB; end; +function TCATAdapter.GetMode: Integer; begin Result := FController.FMode; end; +function TCATAdapter.GetActiveVfo: Integer; begin Result := FController.FActiveVfo; end; +function TCATAdapter.GetAGCMode: Integer; begin Result := FController.FAGCMode; end; +function TCATAdapter.GetVolume: Integer; begin Result := FController.FVolume; end; +function TCATAdapter.GetDriveLevel: Integer; begin Result := FController.FDrivePercent; end; +function TCATAdapter.GetFilterIdx: Integer; begin Result := FController.FFilter; end; +function TCATAdapter.GetFilterBW: Integer; begin Result := FController.FFilterBW; end; +function TCATAdapter.GetNRMode: Integer; begin Result := FController.FNRMode; end; +function TCATAdapter.GetNBMode: Integer; begin Result := FController.FNBMode; end; +function TCATAdapter.GetSNB: Boolean; begin Result := FController.FSNB; end; +function TCATAdapter.GetANF: Boolean; begin Result := FController.FANF; end; +function TCATAdapter.GetTX: Boolean; begin Result := FController.FTransmitting; end; +function TCATAdapter.GetRunning: Boolean; begin Result := FController.FRunning; end; +function TCATAdapter.GetSMeter: Double; begin Result := FController.FLastSMeter; end; +function TCATAdapter.GetBand: Integer; begin Result := FController.FCurrentBand; end; + +// ── Сеттеры (маршалинг) ────────────────────────────────────────────────────── +procedure TCATAdapter.SetVfoA(V: Double); +begin FSyncFreq := V; FController.Invoke(@SyncVfoA); end; + +procedure TCATAdapter.SyncVfoA; +begin + // A активен — полная перестройка (CTUN/band/сеть + канальная оркестрация через + // OnAfterTune). B активен — только сохраняем/отображаем A (приёмник на B). + if FController.FActiveVfo = 0 then FController.SetVfoA(FSyncFreq) + else FController.StoreVfoA(FSyncFreq); +end; + +procedure TCATAdapter.SetVfoB(V: Double); +begin FSyncFreq := V; FController.Invoke(@SyncVfoB); end; + +procedure TCATAdapter.SyncVfoB; +begin FController.SetVfoB(FSyncFreq); end; + +procedure TCATAdapter.SetMode(V: Integer); +begin FSyncInt := V; FController.Invoke(@SyncMode); end; + +procedure TCATAdapter.SyncMode; +begin + if (FSyncInt < 0) or (FSyncInt > MODE_SAM) then Exit; + FController.SetMode(FSyncInt); +end; + +procedure TCATAdapter.SetActiveVfo(V: Integer); +begin FSyncInt := V; FController.Invoke(@SyncActiveVfo); end; + +procedure TCATAdapter.SyncActiveVfo; +begin FController.SetActiveVfo(FSyncInt); end; + +procedure TCATAdapter.SetAGCMode(V: Integer); +begin FSyncInt := V; FController.Invoke(@SyncAGC); end; + +procedure TCATAdapter.SyncAGC; +begin FController.SetAGCMode(FSyncInt); end; + +procedure TCATAdapter.SetVolume(V: Integer); +begin FSyncInt := V; FController.Invoke(@SyncVolume); end; + +procedure TCATAdapter.SyncVolume; +begin FController.SetVolume(FSyncInt); end; + +procedure TCATAdapter.SetDriveLevel(V: Integer); +begin FSyncInt := V; FController.Invoke(@SyncDrive); end; + +procedure TCATAdapter.SyncDrive; +begin FController.SetDrive(FSyncInt); end; + +procedure TCATAdapter.SetFilterIdx(V: Integer); +begin FSyncInt := V; FController.Invoke(@SyncFilterIdx); end; + +procedure TCATAdapter.SyncFilterIdx; +begin FController.SetFilterIdx(FSyncInt); end; // прямой индекс пресета + +procedure TCATAdapter.SetNRMode(V: Integer); +begin FSyncInt := V; FController.Invoke(@SyncNR); end; + +procedure TCATAdapter.SyncNR; +begin FController.SetNR(FSyncInt); end; + +procedure TCATAdapter.SetNBMode(V: Integer); +begin FSyncInt := V; FController.Invoke(@SyncNB); end; + +procedure TCATAdapter.SyncNB; +begin FController.SetNB(FSyncInt); end; + +procedure TCATAdapter.SetSNB(V: Boolean); +begin FSyncBool := V; FController.Invoke(@SyncSNB); end; + +procedure TCATAdapter.SyncSNB; +begin FController.SetSNB(FSyncBool); end; + +procedure TCATAdapter.SetANF(V: Boolean); +begin FSyncBool := V; FController.Invoke(@SyncANF); end; + +procedure TCATAdapter.SyncANF; +begin FController.SetANF(FSyncBool); end; + +procedure TCATAdapter.SetTransmitting(V: Boolean); +begin FSyncBool := V; FController.Invoke(@SyncMOX); end; + +procedure TCATAdapter.SyncMOX; +begin FController.SetMOX(FSyncBool); end; + +procedure TCATAdapter.DoBandUp; +begin FController.Invoke(@SyncBandUp); end; + +procedure TCATAdapter.SyncBandUp; +begin FController.BandUp; end; + +procedure TCATAdapter.DoBandDown; +begin FController.Invoke(@SyncBandDown); end; + +procedure TCATAdapter.SyncBandDown; +begin FController.BandDown; end; + +procedure TCATAdapter.DoTuneUp; +begin FController.Invoke(@SyncTuneUp); end; + +procedure TCATAdapter.SyncTuneUp; +begin FController.TuneActiveBy(10); end; + +procedure TCATAdapter.DoTuneDown; +begin FController.Invoke(@SyncTuneDown); end; + +procedure TCATAdapter.SyncTuneDown; +begin FController.TuneActiveBy(-10); end; + +procedure TCATAdapter.DoBandByIndex(Idx: Integer); +begin FSyncInt := Idx; FController.Invoke(@SyncBandByIndex); end; + +procedure TCATAdapter.SyncBandByIndex; +begin FController.SetBand(FSyncInt); end; + +end. diff --git a/MainForm.pas b/MainForm.pas index f74505a..821eb44 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -31,7 +31,7 @@ uses WDSP, WDSPEngine, AudioOutput, AudioInput, Settings, WebServer, WebAdapter, - CATEngine, CATSerial, CATTcp, + CATAdapter, SpectrumView, SpectrumViewOpengl, WidebandView, StatusBar, @@ -177,11 +177,8 @@ type FWebUser: string; FWebPass: string; // --- CAT --- - FCATEngine: TCATEngine; - FCATSerial: TCATSerialManager; - FCATTcp: TCATTcpServer; - FCATSyncFreq: Double; - FCATLastGlobal: TGlobalSettings; // текущие CAT-настройки (для сохранения) + FCATAdapter: TCATAdapter; // адаптер CAT поверх контроллера (движок+транспорты+контекст) + FCATLastGlobal: TGlobalSettings; // текущие CAT-настройки (UI/persist; ApplySettings → адаптер) // Временные поля для передачи параметров в Synchronize-методы FWebSyncFreq: Double; FWebSyncInt: Integer; @@ -453,39 +450,7 @@ type procedure PushDeviceListToWeb; // store → FWebServer.SetDeviceList // Synchronize-обёртки (выполняются в UI-потоке) — только host-зависимые procedure SyncWebRun; - // --- CAT callbacks ------------------------------------------------------- - function CATGetVfoA: Double; - function CATGetVfoB: Double; - function CATGetMode: Integer; - function CATGetActiveVfo: Integer; - function CATGetAGCMode: Integer; - function CATGetVolume: Integer; - function CATGetDriveLevel: Integer; - function CATGetFilterIdx: Integer; - function CATGetFilterBW: Integer; - function CATGetNRMode: Integer; - function CATGetNBMode: Integer; - function CATGetSNB: Boolean; - function CATGetANF: Boolean; - function CATGetTX: Boolean; - function CATGetRunning: Boolean; - function CATGetSMeter: Double; - function CATGetBand: Integer; - procedure CATSetVfoA(V: Double); - procedure CATSetVfoB(V: Double); - procedure CATSetFilterIdx(V: Integer); - procedure CATDoBandUp; - procedure CATDoBandDown; - procedure CATDoTuneUp; - procedure CATDoTuneDown; - procedure SyncCATVfoA; - procedure SyncCATVfoB; - procedure SyncCATBandUp; - procedure SyncCATBandDown; - procedure SyncCATTuneUp; - procedure SyncCATTuneDown; - procedure InitCATEngine; - procedure CATApplySettings(const G: TGlobalSettings); + // --- CAT settings (UI/persist; контекст+движок — в TCATAdapter) ---------- procedure OnCATSettingsChange( const SerEnabled: array of Boolean; const SerPort: array of string; @@ -908,8 +873,8 @@ begin end; FController.FSettings.LoadCATSettings(FCATLastGlobal); FLightTheme := FController.FSettings.LoadTheme; - InitCATEngine; - CATApplySettings(FCATLastGlobal); + FCATAdapter := TCATAdapter.Create(FController); + FCATAdapter.ApplySettings(FCATLastGlobal); FSMeterPeak := -130; FSMeterMin := -130; FSMeterAvg := -130; @@ -1079,9 +1044,7 @@ begin FWebServer.Stop; FreeAndNil(FWebAdapter); // адаптер не владеет сервером — освобождаем после Stop FWebServer.Free; - if Assigned(FCATTcp) then begin FCATTcp.Stop; FreeAndNil(FCATTcp); end; - if Assigned(FCATSerial) then begin FCATSerial.StopAll; FreeAndNil(FCATSerial); end; - FreeAndNil(FCATEngine); + FreeAndNil(FCATAdapter); // его Destroy останавливает+освобождает CAT движок/транспорты // Ядро освобождаем последним — его Destroy закрывает и освобождает движки // (FreeEngines) и FSettings. FreeAndNil(FController); @@ -1350,7 +1313,7 @@ begin // Тема — app-global (своё хранилище LoadTheme/SaveTheme), не из device-блоба: // возвращаем живое значение в persist-буфер, чтобы blob нёс актуальную тему. FController.FLoadedGlobal.LightTheme := FLightTheme; - CATApplySettings(FController.FLoadedGlobal); + FCATAdapter.ApplySettings(FController.FLoadedGlobal); RebuildXvtrButtons; PushXvtrToWeb; TrkDrive.Position := FController.FDrivePercent; // слайдер ← drive% @@ -5542,78 +5505,6 @@ end; // CAT subsystem // =========================================================================== -procedure TMainForm.InitCATEngine; -var - Ctx: TCATContext; -begin - FillChar(Ctx, SizeOf(Ctx), 0); - Ctx.GetVfoA := CATGetVfoA; - Ctx.GetVfoB := CATGetVfoB; - Ctx.GetMode := CATGetMode; - Ctx.GetActiveVfo := CATGetActiveVfo; - Ctx.GetAGCMode := CATGetAGCMode; - Ctx.GetVolume := CATGetVolume; - Ctx.GetDriveLevel := CATGetDriveLevel; - Ctx.GetFilterIdx := CATGetFilterIdx; - Ctx.GetFilterBW := CATGetFilterBW; - Ctx.GetNRMode := CATGetNRMode; - Ctx.GetNBMode := CATGetNBMode; - Ctx.GetSNBEnabled := CATGetSNB; - Ctx.GetANFEnabled := CATGetANF; - Ctx.GetTransmitting := CATGetTX; - Ctx.GetRunning := CATGetRunning; - Ctx.GetSMeter := CATGetSMeter; - Ctx.GetCurrentBand := CATGetBand; - Ctx.SetVfoA := CATSetVfoA; - Ctx.SetVfoB := CATSetVfoB; - Ctx.SetMode := FWebAdapter.OnMode; - Ctx.SetActiveVfo := FWebAdapter.OnActiveVfo; - Ctx.SetAGCMode := FWebAdapter.OnAGC; - Ctx.SetVolume := FWebAdapter.OnVolume; - Ctx.SetDriveLevel := FWebAdapter.OnDrive; - Ctx.SetFilterIdx := CATSetFilterIdx; - Ctx.SetNRMode := FWebAdapter.OnNR; - Ctx.SetNBMode := FWebAdapter.OnNB; - Ctx.SetSNBEnabled := FWebAdapter.OnSNB; - Ctx.SetANFEnabled := FWebAdapter.OnANF; - Ctx.SetTransmitting := FWebAdapter.OnMOX; - Ctx.DoBandUp := CATDoBandUp; - Ctx.DoBandDown := CATDoBandDown; - Ctx.DoTuneUp := CATDoTuneUp; - Ctx.DoTuneDown := CATDoTuneDown; - Ctx.DoBandByIndex := FWebAdapter.OnBand; - FCATEngine := TCATEngine.Create(Ctx); - FCATSerial := TCATSerialManager.Create(FCATEngine); - FCATTcp := TCATTcpServer.Create(FCATEngine); -end; - -procedure TMainForm.CATApplySettings(const G: TGlobalSettings); -var - Cfgs: array[0..3] of TCATSerialConfig; - i: Integer; -begin - for i := 0 to 3 do - begin - Cfgs[i].Enabled := G.CATSerialEnabled[i]; - Cfgs[i].PortName := G.CATSerialPort[i]; - Cfgs[i].BaudRate := G.CATSerialBaud[i]; - Cfgs[i].DataBits := G.CATSerialDataBits[i]; - Cfgs[i].StopBits := G.CATSerialStopBits[i]; - case G.CATSerialParity[i] of - 1: Cfgs[i].Parity := cspOdd; - 2: Cfgs[i].Parity := cspEven; - else Cfgs[i].Parity := cspNone; - end; - end; - FCATSerial.ApplyConfig(Cfgs); - FCATTcp.Stop; - if G.CATTcpEnabled then - begin - FCATTcp.Port := G.CATTcpPort; - FCATTcp.Start; - end; -end; - procedure TMainForm.OnCATSettingsChange( const SerEnabled: array of Boolean; const SerPort: array of string; @@ -5642,126 +5533,10 @@ begin FController.FLoadedGlobal.CATSerialParity := FCATLastGlobal.CATSerialParity; FController.FLoadedGlobal.CATTcpEnabled := FCATLastGlobal.CATTcpEnabled; FController.FLoadedGlobal.CATTcpPort := FCATLastGlobal.CATTcpPort; - CATApplySettings(FCATLastGlobal); + FCATAdapter.ApplySettings(FCATLastGlobal); FController.FSettings.SaveCATSettings(FCATLastGlobal); FController.SaveGlobalSettings; FController.FSettings.Save; end; -// --- Getters (called from CAT thread — read only, no sync needed) ----------- - -function TMainForm.CATGetVfoA: Double; begin Result := FController.FVfoA; end; -function TMainForm.CATGetVfoB: Double; begin Result := FController.FVfoB; end; -function TMainForm.CATGetMode: Integer; begin Result := FController.FMode; end; -function TMainForm.CATGetActiveVfo: Integer; begin Result := FController.FActiveVfo; end; -function TMainForm.CATGetAGCMode: Integer; begin Result := FController.FAGCMode; end; -function TMainForm.CATGetVolume: Integer; begin Result := FController.FVolume; end; -function TMainForm.CATGetDriveLevel: Integer;begin Result := FController.FDrivePercent; end; -function TMainForm.CATGetFilterIdx: Integer; begin Result := FController.FFilter; end; -function TMainForm.CATGetFilterBW: Integer; begin Result := FController.FFilterBW; end; -function TMainForm.CATGetNRMode: Integer; begin Result := FController.FNRMode; end; -function TMainForm.CATGetNBMode: Integer; begin Result := FController.FNBMode; end; -function TMainForm.CATGetSNB: Boolean; begin Result := FController.FSNB; end; -function TMainForm.CATGetANF: Boolean; begin Result := FController.FANF; end; -function TMainForm.CATGetTX: Boolean; begin Result := FController.FTransmitting; end; -function TMainForm.CATGetRunning: Boolean; begin Result := FController.FRunning; end; -function TMainForm.CATGetSMeter: Double; begin Result := FController.FLastSMeter; end; -function TMainForm.CATGetBand: Integer; begin Result := FController.FCurrentBand; end; - -// --- Setters ---------------------------------------------------------------- - -procedure TMainForm.CATSetVfoA(V: Double); -begin - FCATSyncFreq := V; - TThread.Synchronize(nil, SyncCATVfoA); -end; - -procedure TMainForm.CATSetVfoB(V: Double); -begin - FCATSyncFreq := V; - TThread.Synchronize(nil, SyncCATVfoB); -end; - -procedure TMainForm.CATSetFilterIdx(V: Integer); -begin - // OnFilter: negative value encodes 0-based index as -(idx+1) - FWebAdapter.OnFilter(-(V + 1)); -end; - -procedure TMainForm.CATDoBandUp; -begin TThread.Synchronize(nil, SyncCATBandUp); end; - -procedure TMainForm.CATDoBandDown; -begin TThread.Synchronize(nil, SyncCATBandDown); end; - -procedure TMainForm.CATDoTuneUp; -begin TThread.Synchronize(nil, SyncCATTuneUp); end; - -procedure TMainForm.CATDoTuneDown; -begin TThread.Synchronize(nil, SyncCATTuneDown); end; - -// --- Sync methods (run in main thread) -------------------------------------- - -procedure TMainForm.SyncCATVfoA; -var BandIdx: Integer; -begin - FController.FVfoA := FCATSyncFreq; - if FController.FActiveVfo = 0 then - begin - ApplyVfoA(Round(FController.FVfoA)); - end - else - begin - // VFO A is not active — just update display and band indicator - FreqDispA.Frequency := Round(FController.FVfoA); - BandIdx := FreqToBandIdx(FController.FVfoA); - if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then - begin - StyleButton(BtnBand[FController.FCurrentBand], False); - FController.FCurrentBand := BandIdx; - StyleButton(BtnBand[FController.FCurrentBand], True); - end; - end; -end; - -procedure TMainForm.SyncCATVfoB; -begin - // Унифицировано с десктопом/вебом: та же команда SetVfoB. - FController.SetVfoB(FCATSyncFreq); -end; - -procedure TMainForm.SyncCATBandUp; -begin - if FController.FCurrentBand < BAND_COUNT - 1 then - FWebAdapter.OnBand(FController.FCurrentBand + 1); -end; - -procedure TMainForm.SyncCATBandDown; -begin - if FController.FCurrentBand > 0 then - FWebAdapter.OnBand(FController.FCurrentBand - 1); -end; - -procedure TMainForm.SyncCATTuneUp; -begin - if FController.FActiveVfo = 0 then - ApplyVfoA(Round(FController.FVfoA) + 10) - else - begin - FCATSyncFreq := FController.FVfoB + 10; - SyncCATVfoB; - end; -end; - -procedure TMainForm.SyncCATTuneDown; -begin - if FController.FActiveVfo = 0 then - ApplyVfoA(Round(FController.FVfoA) - 10) - else - begin - FCATSyncFreq := FController.FVfoB - 10; - SyncCATVfoB; - end; -end; - end. diff --git a/RadioController.pas b/RadioController.pas index 25d4946..84ff79b 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -350,6 +350,7 @@ type // Частоты / VFO procedure SetVfoA(Hz: Double); + procedure StoreVfoA(Hz: Double); // сохранить частоту A без перестройки (когда активен B) procedure SetVfoB(Hz: Double); procedure SetActiveVfo(Idx: Integer); procedure TuneActiveBy(DeltaHz: Double); // энкодер VFO @@ -1249,6 +1250,27 @@ begin if Assigned(FOnAfterTune) then FOnAfterTune; end; +procedure TRadioController.StoreVfoA(Hz: Double); +// Сохранить частоту VFO A БЕЗ перестройки приёмника (когда активен B — слушаем B, +// A только хранится/отображается). Симметрично «неактивной» ветке SetVfoB. +// Рендер FreqDispA — через rfVfoA (спектр он не трогает, т.к. SyncSpecViewFreq +// держит активный VFO B); смена band-кнопки — через rfBand. +var BandIdx: Integer; +begin + FVfoA := Hz; + if FCurrentXvtr < 0 then + begin + BandIdx := FreqToBandIdx(FVfoA); + if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + begin + FCurrentBand := BandIdx; + FDriveLevel := CalcDriveByte; + Changed(rfBand); + end; + end; + Changed(rfVfoA); +end; + procedure TRadioController.SetVfoB(Hz: Double); // VFO B всегда сохраняется. Если B активен — перестраиваем приёмник через // ApplyTuneCore (с учётом CTUN), детектим диапазон и толкаем в сеть. Если B