mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat(cat): CAT-порт на каждый слайс + Auto TX (эталон SmartSDR CAT)
Внешняя программа управляет отдельным слайсом как отдельным трансивером
через свой TCP-порт. COM-порты и 19090 остаются за главным приёмником
(слайс A) и не тронуты.
Протокол не дублируется: TCATEngine — чистый парсер поверх записи
callback'ов TCATContext, TCATTcpServer — транспорт поверх движка, поэтому
слайс-CAT = тот же движок с другим контекстом. Новый TCATSliceEndpoint
(движок + TCP-сервер на слот), массивом владеет TCATAdapter. Привязка к
СЛОТУ (буква B..G), не к Id: порт живёт, даже когда слайса нет (PS0).
Auto TX = «Auto Switch TX Slice» из SmartSDR CAT. Вся логика в одной точке
— TRadioController.RequestSliceTx, исполняется в потоке контроллера:
• пока кто-то уже в эфире (любой источник) — заявка игнорируется,
перехвата передачи нет никогда;
• без Auto TX слайс передаёт, только если уже выбран TX-источником;
• RX; снимает только СВОЮ передачу;
• TX-источник после отпускания остаётся на слайсе (как в SmartSDR).
На флаге слайса бейдж TX → AutoTX.
Частота слайса — TuneSliceInBand: свобода в пределах ВКЛЮЧЁННОГО диапазона
(трансвертер → его FreqBegin/FreqEnd, иначе band-план), другой диапазон —
отказ. Вне захваченной полосы окно DDC переезжает: доп. пан — центром на
цель (rfPanFreq → UI перекладывает флаги/шапку/зум-бар), пан 0 — центр
посередине между целью и главным VFO и только если оба влезают (общее
железное окно, главный приёмник не оглушаем).
Настройки: вкладка Slices (enable/порт 19091../Auto TX на слайс), персист
в секции cat + device-blob.
Попутно:
• ZZFL/ZZFH больше не заглушки — новые callback'и кромок фильтра
(у главного nil → прежний дефолт режима), хелпер SignedPad;
• FilterIdxFor, AGCModeToUI, BoardUtils.BandEdges;
• DSP-состояние слайса (NR/NB/SNB/ANF) зеркалится в TCtrlSlice — флаг
слайса рисовал нули и AGC главного;
• FUIReady в TMainForm.OnControllerState: адаптеры, создаваемые по ходу
FormCreate, больше не могут уронить старт рендером до постройки
виджетов (на этом падал Auto TX из настроек).
Проверено вживую: все порты 19091..19096 слушают, PS;/ZZFA;/MD;/ID;/SM0;/
ZZTX; отвечают; старт со всеми включёнными слайсами чист под gdb.
Документ — doc/CAT_SLICES.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+193
-3
@@ -76,6 +76,11 @@ type
|
||||
const SerParity: array of Integer;
|
||||
const SerAndromeda: array of Boolean;
|
||||
TcpEnabled: Boolean; TcpPort: Integer) of object;
|
||||
// CAT дополнительных слайсов: индекс = слот (0 = слайс B).
|
||||
TOnSliceCATChange = procedure(
|
||||
const Enabled: array of Boolean;
|
||||
const Port: array of Integer;
|
||||
const AutoTX: array of Boolean) of object;
|
||||
TOnAlexChange = procedure(const A: TAlexSettings) of object;
|
||||
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
|
||||
|
||||
@@ -92,6 +97,7 @@ type
|
||||
FPagePA: TScrollBox;
|
||||
FPageAdvanced: TScrollBox;
|
||||
FPageCAT: TScrollBox;
|
||||
FPageSlices: TScrollBox;
|
||||
FPageTransmit: TScrollBox;
|
||||
FNavAudio: TFlatButton;
|
||||
FNavDisplay: TFlatButton;
|
||||
@@ -100,6 +106,7 @@ type
|
||||
FNavPA: TFlatButton;
|
||||
FNavAdvanced: TFlatButton;
|
||||
FNavCAT: TFlatButton;
|
||||
FNavSlices: TFlatButton;
|
||||
FNavTransmit: TFlatButton;
|
||||
|
||||
// ---- Audio tab controls ----
|
||||
@@ -335,6 +342,10 @@ type
|
||||
FCATSerialAndromeda:array[0..3] of TFlatCheckBox;
|
||||
FCATTcpEn: TFlatCheckBox;
|
||||
FCATTcpPort: TFlatSpinEdit;
|
||||
// ---- Slices tab controls (CAT доп. слайсов; индекс = слот, 0 = слайс B) ----
|
||||
FSliceCATEn: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatCheckBox;
|
||||
FSliceCATPort: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatSpinEdit;
|
||||
FSliceCATAutoTx: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatCheckBox;
|
||||
|
||||
// ---- Advanced tab controls ----
|
||||
FChkDither: TFlatCheckBox;
|
||||
@@ -350,6 +361,7 @@ type
|
||||
|
||||
// ---- Callbacks ----
|
||||
FOnCATChange: TOnCATChange;
|
||||
FOnSliceCATChange: TOnSliceCATChange;
|
||||
FOnPAChange: TOnPASettingsChange;
|
||||
FOnPlutoTxChange: TOnPlutoTxChange;
|
||||
FOnAnt936xChange: TOnAnt936xChange;
|
||||
@@ -393,6 +405,7 @@ type
|
||||
procedure CollectCalFromUI;
|
||||
procedure OnCalAnyChange(Sender: TObject);
|
||||
procedure BuildCATTab;
|
||||
procedure BuildSlicesTab;
|
||||
procedure BuildTransmitTab;
|
||||
procedure BuildAlexTab;
|
||||
procedure BuildAdvancedTab;
|
||||
@@ -459,6 +472,8 @@ type
|
||||
procedure OnCATSerialChange(Sender: TObject);
|
||||
procedure OnCATTcpChange(Sender: TObject);
|
||||
procedure FireCATChange;
|
||||
procedure OnSliceCATCtrlChange(Sender: TObject);
|
||||
procedure FireSliceCATChange;
|
||||
procedure BtnCloseClick(Sender: TObject);
|
||||
|
||||
procedure FireDisplayChange;
|
||||
@@ -508,6 +523,11 @@ type
|
||||
// Таблица разъёмов + имена/видимость слотов трансвертеров.
|
||||
procedure LoadAnt936x(const A: TAD936xAnt; const X: TXvtrSettings);
|
||||
procedure LoadVHFCalSettings(const VHFCal: array of Double);
|
||||
// Слайс-CAT: enable/порт/AutoTX по слотам (0 = слайс B).
|
||||
procedure LoadSliceCATSettings(
|
||||
const Enabled: array of Boolean;
|
||||
const Port: array of Integer;
|
||||
const AutoTX: array of Boolean);
|
||||
procedure LoadCATSettings(
|
||||
const SerEnabled: array of Boolean;
|
||||
const SerPort: array of string;
|
||||
@@ -526,6 +546,7 @@ type
|
||||
const BindAddr, User, Pass: string);
|
||||
|
||||
property OnCATChange: TOnCATChange read FOnCATChange write FOnCATChange;
|
||||
property OnSliceCATChange: TOnSliceCATChange read FOnSliceCATChange write FOnSliceCATChange;
|
||||
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
|
||||
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
|
||||
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
|
||||
@@ -781,9 +802,10 @@ begin
|
||||
FNavCalib := MakeNavButton('Calibration', 290);
|
||||
FNavAdvanced := MakeNavButton('Advanced', 326);
|
||||
FNavCAT := MakeNavButton('CAT', 362);
|
||||
FNavAlex := MakeNavButton('Antenna / Alex', 398);
|
||||
FNavXvtr := MakeNavButton('Transverter', 434);
|
||||
FNavQO := MakeNavButton('QO-100', 470);
|
||||
FNavSlices := MakeNavButton('Slices', 398);
|
||||
FNavAlex := MakeNavButton('Antenna / Alex', 434);
|
||||
FNavXvtr := MakeNavButton('Transverter', 470);
|
||||
FNavQO := MakeNavButton('QO-100', 506);
|
||||
|
||||
FContentPanel := TPanel.Create(Self);
|
||||
FContentPanel.Parent := Self;
|
||||
@@ -800,6 +822,7 @@ begin
|
||||
FPageCalib := MakeScrollPage;
|
||||
FPageAdvanced := MakeScrollPage;
|
||||
FPageCAT := MakeScrollPage;
|
||||
FPageSlices := MakeScrollPage;
|
||||
FPageAlex := MakeScrollPage;
|
||||
FPageXvtr := MakeScrollPage;
|
||||
FPageQO := MakeScrollPage;
|
||||
@@ -816,6 +839,7 @@ begin
|
||||
BuildCalibrationTab;
|
||||
BuildAdvancedTab;
|
||||
BuildCATTab;
|
||||
BuildSlicesTab;
|
||||
BuildAlexTab;
|
||||
BuildXvtrTab;
|
||||
BuildQOTab;
|
||||
@@ -849,6 +873,7 @@ begin
|
||||
else if Sender = FNavCalib then SelectPage(FPageCalib, FNavCalib)
|
||||
else if Sender = FNavAdvanced then SelectPage(FPageAdvanced, FNavAdvanced)
|
||||
else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT)
|
||||
else if Sender = FNavSlices then SelectPage(FPageSlices, FNavSlices)
|
||||
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex)
|
||||
else if Sender = FNavXvtr then SelectPage(FPageXvtr, FNavXvtr)
|
||||
else if Sender = FNavQO then SelectPage(FPageQO, FNavQO);
|
||||
@@ -865,6 +890,7 @@ begin
|
||||
FPageCalib.Visible := APage = FPageCalib;
|
||||
FPageAdvanced.Visible := APage = FPageAdvanced;
|
||||
FPageCAT.Visible := APage = FPageCAT;
|
||||
FPageSlices.Visible := APage = FPageSlices;
|
||||
FPageAlex.Visible := APage = FPageAlex;
|
||||
FPageXvtr.Visible := APage = FPageXvtr;
|
||||
FPageQO.Visible := APage = FPageQO;
|
||||
@@ -878,6 +904,7 @@ begin
|
||||
FNavCalib.Active := ANav = FNavCalib;
|
||||
FNavAdvanced.Active := ANav = FNavAdvanced;
|
||||
FNavCAT.Active := ANav = FNavCAT;
|
||||
FNavSlices.Active := ANav = FNavSlices;
|
||||
FNavAlex.Active := ANav = FNavAlex;
|
||||
FNavXvtr.Active := ANav = FNavXvtr;
|
||||
FNavQO.Active := ANav = FNavQO;
|
||||
@@ -3522,6 +3549,169 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BuildSlicesTab — CAT дополнительных слайсов (B..): свой TCP-порт на слайс.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
procedure TSettingsForm.BuildSlicesTab;
|
||||
const
|
||||
MARGIN = 22;
|
||||
PAD = 18;
|
||||
LW = 120;
|
||||
CX = PAD + LW + 8;
|
||||
GRP_W = 320;
|
||||
GRP_H = 168;
|
||||
GRP_GAP = 16;
|
||||
R1 = 42;
|
||||
STEP = 32;
|
||||
var
|
||||
i, col, row, gx, gy: Integer;
|
||||
Grp: TPanel;
|
||||
Chk: TFlatCheckBox;
|
||||
Spin: TFlatSpinEdit;
|
||||
begin
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
Parent := FPageSlices;
|
||||
Caption := 'Slices';
|
||||
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 := FPageSlices;
|
||||
Caption := 'A separate CAT TCP port per additional slice — external software '
|
||||
+ 'controls its own slice as a standalone transceiver.';
|
||||
SetBounds(MARGIN, 48, 760, 20);
|
||||
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
|
||||
end;
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
Parent := FPageSlices;
|
||||
Caption := 'Slice A is the main receiver — it is configured on the CAT tab '
|
||||
+ '(serial ports and the main TCP port).';
|
||||
SetBounds(MARGIN, 66, 760, 20);
|
||||
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
|
||||
end;
|
||||
|
||||
for i := 0 to CFG_CAT_SLICE_COUNT - 1 do
|
||||
begin
|
||||
row := i div 2;
|
||||
col := i mod 2;
|
||||
gx := MARGIN + col * (GRP_W + GRP_GAP);
|
||||
gy := 96 + row * (GRP_H + GRP_GAP);
|
||||
|
||||
Grp := MakeGroupPanel(FPageSlices, 'Slice ' + Chr(Ord('B') + i), gx, gy, GRP_W, GRP_H);
|
||||
|
||||
Chk := TFlatCheckBox.Create(Self);
|
||||
Chk.Parent := Grp;
|
||||
Chk.Caption := 'Enable CAT TCP server';
|
||||
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 := OnSliceCATCtrlChange;
|
||||
FSliceCATEn[i] := Chk;
|
||||
|
||||
MakeLbl(Grp, 'Port:', PAD, R1 + STEP + 6, LW);
|
||||
Spin := TFlatSpinEdit.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 := CFG_CAT_SLICE_PORT0 + i;
|
||||
Spin.Tag := i;
|
||||
Spin.OnChange := OnSliceCATCtrlChange;
|
||||
FSliceCATPort[i] := Spin;
|
||||
|
||||
// Auto TX = «Auto Switch TX Slice» из SmartSDR CAT: команда TX по этому
|
||||
// порту сама делает слайс источником передачи. Пока кто-то уже в эфире,
|
||||
// заявка игнорируется — передаёт всегда только один слайс. Подпись короткая
|
||||
// (в ширину группы), пояснение — строкой ниже.
|
||||
Chk := TFlatCheckBox.Create(Self);
|
||||
Chk.Parent := Grp;
|
||||
Chk.Caption := 'Auto TX';
|
||||
Chk.SetBounds(PAD, R1 + 2 * STEP, GRP_W - PAD * 2, 22);
|
||||
Chk.Font.Color := CLR_TEXT;
|
||||
Chk.Font.Name := UI_FONT; Chk.Font.Size := 9;
|
||||
Chk.Tag := i;
|
||||
Chk.OnChange := OnSliceCATCtrlChange;
|
||||
FSliceCATAutoTx[i] := Chk;
|
||||
|
||||
MakeLbl(Grp, 'CAT TX takes transmit to this slice',
|
||||
PAD + 2, R1 + 2 * STEP + 24, GRP_W - PAD * 2 - 2);
|
||||
end;
|
||||
|
||||
gy := 96 + ((CFG_CAT_SLICE_COUNT + 1) div 2) * (GRP_H + GRP_GAP);
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
Parent := FPageSlices;
|
||||
Caption := 'Only one slice transmits at a time: while anything is already on '
|
||||
+ 'the air the request is ignored — no take-over.';
|
||||
SetBounds(MARGIN, gy, 760, 20);
|
||||
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
|
||||
end;
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
Parent := FPageSlices;
|
||||
Caption := 'Without Auto TX a slice keys up over CAT only when it is already '
|
||||
+ 'the transmit source (TX badge on the slice flag).';
|
||||
SetBounds(MARGIN, gy + 18, 760, 20);
|
||||
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
|
||||
end;
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
Parent := FPageSlices;
|
||||
Caption := 'The port is opened even when the slice does not exist yet — until '
|
||||
+ 'it is created CAT answers "radio off" (PS0).';
|
||||
SetBounds(MARGIN, gy + 36, 760, 20);
|
||||
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.FireSliceCATChange;
|
||||
var
|
||||
En, Auto: array[0..CFG_CAT_SLICE_COUNT-1] of Boolean;
|
||||
Prt: array[0..CFG_CAT_SLICE_COUNT-1] of Integer;
|
||||
i: Integer;
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
if not Assigned(FOnSliceCATChange) then Exit;
|
||||
for i := 0 to CFG_CAT_SLICE_COUNT - 1 do
|
||||
begin
|
||||
En[i] := FSliceCATEn[i].Checked;
|
||||
Prt[i] := FSliceCATPort[i].Value;
|
||||
Auto[i] := FSliceCATAutoTx[i].Checked;
|
||||
end;
|
||||
FOnSliceCATChange(En, Prt, Auto);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.OnSliceCATCtrlChange(Sender: TObject);
|
||||
begin FireSliceCATChange; end;
|
||||
|
||||
procedure TSettingsForm.LoadSliceCATSettings(
|
||||
const Enabled: array of Boolean;
|
||||
const Port: array of Integer;
|
||||
const AutoTX: array of Boolean);
|
||||
var i: Integer;
|
||||
begin
|
||||
FLoading := True;
|
||||
try
|
||||
for i := 0 to CFG_CAT_SLICE_COUNT - 1 do
|
||||
begin
|
||||
if i <= High(Enabled) then FSliceCATEn[i].Checked := Enabled[i];
|
||||
if (i <= High(Port)) and (Port[i] > 0) then
|
||||
FSliceCATPort[i].Value := EnsureRange(Port[i], 1, 65535);
|
||||
if i <= High(AutoTX) then FSliceCATAutoTx[i].Checked := AutoTX[i];
|
||||
end;
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BuildAlexTab
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user