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; // ── Геттеры (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); 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; 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; 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.