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, CATSliceEndpoint, Settings, WDSPEngine, AndromedaPanel; type TCATAdapter = class private FController: TRadioController; FEngine: TCATEngine; FSerial: TCATSerialManager; FTcp: TCATTcpServer; // CAT дополнительных слайсов: свой движок+TCP-порт на слот (B..). Порты // главного (serial + FTcp) остаются за главным приёмником — слайс A. FSliceEps: array[0..CAT_SLICE_SLOTS-1] of TCATSliceEndpoint; FPanel: TAndromedaPanel; // передняя панель Andromeda/G2 (если включена) // Параметры команды для маршалинга в поток контроллера (через Invoke). FSyncFreq: Double; FSyncInt: Integer; FSyncBool: Boolean; FSyncStr: string; // Какое поле TX-настроек правит SyncTXField (значение — в FSyncInt/FSyncBool). FSyncTXFld: (txfMicGain, txfFiltLow, txfFiltHigh, txfCompOn, txfCompGain, txfEQOn, txfTUNLevel, txfFMDev); FSyncEQ: TCATEQGains; // ── Геттеры (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 GetFilterLow: Integer; function GetFilterHigh: Integer; procedure SetFilterLow(V: Integer); procedure SetFilterHigh(V: 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; function GetSplit: Boolean; function GetSquelchOn: Boolean; function GetSquelchLevel: Integer; function GetCTCSSOn: Boolean; function GetCTCSSTone: Integer; function GetFMRptDir: Integer; function GetFMRptOffset: Double; function GetFMStep: Integer; function GetAGCTop: Integer; function GetCTun: Boolean; // ── Сеттеры/команды (из 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); function GetCWSpeed: Integer; procedure SetCWSpeed(V: Integer); function GetCWKeyerMode: Integer; procedure SetCWKeyerMode(V: Integer); procedure SendCWText(const S: string); function GetCWTextBusy: Boolean; procedure SyncCWSpeed; procedure SyncCWKeyerMode; procedure SyncCWText; // ── Телеграф: тон, break-in, сайдтон ── function GetCWPitch: Integer; procedure SetCWPitch(V: Integer); function GetCWBreakIn: Boolean; procedure SetCWBreakIn(V: Boolean); function GetCWHangTime: Integer; procedure SetCWHangTime(V: Integer); function GetCWMonitor: Boolean; procedure SetCWMonitor(V: Boolean); procedure SyncCWPitch; procedure SyncCWBreakIn; procedure SyncCWHangTime; procedure SyncCWMonitor; // ── TX-тракт (активный TX-профиль + вкладка Transmit) ── function GetMicGain: Integer; procedure SetMicGain(V: Integer); function GetTXFilterLow: Integer; procedure SetTXFilterLow(V: Integer); function GetTXFilterHigh: Integer; procedure SetTXFilterHigh(V: Integer); function GetCompOn: Boolean; procedure SetCompOn(V: Boolean); function GetCompGain: Integer; procedure SetCompGain(V: Integer); function GetTXEQOn: Boolean; procedure SetTXEQOn(V: Boolean); function GetTXEQ(out Gains: TCATEQGains): Integer; procedure SetTXEQ(NumBands: Integer; const Gains: TCATEQGains); function GetTUNLevel: Integer; procedure SetTUNLevel(V: Integer); function GetFMDeviation: Integer; procedure SetFMDeviation(V: Integer); function GetTXProfile: Integer; procedure SetTXProfile(V: Integer); function GetTXProfileCount: Integer; function GetMonitorOn: Boolean; procedure SetMonitorOn(V: Boolean); function GetMonitorLevel: Integer; procedure SetMonitorLevel(V: Integer); function GetTuning: Boolean; procedure SetTuning(V: Boolean); function GetTwoTone: Boolean; procedure SetTwoTone(V: Boolean); function GetPureSignal: Boolean; procedure SetPureSignal(V: Boolean); procedure DoPSSingleCal; // ── Приёмник и сервис ── function GetMuted: Boolean; procedure SetMuted(V: Boolean); function GetAtten: Integer; procedure SetAtten(V: Integer); function GetSupplyVolts: Double; procedure DoVfoSwap; function GetMemoryCount: Integer; procedure SendCWMacro(V: Integer); procedure SyncMuted; procedure SyncAtten; procedure SyncVfoSwap; procedure SyncCWMacro; // Правка одного поля TX-настроек: копия записи, поле, SetTXSettings. Так же // это делает вкладка Transmit — она тоже отдаёт запись целиком. procedure SyncTXField; procedure SyncTXEQ; procedure SyncTXProfile; procedure SyncMonitorOn; procedure SyncMonitorLevel; procedure SyncTuning; procedure SyncTwoTone; procedure SyncPureSignal; procedure SyncPSSingleCal; procedure DoBandUp; procedure DoBandDown; procedure DoTuneUp; procedure DoTuneDown; procedure DoBandByIndex(Idx: Integer); procedure SetSplit(V: Boolean); procedure SetSquelchOn(V: Boolean); procedure SetSquelchLevel(V: Integer); procedure SetCTCSSOn(V: Boolean); procedure SetCTCSSTone(V: Integer); procedure SetFMRptDir(V: Integer); procedure SetFMRptOffset(V: Double); procedure SetFMStep(V: Integer); procedure SetAGCTop(V: Integer); procedure SetCTun(V: Boolean); // ── Andromeda: форвард callback'ов движка в панель (из потока транспорта) ── procedure PanelVfoStep(V: Integer); procedure PanelEncoder(Enc, Step: Integer); procedure PanelButton(Btn: Integer; State, LongPress: Boolean); procedure PanelVersion(V: 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; procedure SyncSplit; procedure SyncSquelchOn; procedure SyncSquelchLevel; procedure SyncCTCSSOn; procedure SyncCTCSSTone; procedure SyncFMRptDir; procedure SyncFMRptOffset; procedure SyncFMStep; procedure SyncAGCTop; procedure SyncCTun; procedure ApplySliceSettings(const G: TGlobalSettings); // CAT-порты слайсов 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; i: Integer; 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; // Кромки фильтра (ZZFL/ZZFH). Раньше у главного приёмника были nil (фильтр // был индексным) — теперь он хранит те же знаковые кромки, что и слайс. Ctx.GetFilterLow := @GetFilterLow; Ctx.GetFilterHigh := @GetFilterHigh; 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.SetFilterLow := @SetFilterLow; Ctx.SetFilterHigh := @SetFilterHigh; 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; Ctx.GetSplit := @GetSplit; Ctx.SetSplit := @SetSplit; Ctx.GetSquelchOn := @GetSquelchOn; Ctx.SetSquelchOn := @SetSquelchOn; Ctx.GetSquelchLevel := @GetSquelchLevel; Ctx.SetSquelchLevel := @SetSquelchLevel; Ctx.GetCTCSSOn := @GetCTCSSOn; Ctx.SetCTCSSOn := @SetCTCSSOn; Ctx.GetCTCSSTone := @GetCTCSSTone; Ctx.SetCTCSSTone := @SetCTCSSTone; Ctx.GetFMRptDir := @GetFMRptDir; Ctx.SetFMRptDir := @SetFMRptDir; Ctx.GetFMRptOffset := @GetFMRptOffset; Ctx.SetFMRptOffset := @SetFMRptOffset; Ctx.GetFMStep := @GetFMStep; Ctx.SetFMStep := @SetFMStep; Ctx.GetAGCTop := @GetAGCTop; Ctx.SetAGCTop := @SetAGCTop; Ctx.GetCTun := @GetCTun; Ctx.SetCTun := @SetCTun; // Телеграф: скорость/режим кейера и программная передача текста (KY/ZZKY). Ctx.GetCWSpeed := @GetCWSpeed; Ctx.SetCWSpeed := @SetCWSpeed; Ctx.GetCWKeyerMode := @GetCWKeyerMode; Ctx.SetCWKeyerMode := @SetCWKeyerMode; Ctx.SendCWText := @SendCWText; Ctx.CWTextBusy := @GetCWTextBusy; // Телеграф: тон (ZZCL), break-in и его hang-time (ZZCB/ZZCD), сайдтон (ZZCM). Ctx.GetCWPitch := @GetCWPitch; Ctx.SetCWPitch := @SetCWPitch; Ctx.GetCWBreakIn := @GetCWBreakIn; Ctx.SetCWBreakIn := @SetCWBreakIn; Ctx.GetCWHangTime := @GetCWHangTime; Ctx.SetCWHangTime := @SetCWHangTime; Ctx.GetCWMonitor := @GetCWMonitor; Ctx.SetCWMonitor := @SetCWMonitor; // TX-тракт: усиление микрофона, кромки TX-фильтра, компрессор, эквалайзер, // TUN/2TON/PS и выбор TX-профиля. Ctx.GetMicGain := @GetMicGain; Ctx.SetMicGain := @SetMicGain; Ctx.GetTXFilterLow := @GetTXFilterLow; Ctx.SetTXFilterLow := @SetTXFilterLow; Ctx.GetTXFilterHigh := @GetTXFilterHigh; Ctx.SetTXFilterHigh := @SetTXFilterHigh; Ctx.GetCompOn := @GetCompOn; Ctx.SetCompOn := @SetCompOn; Ctx.GetCompGain := @GetCompGain; Ctx.SetCompGain := @SetCompGain; Ctx.GetTXEQOn := @GetTXEQOn; Ctx.SetTXEQOn := @SetTXEQOn; Ctx.GetTXEQ := @GetTXEQ; Ctx.SetTXEQ := @SetTXEQ; Ctx.GetTUNLevel := @GetTUNLevel; Ctx.SetTUNLevel := @SetTUNLevel; Ctx.GetFMDeviation := @GetFMDeviation; Ctx.SetFMDeviation := @SetFMDeviation; Ctx.GetTXProfile := @GetTXProfile; Ctx.SetTXProfile := @SetTXProfile; Ctx.GetTXProfileCount := @GetTXProfileCount; Ctx.GetMonitorOn := @GetMonitorOn; Ctx.SetMonitorOn := @SetMonitorOn; Ctx.GetMonitorLevel := @GetMonitorLevel; Ctx.SetMonitorLevel := @SetMonitorLevel; Ctx.GetTuning := @GetTuning; Ctx.SetTuning := @SetTuning; Ctx.GetTwoTone := @GetTwoTone; Ctx.SetTwoTone := @SetTwoTone; Ctx.GetPureSignal := @GetPureSignal; Ctx.SetPureSignal := @SetPureSignal; Ctx.DoPSSingleCal := @DoPSSingleCal; // Приёмник и сервис: MUT (ZZMA), шаговый аттенюатор (ZZRX), напряжение // питания (ZZRV), обмен VFO (ZZVS), счётчик памяти (ZZMV), CW-макрос (ZZKM). Ctx.GetMuted := @GetMuted; Ctx.SetMuted := @SetMuted; Ctx.GetAtten := @GetAtten; Ctx.SetAtten := @SetAtten; Ctx.GetSupplyVolts := @GetSupplyVolts; Ctx.DoVfoSwap := @DoVfoSwap; Ctx.GetMemoryCount := @GetMemoryCount; Ctx.SendCWMacro := @SendCWMacro; Ctx.OnPanelVfoStep := @PanelVfoStep; Ctx.OnPanelEncoder := @PanelEncoder; Ctx.OnPanelButton := @PanelButton; Ctx.OnPanelVersion := @PanelVersion; FEngine := TCATEngine.Create(Ctx); FSerial := TCATSerialManager.Create(FEngine); FTcp := TCATTcpServer.Create(FEngine); for i := 0 to CAT_SLICE_SLOTS - 1 do FSliceEps[i] := TCATSliceEndpoint.Create(FController, i); // Передняя панель: отправляет наружу через Andromeda-порт, слушает состояние. FPanel := TAndromedaPanel.Create(FController, @FSerial.SendToAndromeda); FController.AddStateListener(@FPanel.OnControllerState); end; destructor TCATAdapter.Destroy; var i: Integer; begin for i := 0 to CAT_SLICE_SLOTS - 1 do FreeAndNil(FSliceEps[i]); if Assigned(FTcp) then begin FTcp.Stop; FreeAndNil(FTcp); end; if Assigned(FSerial) then begin FSerial.StopAll; FreeAndNil(FSerial); end; FreeAndNil(FEngine); FreeAndNil(FPanel); 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; Cfgs[i].Andromeda := G.CATSerialAndromeda[i]; end; FSerial.ApplyConfig(Cfgs); // Если назначен Andromeda-порт — запросить версию панели (рукопожатие ZZZS). if FSerial.HasAndromeda and Assigned(FPanel) then FPanel.RequestVersion; FTcp.Stop; if G.CATTcpEnabled then begin FTcp.Port := G.CATTcpPort; FTcp.Start; end; ApplySliceSettings(G); end; procedure TCATAdapter.ApplySliceSettings(const G: TGlobalSettings); // CAT слайсов: Auto TX — в контроллер (его читают TX-логика и флаг слайса), // порт — в свой эндпоинт. Совпадающие порты не поднимаем: второй bind всё // равно упадёт, а так поведение предсказуемо (выигрывает слот меньшей буквы). var i, j: Integer; Enable: Boolean; begin for i := 0 to CAT_SLICE_SLOTS - 1 do begin FController.SetSliceSlotAutoTx(i, G.CATSliceAutoTX[i]); Enable := G.CATSliceEnabled[i] and (G.CATSlicePort[i] > 0); if Enable and G.CATTcpEnabled and (G.CATSlicePort[i] = G.CATTcpPort) then Enable := False; for j := 0 to i - 1 do if G.CATSliceEnabled[j] and (G.CATSlicePort[j] = G.CATSlicePort[i]) then Enable := False; FSliceEps[i].Apply(Enable, G.CATSlicePort[i]); 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.GetFilterLow: Integer; begin Result := FController.FilterLo; end; function TCATAdapter.GetFilterHigh: Integer; begin Result := FController.FilterHi; end; // Кромку меняем по одной — вторую держим текущей (контроллер уводит правку в // VAR, поэтому пресеты внешняя программа не испортит). procedure TCATAdapter.SetFilterLow(V: Integer); begin FController.SetFilterEdges(V, FController.FilterHi); end; procedure TCATAdapter.SetFilterHigh(V: Integer); begin FController.SetFilterEdges(FController.FilterLo, V); 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; function TCATAdapter.GetSplit: Boolean; begin Result := FController.FSplitTxB; end; function TCATAdapter.GetSquelchOn: Boolean; begin Result := FController.FFMSQOn; end; function TCATAdapter.GetSquelchLevel: Integer; begin Result := FController.FFMSQLevel; end; function TCATAdapter.GetCTCSSOn: Boolean; begin Result := FController.FFMCTCSSOn; end; function TCATAdapter.GetCTCSSTone: Integer; begin Result := FController.FFMCTCSSToneIdx; end; function TCATAdapter.GetFMRptDir: Integer; begin Result := FController.FFMRptDir; end; function TCATAdapter.GetFMRptOffset: Double; begin Result := FController.FFMRptOffsetHz; end; function TCATAdapter.GetFMStep: Integer; begin Result := FController.FFMStepIdx; end; function TCATAdapter.GetAGCTop: Integer; begin Result := FController.FAGCTop; end; function TCATAdapter.GetCTun: Boolean; begin Result := FController.FCTun; 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 < MODE_MIN) or (FSyncInt > MODE_MAX) 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; // ---- Телеграф (KS/KY/ZZKM/ZZKS/ZZKY) -------------------------------------- // Правки настроек и запуск передачи маршалим в поток контроллера, как всё // остальное в адаптере: SetCWSettings трогает движок и пишет конфиг. function TCATAdapter.GetCWSpeed: Integer; begin Result := FController.CWSettings.Speed; end; procedure TCATAdapter.SetCWSpeed(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncCWSpeed); end; procedure TCATAdapter.SyncCWSpeed; var C: TCWSettings; begin C := FController.CWSettings; C.Speed := FSyncInt; FController.SetCWSettings(C); end; function TCATAdapter.GetCWKeyerMode: Integer; begin Result := FController.CWSettings.KeyerMode; end; procedure TCATAdapter.SetCWKeyerMode(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncCWKeyerMode); end; procedure TCATAdapter.SyncCWKeyerMode; var C: TCWSettings; begin C := FController.CWSettings; C.KeyerMode := FSyncInt; FController.SetCWSettings(C); end; procedure TCATAdapter.SendCWText(const S: string); begin FSyncStr := S; FController.Invoke(@SyncCWText); end; procedure TCATAdapter.SyncCWText; begin FController.CWXSend(FSyncStr); end; function TCATAdapter.GetCWTextBusy: Boolean; begin Result := FController.CWXBusy; end; function TCATAdapter.GetCWPitch: Integer; begin Result := FController.CWSettings.Pitch; end; procedure TCATAdapter.SetCWPitch(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncCWPitch); end; procedure TCATAdapter.SyncCWPitch; var C: TCWSettings; begin C := FController.CWSettings; C.Pitch := FSyncInt; FController.SetCWSettings(C); end; function TCATAdapter.GetCWBreakIn: Boolean; begin Result := FController.CWSettings.BreakIn; end; procedure TCATAdapter.SetCWBreakIn(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncCWBreakIn); end; procedure TCATAdapter.SyncCWBreakIn; var C: TCWSettings; begin C := FController.CWSettings; C.BreakIn := FSyncBool; FController.SetCWSettings(C); end; function TCATAdapter.GetCWHangTime: Integer; begin Result := FController.CWSettings.HangTimeMS; end; procedure TCATAdapter.SetCWHangTime(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncCWHangTime); end; procedure TCATAdapter.SyncCWHangTime; var C: TCWSettings; begin C := FController.CWSettings; C.HangTimeMS := FSyncInt; FController.SetCWSettings(C); end; function TCATAdapter.GetCWMonitor: Boolean; // Сайдтонов два, и они не взаимозаменяемы: аппаратный звучит в наушниках // трансивера, программный — в звуковой карте PC. ZZCM правит тот, что отвечает // за текущий источник манипуляции, иначе команда молча промахивалась бы мимо. begin if FController.CWLocalSource then Result := FController.CWSettings.SidetoneSW else Result := FController.CWSettings.SidetoneHW; end; procedure TCATAdapter.SetCWMonitor(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncCWMonitor); end; procedure TCATAdapter.SyncCWMonitor; var C: TCWSettings; begin C := FController.CWSettings; if FController.CWLocalSource then C.SidetoneSW := FSyncBool else C.SidetoneHW := FSyncBool; FController.SetCWSettings(C); end; // ── TX-тракт ───────────────────────────────────────────────────────────────── // Геттеры читают живые TX-настройки контроллера (они же — активный профиль), // сеттеры кладут значение в scratch и правят одно поле в потоке контроллера. function TCATAdapter.GetMicGain: Integer; begin Result := Round(FController.FTXSettings.MicGainDB); end; procedure TCATAdapter.SetMicGain(V: Integer); begin FSyncTXFld := txfMicGain; FSyncInt := V; FController.Invoke(@SyncTXField); end; function TCATAdapter.GetTXFilterLow: Integer; begin Result := FController.FTXSettings.FilterLow; end; procedure TCATAdapter.SetTXFilterLow(V: Integer); begin FSyncTXFld := txfFiltLow; FSyncInt := V; FController.Invoke(@SyncTXField); end; function TCATAdapter.GetTXFilterHigh: Integer; begin Result := FController.FTXSettings.FilterHigh; end; procedure TCATAdapter.SetTXFilterHigh(V: Integer); begin FSyncTXFld := txfFiltHigh; FSyncInt := V; FController.Invoke(@SyncTXField); end; function TCATAdapter.GetCompOn: Boolean; begin Result := FController.FTXSettings.CompressorOn; end; procedure TCATAdapter.SetCompOn(V: Boolean); begin FSyncTXFld := txfCompOn; FSyncBool := V; FController.Invoke(@SyncTXField); end; function TCATAdapter.GetCompGain: Integer; begin Result := Round(FController.FTXSettings.CompressorGain); end; procedure TCATAdapter.SetCompGain(V: Integer); begin FSyncTXFld := txfCompGain; FSyncInt := V; FController.Invoke(@SyncTXField); end; function TCATAdapter.GetTXEQOn: Boolean; begin Result := FController.FTXSettings.EQOn; end; procedure TCATAdapter.SetTXEQOn(V: Boolean); begin FSyncTXFld := txfEQOn; FSyncBool := V; FController.Invoke(@SyncTXField); end; function TCATAdapter.GetTXEQ(out Gains: TCATEQGains): Integer; var i: Integer; begin for i := 0 to High(Gains) do Gains[i] := Round(FController.FTXSettings.EQGains[i]); Result := FController.FTXSettings.EQNumBands; end; procedure TCATAdapter.SetTXEQ(NumBands: Integer; const Gains: TCATEQGains); begin FSyncInt := NumBands; FSyncEQ := Gains; FController.Invoke(@SyncTXEQ); end; procedure TCATAdapter.SyncTXEQ; // Частоты полос команда не несёт (их в формате Thetis попросту нет) — трогаем // только усиления и число полос, сетка остаётся той, что настроена в UI. var T: TTXSettings; i: Integer; begin T := FController.FTXSettings; T.EQNumBands := FSyncInt; for i := 0 to High(FSyncEQ) do if i <= FSyncInt then T.EQGains[i] := FSyncEQ[i]; FController.SetTXSettings(T); end; function TCATAdapter.GetTUNLevel: Integer; begin Result := FController.FTXSettings.TUNLevel; end; procedure TCATAdapter.SetTUNLevel(V: Integer); begin FSyncTXFld := txfTUNLevel; FSyncInt := V; FController.Invoke(@SyncTXField); end; function TCATAdapter.GetFMDeviation: Integer; begin Result := Round(FController.FTXSettings.FMDeviation); end; procedure TCATAdapter.SetFMDeviation(V: Integer); begin FSyncTXFld := txfFMDev; FSyncInt := V; FController.Invoke(@SyncTXField); end; procedure TCATAdapter.SyncTXField; var T: TTXSettings; begin T := FController.FTXSettings; case FSyncTXFld of txfMicGain: T.MicGainDB := FSyncInt; txfFiltLow: T.FilterLow := FSyncInt; txfFiltHigh: T.FilterHigh := FSyncInt; txfCompOn: T.CompressorOn := FSyncBool; txfCompGain: T.CompressorGain := FSyncInt; txfEQOn: T.EQOn := FSyncBool; txfTUNLevel: T.TUNLevel := FSyncInt; txfFMDev: T.FMDeviation := FSyncInt; end; FController.SetTXSettings(T); end; function TCATAdapter.GetTXProfile: Integer; begin Result := FController.ActiveTXProfile; end; procedure TCATAdapter.SetTXProfile(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncTXProfile); end; procedure TCATAdapter.SyncTXProfile; begin FController.SelectTXProfile(FSyncInt); end; function TCATAdapter.GetTXProfileCount: Integer; begin Result := FController.TXProfileCount; end; function TCATAdapter.GetMonitorOn: Boolean; begin Result := not FController.FRxMuteOnTx; end; procedure TCATAdapter.SetMonitorOn(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncMonitorOn); end; procedure TCATAdapter.SyncMonitorOn; begin FController.SetRxMuteOnTx(not FSyncBool); end; function TCATAdapter.GetMonitorLevel: Integer; begin Result := FController.FTxMonVolume; end; procedure TCATAdapter.SetMonitorLevel(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncMonitorLevel); end; procedure TCATAdapter.SyncMonitorLevel; begin FController.SetTXMonVolume(FSyncInt); end; function TCATAdapter.GetTuning: Boolean; begin Result := FController.FTuning; end; procedure TCATAdapter.SetTuning(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncTuning); end; procedure TCATAdapter.SyncTuning; begin FController.SetTune(FSyncBool); end; function TCATAdapter.GetTwoTone: Boolean; begin Result := FController.FTwoTone; end; procedure TCATAdapter.SetTwoTone(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncTwoTone); end; procedure TCATAdapter.SyncTwoTone; begin FController.SetTwoTone(FSyncBool); end; function TCATAdapter.GetPureSignal: Boolean; begin Result := FController.FPSEnabled; end; procedure TCATAdapter.SetPureSignal(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncPureSignal); end; procedure TCATAdapter.SyncPureSignal; begin FController.SetPureSignal(FSyncBool); end; procedure TCATAdapter.DoPSSingleCal; begin FController.Invoke(@SyncPSSingleCal); end; procedure TCATAdapter.SyncPSSingleCal; begin FController.PSSingleCal; end; // ── Приёмник и сервис ──────────────────────────────────────────────────────── function TCATAdapter.GetMuted: Boolean; begin Result := FController.FMuted; end; procedure TCATAdapter.SetMuted(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncMuted); end; procedure TCATAdapter.SyncMuted; begin FController.SetMute(FSyncBool); end; function TCATAdapter.GetAtten: Integer; begin Result := FController.FAtten; end; procedure TCATAdapter.SetAtten(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncAtten); end; procedure TCATAdapter.SyncAtten; begin FController.SetAtten(FSyncInt); end; function TCATAdapter.GetSupplyVolts: Double; // Телеметрия платы; -1 = данных ещё нет (движок отдаст ноль, как Thetis). begin Result := FController.FLastSupplyV; end; procedure TCATAdapter.DoVfoSwap; begin FController.Invoke(@SyncVfoSwap); end; procedure TCATAdapter.SyncVfoSwap; begin FController.VfoSwap; end; function TCATAdapter.GetMemoryCount: Integer; begin if Assigned(FController.FChannelStore) then Result := FController.FChannelStore.Count else Result := 0; end; procedure TCATAdapter.SendCWMacro(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncCWMacro); end; procedure TCATAdapter.SyncCWMacro; // Макрос n (1..9) = ячейка памяти сообщений n. Ячеек восемь (F1..F8): девятая // и пустые просто ничего не передают — очередь дёргать незачем. var Idx: Integer; begin Idx := FSyncInt - 1; if (Idx < 0) or (Idx >= CW_MSG_COUNT) then Exit; if FController.CWSettings.Messages[Idx] = '' then Exit; FController.CWXSend(FController.CWSettings.Messages[Idx]); 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; // ── Split / FM (squelch, CTCSS, repeater, step) ─────────────────────────────── procedure TCATAdapter.SetSplit(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncSplit); end; procedure TCATAdapter.SyncSplit; begin FController.SetSplit(FSyncBool); end; procedure TCATAdapter.SetSquelchOn(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncSquelchOn); end; procedure TCATAdapter.SyncSquelchOn; begin FController.SetFMSquelch(FSyncBool); end; procedure TCATAdapter.SetSquelchLevel(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncSquelchLevel); end; procedure TCATAdapter.SyncSquelchLevel; begin FController.SetFMSquelchLevel(FSyncInt); end; procedure TCATAdapter.SetCTCSSOn(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncCTCSSOn); end; procedure TCATAdapter.SyncCTCSSOn; begin FController.SetFMCTCSS(FSyncBool); end; procedure TCATAdapter.SetCTCSSTone(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncCTCSSTone); end; procedure TCATAdapter.SyncCTCSSTone; begin FController.SetFMCTCSSTone(FSyncInt); end; procedure TCATAdapter.SetFMRptDir(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncFMRptDir); end; procedure TCATAdapter.SyncFMRptDir; begin FController.SetFMRpt(FSyncInt); end; procedure TCATAdapter.SetFMRptOffset(V: Double); begin FSyncFreq := V; FController.Invoke(@SyncFMRptOffset); end; procedure TCATAdapter.SyncFMRptOffset; begin FController.SetFMRptOffset(FSyncFreq); end; procedure TCATAdapter.SetFMStep(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncFMStep); end; procedure TCATAdapter.SyncFMStep; begin FController.SetFMStep(FSyncInt); end; procedure TCATAdapter.SetAGCTop(V: Integer); begin FSyncInt := V; FController.Invoke(@SyncAGCTop); end; procedure TCATAdapter.SyncAGCTop; begin FController.SetAGCTop(FSyncInt); end; procedure TCATAdapter.SetCTun(V: Boolean); begin FSyncBool := V; FController.Invoke(@SyncCTun); end; procedure TCATAdapter.SyncCTun; begin FController.SetCTun(FSyncBool); end; // ── Andromeda: форвард в панель (сама маршалит действия в поток контроллера) ── procedure TCATAdapter.PanelVfoStep(V: Integer); begin if Assigned(FPanel) then FPanel.HandleVfoStep(V); end; procedure TCATAdapter.PanelEncoder(Enc, Step: Integer); begin if Assigned(FPanel) then FPanel.HandleEncoder(Enc, Step); end; procedure TCATAdapter.PanelButton(Btn: Integer; State, LongPress: Boolean); begin if Assigned(FPanel) then FPanel.HandleButton(Btn, State, LongPress); end; procedure TCATAdapter.PanelVersion(V: Integer); begin if Assigned(FPanel) then FPanel.HandleVersion(V); end; end.