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
+251 -1
View File
@@ -43,6 +43,14 @@ type
TOnAudioInDevChange = procedure(DevIndex: Integer; const DevName: string) of object;
TOnVisibilityChange = procedure(ShowSpectrum, ShowWaterfall: Boolean) of object;
TOnFPSChange = procedure(FPS: Integer) of object;
TOnCATChange = procedure(
const SerEnabled: array of Boolean;
const SerPort: array of string;
const SerBaud: array of Integer;
const SerDataBits: array of Integer;
const SerStopBits: array of Integer;
const SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer) of object;
{ TSettingsForm }
TSettingsForm = class(TForm)
@@ -56,12 +64,14 @@ type
FPageWaterfall: TScrollBox;
FPagePA: TScrollBox;
FPageAdvanced: TScrollBox;
FPageCAT: TScrollBox;
FNavAudio: TFlatButton;
FNavDisplay: TFlatButton;
FNavSpectrum: TFlatButton;
FNavWaterfall: TFlatButton;
FNavPA: TFlatButton;
FNavAdvanced: TFlatButton;
FNavCAT: TFlatButton;
// ---- Audio tab controls ----
FCmbRXDev: TComboBox;
@@ -96,10 +106,21 @@ type
FEdMaxPower: TSpinEdit;
FEdBandCal: array[0..10] of TFloatSpinEdit;
// ---- CAT tab controls ----
FCATSerialEn: array[0..3] of TCheckBox;
FCATSerialPort: array[0..3] of TEdit;
FCATSerialBaud: array[0..3] of TComboBox;
FCATSerialDataBits: array[0..3] of TComboBox;
FCATSerialStopBits: array[0..3] of TComboBox;
FCATSerialParity: array[0..3] of TComboBox;
FCATTcpEn: TCheckBox;
FCATTcpPort: TSpinEdit;
// ---- Close button ----
FBtnClose: TFlatButton;
// ---- Callbacks ----
FOnCATChange: TOnCATChange;
FOnPAChange: TOnPASettingsChange;
FOnDisplayChange: TOnDisplayParamChange;
FOnWaterfallChange: TOnWaterfallParamChange;
@@ -116,6 +137,7 @@ type
procedure BuildDisplayTab;
procedure BuildRX1Tab;
procedure BuildPATab;
procedure BuildCATTab;
procedure ApplyTheme;
procedure NavClick(Sender: TObject);
procedure SelectPage(APage: TWinControl; ANav: TFlatButton);
@@ -147,6 +169,9 @@ type
procedure OnFPSCmbChange(Sender: TObject);
procedure OnMaxPowerChange(Sender: TObject);
procedure OnBandCalChange(Sender: TObject);
procedure OnCATSerialChange(Sender: TObject);
procedure OnCATTcpChange(Sender: TObject);
procedure FireCATChange;
procedure BtnCloseClick(Sender: TObject);
procedure FireDisplayChange;
@@ -179,7 +204,13 @@ type
procedure LoadVisibility(ShowSpectrum, ShowWaterfall: Boolean);
procedure LoadFPS(FPS: Integer);
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
procedure LoadCATSettings(
const SerEnabled: array of Boolean;
const SerPort: array of string;
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer);
property OnCATChange: TOnCATChange read FOnCATChange write FOnCATChange;
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
@@ -209,6 +240,9 @@ const
BTN_H = 24;
GRP_TITLE_H = 22; // высота заголовка в group-панелях (title label + divider)
CAT_BAUD_VALS: array[0..7] of Integer = (1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200);
CAT_BAUD_NAMES: array[0..7] of string = ('1200','2400','4800','9600','19200','38400','57600','115200');
FFT_SIZES: array[0..6] of Integer = (4096, 8192, 16384, 32768, 65536, 131072, 262144);
FFT_NAMES: array[0..6] of string = ('4096','8192','16384','32768','65536','131072','262144');
@@ -391,6 +425,7 @@ begin
FNavWaterfall := MakeNavButton('Waterfall', 182);
FNavPA := MakeNavButton('Power Amplifier', 218);
FNavAdvanced := MakeNavButton('Advanced', 254);
FNavCAT := MakeNavButton('CAT', 290);
FContentPanel := TPanel.Create(Self);
FContentPanel.Parent := Self;
@@ -404,11 +439,13 @@ begin
FPageWaterfall := MakeScrollPage;
FPagePA := MakeScrollPage;
FPageAdvanced := MakeScrollPage;
FPageCAT := MakeScrollPage;
BuildAudioTab;
BuildDisplayTab;
BuildRX1Tab;
BuildPATab;
BuildCATTab;
FBtnClose := TFlatButton.Create(Self);
FBtnClose.Parent := Self;
@@ -435,7 +472,8 @@ begin
else if Sender = FNavSpectrum then SelectPage(FPageSpectrum, FNavSpectrum)
else if Sender = FNavWaterfall then SelectPage(FPageWaterfall, FNavWaterfall)
else if Sender = FNavPA then SelectPage(FPagePA, FNavPA)
else if Sender = FNavAdvanced then SelectPage(FPageAdvanced, FNavAdvanced);
else if Sender = FNavAdvanced then SelectPage(FPageAdvanced, FNavAdvanced)
else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT);
end;
procedure TSettingsForm.SelectPage(APage: TWinControl; ANav: TFlatButton);
@@ -446,6 +484,7 @@ begin
FPageWaterfall.Visible := APage = FPageWaterfall;
FPagePA.Visible := APage = FPagePA;
FPageAdvanced.Visible := APage = FPageAdvanced;
FPageCAT.Visible := APage = FPageCAT;
FNavAudio.Active := ANav = FNavAudio;
FNavDisplay.Active := ANav = FNavDisplay;
@@ -453,6 +492,7 @@ begin
FNavWaterfall.Active := ANav = FNavWaterfall;
FNavPA.Active := ANav = FNavPA;
FNavAdvanced.Active := ANav = FNavAdvanced;
FNavCAT.Active := ANav = FNavCAT;
end;
// ---------------------------------------------------------------------------
@@ -859,6 +899,7 @@ begin
if Assigned(FPageWaterfall) then FPageWaterfall.Color := CLR_BG;
if Assigned(FPagePA) then FPagePA.Color := CLR_BG;
if Assigned(FPageAdvanced) then FPageAdvanced.Color := CLR_BG;
if Assigned(FPageCAT) then FPageCAT.Color := CLR_BG;
end;
// ---------------------------------------------------------------------------
@@ -1294,4 +1335,213 @@ begin
Close;
end;
// ---------------------------------------------------------------------------
// BuildCATTab
// ---------------------------------------------------------------------------
procedure TSettingsForm.BuildCATTab;
const
MARGIN = 22;
PAD = 18;
LW = 120;
CX = PAD + LW + 8;
GRP_W = 320;
GRP_H = 240;
GRP_GAP = 16;
CMB_W = 140;
EDT_W = 160;
R1 = 42;
STEP = 32;
var
i, col, row, gx, gy, j: Integer;
Grp: TPanel;
Chk: TCheckBox;
Ed: TEdit;
Cmb: TComboBox;
Spin: TSpinEdit;
begin
with TLabel.Create(Self) do
begin
Parent := FPageCAT;
Caption := 'CAT';
SetBounds(MARGIN, 18, 400, 28);
Font.Name := UI_FONT; Font.Size := 16;
Font.Style := [fsBold]; Font.Color := CLR_TEXT;
end;
with TLabel.Create(Self) do
begin
Parent := FPageCAT;
Caption := 'Computer Aided Transceiver — serial port and TCP server settings.';
SetBounds(MARGIN, 48, 680, 20);
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
end;
for i := 0 to 3 do
begin
row := i div 2;
col := i mod 2;
gx := MARGIN + col * (GRP_W + GRP_GAP);
gy := 86 + row * (GRP_H + GRP_GAP);
Grp := MakeGroupPanel(FPageCAT, 'Serial Port ' + IntToStr(i + 1), gx, gy, GRP_W, GRP_H);
Chk := TCheckBox.Create(Self);
Chk.Parent := Grp;
Chk.Caption := 'Enable';
Chk.SetBounds(PAD, R1, GRP_W - PAD * 2, 22);
Chk.Font.Color := CLR_TEXT;
Chk.Font.Name := UI_FONT; Chk.Font.Size := 9;
Chk.Tag := i;
Chk.OnChange := OnCATSerialChange;
FCATSerialEn[i] := Chk;
MakeLbl(Grp, 'Port:', PAD, R1 + STEP + 6, LW);
Ed := TEdit.Create(Self);
Ed.Parent := Grp;
Ed.SetBounds(CX, R1 + STEP, EDT_W, BTN_H);
Ed.Color := CLR_INPUT; Ed.Font.Color := CLR_INPUT_TEXT;
Ed.Font.Name := UI_FONT; Ed.Font.Size := 9;
Ed.Tag := i;
Ed.OnChange := OnCATSerialChange;
{$IFDEF MSWINDOWS}
Ed.Text := 'COM' + IntToStr(i + 1);
{$ELSE}
Ed.Text := '/dev/ttyS' + IntToStr(i);
{$ENDIF}
FCATSerialPort[i] := Ed;
MakeLbl(Grp, 'Baud rate:', PAD, R1 + 2 * STEP + 6, LW);
Cmb := MakeCombo(Grp, CX, R1 + 2 * STEP, CMB_W, OnCATSerialChange);
Cmb.Tag := i;
for j := 0 to High(CAT_BAUD_NAMES) do Cmb.Items.Add(CAT_BAUD_NAMES[j]);
Cmb.ItemIndex := 3; // 9600
FCATSerialBaud[i] := Cmb;
MakeLbl(Grp, 'Data bits:', PAD, R1 + 3 * STEP + 6, LW);
Cmb := MakeCombo(Grp, CX, R1 + 3 * STEP, CMB_W, OnCATSerialChange);
Cmb.Tag := i;
Cmb.Items.Add('7'); Cmb.Items.Add('8');
Cmb.ItemIndex := 1; // 8
FCATSerialDataBits[i] := Cmb;
MakeLbl(Grp, 'Stop bits:', PAD, R1 + 4 * STEP + 6, LW);
Cmb := MakeCombo(Grp, CX, R1 + 4 * STEP, CMB_W, OnCATSerialChange);
Cmb.Tag := i;
Cmb.Items.Add('1'); Cmb.Items.Add('2');
Cmb.ItemIndex := 0; // 1
FCATSerialStopBits[i] := Cmb;
MakeLbl(Grp, 'Parity:', PAD, R1 + 5 * STEP + 6, LW);
Cmb := MakeCombo(Grp, CX, R1 + 5 * STEP, CMB_W, OnCATSerialChange);
Cmb.Tag := i;
Cmb.Items.Add('None'); Cmb.Items.Add('Odd'); Cmb.Items.Add('Even');
Cmb.ItemIndex := 0; // None
FCATSerialParity[i] := Cmb;
end;
// TCP server group below the two rows of serial port panels
gy := 86 + 2 * (GRP_H + GRP_GAP);
Grp := MakeGroupPanel(FPageCAT, 'TCP CAT Server', MARGIN, gy, GRP_W * 2 + GRP_GAP, 116);
FCATTcpEn := TCheckBox.Create(Self);
FCATTcpEn.Parent := Grp;
FCATTcpEn.Caption := 'Enable TCP server (default port 4992)';
FCATTcpEn.SetBounds(PAD, R1, 300, 22);
FCATTcpEn.Font.Color := CLR_TEXT;
FCATTcpEn.Font.Name := UI_FONT; FCATTcpEn.Font.Size := 9;
FCATTcpEn.OnChange := OnCATTcpChange;
MakeLbl(Grp, 'Port:', PAD, R1 + STEP + 6, LW);
Spin := TSpinEdit.Create(Self);
Spin.Parent := Grp;
Spin.SetBounds(CX, R1 + STEP, 100, BTN_H + 2);
Spin.Color := CLR_INPUT; Spin.Font.Color := CLR_INPUT_TEXT;
Spin.Font.Name := UI_FONT; Spin.Font.Size := 9;
Spin.MinValue := 1; Spin.MaxValue := 65535;
Spin.Value := 4992;
Spin.OnChange := OnCATTcpChange;
FCATTcpPort := Spin;
end;
// ---------------------------------------------------------------------------
// CAT event handlers
// ---------------------------------------------------------------------------
procedure TSettingsForm.FireCATChange;
var
SerEn: array[0..3] of Boolean;
SerPort: array[0..3] of string;
SerBaud, SerData, SerStop, SerPar: array[0..3] of Integer;
i, Idx: Integer;
begin
if FLoading then Exit;
if not Assigned(FOnCATChange) then Exit;
for i := 0 to 3 do
begin
SerEn[i] := FCATSerialEn[i].Checked;
SerPort[i] := FCATSerialPort[i].Text;
Idx := FCATSerialBaud[i].ItemIndex;
if (Idx >= 0) and (Idx <= High(CAT_BAUD_VALS)) then
SerBaud[i] := CAT_BAUD_VALS[Idx]
else
SerBaud[i] := 9600;
if FCATSerialDataBits[i].ItemIndex = 0 then SerData[i] := 7 else SerData[i] := 8;
if FCATSerialStopBits[i].ItemIndex = 1 then SerStop[i] := 2 else SerStop[i] := 1;
SerPar[i] := FCATSerialParity[i].ItemIndex;
end;
FOnCATChange(SerEn, SerPort, SerBaud, SerData, SerStop, SerPar,
FCATTcpEn.Checked, FCATTcpPort.Value);
end;
procedure TSettingsForm.OnCATSerialChange(Sender: TObject);
begin FireCATChange; end;
procedure TSettingsForm.OnCATTcpChange(Sender: TObject);
begin FireCATChange; end;
// ---------------------------------------------------------------------------
// LoadCATSettings
// ---------------------------------------------------------------------------
procedure TSettingsForm.LoadCATSettings(
const SerEnabled: array of Boolean;
const SerPort: array of string;
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer);
var
i, j, Idx: Integer;
begin
FLoading := True;
try
for i := 0 to 3 do
begin
if i <= High(SerEnabled) then FCATSerialEn[i].Checked := SerEnabled[i];
if i <= High(SerPort) then FCATSerialPort[i].Text := SerPort[i];
if i <= High(SerBaud) then
begin
Idx := 3;
for j := 0 to High(CAT_BAUD_VALS) do
if CAT_BAUD_VALS[j] = SerBaud[i] then begin Idx := j; Break; end;
FCATSerialBaud[i].ItemIndex := Idx;
end;
if i <= High(SerDataBits) then
begin
if SerDataBits[i] = 7 then FCATSerialDataBits[i].ItemIndex := 0
else FCATSerialDataBits[i].ItemIndex := 1;
end;
if i <= High(SerStopBits) then
begin
if SerStopBits[i] = 2 then FCATSerialStopBits[i].ItemIndex := 1
else FCATSerialStopBits[i].ItemIndex := 0;
end;
if i <= High(SerParity) then
FCATSerialParity[i].ItemIndex := EnsureRange(SerParity[i], 0, 2);
end;
FCATTcpEn.Checked := TcpEnabled;
FCATTcpPort.Value := EnsureRange(TcpPort, 1, 65535);
finally
FLoading := False;
end;
end;
end.