Add full CAT support (serial + TCP)

New units: CATEngine (Kenwood + Thetis ZZ* commands), CATSerial
(up to 4 serial ports), CATTcp (multi-client TCP server on port 4992).
MainForm wired with TCATContext callbacks reusing existing WebOn* handlers.
SettingsForm gets a new CAT tab with 4 serial port groups and TCP section.
Settings.pas extended with CAT config fields (per-device, JSON-persisted).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 21:46:19 +03:00
co-authored by Claude Sonnet 4.6
parent 537d288e1e
commit 36ae140cad
7 changed files with 3450 additions and 2 deletions
+308 -1
View File
@@ -29,7 +29,8 @@ uses
WDSP, WDSPEngine, AudioOutput, AudioInput,
IntfGraphics, FPImage,
Settings,
WebServer;
WebServer,
CATEngine, CATSerial, CATTcp;
const
CLR_BG = TColor($00101010);
@@ -286,6 +287,12 @@ type
// --- Settings ---
FSettings: TSettingsManager;
FWebServer: TWebServer; // веб-интерфейс (порт 8080)
// --- CAT ---
FCATEngine: TCATEngine;
FCATSerial: TCATSerialManager;
FCATTcp: TCATTcpServer;
FCATSyncFreq: Double;
FCATLastGlobal: TGlobalSettings; // текущие CAT-настройки (для сохранения)
// Временные поля для передачи параметров в Synchronize-методы
FWebSyncFreq: Double;
FWebSyncInt: Integer;
@@ -532,6 +539,45 @@ type
procedure SyncWebCenter;
procedure SyncWebMOX;
procedure SyncWebDrive;
// --- 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);
procedure OnCATSettingsChange(
const SerEnabled: array of Boolean;
const SerPort: array of string;
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer);
// -------------------------------------------------------------------------
procedure BtnWfAGCClick(Sender: TObject);
procedure BtnWfNFClick(Sender: TObject);
procedure BtnHidePanelClick(Sender: TObject);
@@ -974,6 +1020,15 @@ begin
// Audio device names
Result.AudioOutDevice := FAudioOutDevName;
Result.AudioInDevice := FAudioInDevName;
// CAT settings — carried from the last loaded/saved device config
Result.CATSerialEnabled := FCATLastGlobal.CATSerialEnabled;
Result.CATSerialPort := FCATLastGlobal.CATSerialPort;
Result.CATSerialBaud := FCATLastGlobal.CATSerialBaud;
Result.CATSerialDataBits := FCATLastGlobal.CATSerialDataBits;
Result.CATSerialStopBits := FCATLastGlobal.CATSerialStopBits;
Result.CATSerialParity := FCATLastGlobal.CATSerialParity;
Result.CATTcpEnabled := FCATLastGlobal.CATTcpEnabled;
Result.CATTcpPort := FCATLastGlobal.CATTcpPort;
end;
procedure TMainForm.SaveCurrentBand;
@@ -1159,6 +1214,8 @@ begin
FWebServer.OnMOX := WebOnMOX;
FWebServer.OnDrive := WebOnDrive;
FWebServer.Start;
FillChar(FCATLastGlobal, SizeOf(FCATLastGlobal), 0);
InitCATEngine;
FSMeterPeak := -130;
FSMeterMin := -130;
FSMeterAvg := -130;
@@ -1298,6 +1355,9 @@ begin
FSettings.Free;
FWebServer.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);
end;
procedure TMainForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
@@ -3909,6 +3969,8 @@ begin
Move(FNetwork.Device.MAC[0], FDevMAC[0], 6);
FDevConnected := True;
FSettings.LoadDevice(FDevMAC, G_Settings, FBandCache);
FCATLastGlobal := G_Settings;
CATApplySettings(G_Settings);
FVolume := G_Settings.Volume;
FActiveVfo := G_Settings.ActiveVfo;
// PA settings
@@ -5790,9 +5852,19 @@ begin
SF.OnVisibilityChange := ApplyVisibility;
SF.OnFPSChange := ApplyFPS;
SF.OnPAChange := OnPASettingsChange;
SF.OnCATChange := OnCATSettingsChange;
end;
SF := TSettingsForm(FSettingsForm);
SF.LoadPASettings(FPAMaxPower, FPABandCal);
SF.LoadCATSettings(
FCATLastGlobal.CATSerialEnabled,
FCATLastGlobal.CATSerialPort,
FCATLastGlobal.CATSerialBaud,
FCATLastGlobal.CATSerialDataBits,
FCATLastGlobal.CATSerialStopBits,
FCATLastGlobal.CATSerialParity,
FCATLastGlobal.CATTcpEnabled,
FCATLastGlobal.CATTcpPort);
// Перечисляем PA устройства
SF.RefreshAudioDevices(FAudioOut, FAudioIn);
SF.LoadVisibility(FShowSpectrum, FShowWaterfall);
@@ -5841,5 +5913,240 @@ begin
EnsureWDSPWisdom;
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 := WebOnMode;
Ctx.SetActiveVfo := WebOnActiveVfo;
Ctx.SetAGCMode := WebOnAGC;
Ctx.SetVolume := WebOnVolume;
Ctx.SetDriveLevel := WebOnDrive;
Ctx.SetFilterIdx := CATSetFilterIdx;
Ctx.SetNRMode := WebOnNR;
Ctx.SetNBMode := WebOnNB;
Ctx.SetSNBEnabled := WebOnSNB;
Ctx.SetANFEnabled := WebOnANF;
Ctx.SetTransmitting := WebOnMOX;
Ctx.DoBandUp := CATDoBandUp;
Ctx.DoBandDown := CATDoBandDown;
Ctx.DoTuneUp := CATDoTuneUp;
Ctx.DoTuneDown := CATDoTuneDown;
Ctx.DoBandByIndex := WebOnBand;
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;
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer);
var i: Integer;
begin
for i := 0 to 3 do
begin
if i <= High(SerEnabled) then FCATLastGlobal.CATSerialEnabled[i] := SerEnabled[i];
if i <= High(SerPort) then FCATLastGlobal.CATSerialPort[i] := SerPort[i];
if i <= High(SerBaud) then FCATLastGlobal.CATSerialBaud[i] := SerBaud[i];
if i <= High(SerDataBits) then FCATLastGlobal.CATSerialDataBits[i] := SerDataBits[i];
if i <= High(SerStopBits) then FCATLastGlobal.CATSerialStopBits[i] := SerStopBits[i];
if i <= High(SerParity) then FCATLastGlobal.CATSerialParity[i] := SerParity[i];
end;
FCATLastGlobal.CATTcpEnabled := TcpEnabled;
FCATLastGlobal.CATTcpPort := TcpPort;
CATApplySettings(FCATLastGlobal);
if FDevConnected then begin
FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings);
FSettings.Save;
end;
end;
// --- Getters (called from CAT thread — read only, no sync needed) -----------
function TMainForm.CATGetVfoA: Double; begin Result := FVfoA; end;
function TMainForm.CATGetVfoB: Double; begin Result := FVfoB; end;
function TMainForm.CATGetMode: Integer; begin Result := FMode; end;
function TMainForm.CATGetActiveVfo: Integer; begin Result := FActiveVfo; end;
function TMainForm.CATGetAGCMode: Integer; begin Result := FAGCMode; end;
function TMainForm.CATGetVolume: Integer; begin Result := FVolume; end;
function TMainForm.CATGetDriveLevel: Integer;begin Result := TrkDrive.Position; end;
function TMainForm.CATGetFilterIdx: Integer; begin Result := FFilter; end;
function TMainForm.CATGetFilterBW: Integer; begin Result := FFilterBW; end;
function TMainForm.CATGetNRMode: Integer; begin Result := BtnNR.Tag; end;
function TMainForm.CATGetNBMode: Integer; begin Result := BtnNB.Tag; end;
function TMainForm.CATGetSNB: Boolean; begin Result := BtnSNB.Tag <> 0; end;
function TMainForm.CATGetANF: Boolean; begin Result := BtnANF.Tag <> 0; end;
function TMainForm.CATGetTX: Boolean; begin Result := FTransmitting; end;
function TMainForm.CATGetRunning: Boolean; begin Result := FRunning; end;
function TMainForm.CATGetSMeter: Double; begin Result := FLastSMeter; end;
function TMainForm.CATGetBand: Integer; begin Result := 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
// WebOnFilter: negative value encodes 0-based index as -(idx+1)
WebOnFilter(-(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
FVfoA := FCATSyncFreq;
if FActiveVfo = 0 then
begin
ApplyVfoA(Round(FVfoA));
end
else
begin
// VFO A is not active — just update display and band indicator
FreqDispA.Frequency := Round(FVfoA);
BandIdx := FreqToBandIdx(FVfoA);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
StyleButton(BtnBand[FCurrentBand], False);
FCurrentBand := BandIdx;
StyleButton(BtnBand[FCurrentBand], True);
end;
end;
end;
procedure TMainForm.SyncCATVfoB;
var BandIdx: Integer;
begin
FVfoB := FCATSyncFreq;
FreqDispB.Frequency := Round(FVfoB);
if FActiveVfo = 1 then
begin
FCenterFreq := FVfoB;
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FRunning then
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
BandIdx := FreqToBandIdx(FVfoB);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
StyleButton(BtnBand[FCurrentBand], False);
FCurrentBand := BandIdx;
StyleButton(BtnBand[FCurrentBand], True);
end;
FRulerLastFreq := -1.0; FRulerLastVfo := -1.0;
DrawSpectrum; PbSpectrum.Invalidate;
if PbRuler <> nil then PbRuler.Invalidate;
UpdateVfoDisplay;
end;
end;
procedure TMainForm.SyncCATBandUp;
begin
if FCurrentBand < BAND_COUNT - 1 then
WebOnBand(FCurrentBand + 1);
end;
procedure TMainForm.SyncCATBandDown;
begin
if FCurrentBand > 0 then
WebOnBand(FCurrentBand - 1);
end;
procedure TMainForm.SyncCATTuneUp;
begin
if FActiveVfo = 0 then
ApplyVfoA(Round(FVfoA) + 10)
else
begin
FCATSyncFreq := FVfoB + 10;
SyncCATVfoB;
end;
end;
procedure TMainForm.SyncCATTuneDown;
begin
if FActiveVfo = 0 then
ApplyVfoA(Round(FVfoA) - 10)
else
begin
FCATSyncFreq := FVfoB - 10;
SyncCATVfoB;
end;
end;
end.