mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(cat): wire split/FM/CTUN/AGC-T CAT commands to controller
Реализованы команды, ранее бывшие заглушками — функционал уже был в TRadioController, но не пробрасывался в CAT: - Split TX: ZZSP/FT/FR + split-байт в IF (новый RadioController.SetSplit) - FM Squelch: SQ (0–255), ZZSQ (0–100%), ZZSO (on/off) - FM CTCSS: CT/ZZTA (on), CN (1-based)/ZZTB (0-based тон) - FM-репитер: OF (offset), OS (направление, с трансляцией Kenwood↔RPT_*) - FM step: ZZST (00–03) - CTUN: ZZCN; AGC-T/RF gain: ZZAR - ANF: ZZNT (раньше ошибочно алиас NB) - Шаги VFO: ZZAU/ZZBP (исправлен баг — были алиасы ZZFA/ZZAP), ZZSU TCATContext расширен callback'ами (split, squelch, CTCSS, repeater, step, AGC-T, CTUN); CATAdapter — геттеры/сеттеры/sync с маршалингом через Invoke. Добавлен doc/CAT_STATUS.md — полный статус CAT и план работ. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+121
@@ -52,6 +52,16 @@ type
|
||||
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);
|
||||
@@ -72,6 +82,16 @@ type
|
||||
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);
|
||||
|
||||
// ── Sync-методы (выполняются в потоке контроллера) ──
|
||||
procedure SyncVfoA;
|
||||
@@ -92,6 +112,16 @@ type
|
||||
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;
|
||||
public
|
||||
constructor Create(AController: TRadioController);
|
||||
destructor Destroy; override;
|
||||
@@ -144,6 +174,26 @@ begin
|
||||
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;
|
||||
|
||||
FEngine := TCATEngine.Create(Ctx);
|
||||
FSerial := TCATSerialManager.Create(FEngine);
|
||||
@@ -203,6 +253,16 @@ function TCATAdapter.GetTX: Boolean; begin Result := FController.FTransm
|
||||
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);
|
||||
@@ -321,4 +381,65 @@ 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;
|
||||
|
||||
end.
|
||||
|
||||
+269
-62
@@ -36,6 +36,10 @@ const
|
||||
MODE_CWL = 3; MODE_CWU = 4; MODE_FM = 5;
|
||||
MODE_AM = 6; MODE_SAM = 7;
|
||||
|
||||
// FM repeater offset directions — зеркало FMRepeater.RPT_* (значения должны
|
||||
// совпадать: контроллер хранит FFMRptDir в этих же кодах).
|
||||
RPT_NONE = 0; RPT_MINUS = 1; RPT_PLUS = 2;
|
||||
|
||||
type
|
||||
TCATGetDoubleFunc = function: Double of object;
|
||||
TCATGetIntFunc = function: Integer of object;
|
||||
@@ -81,6 +85,27 @@ type
|
||||
DoTuneUp: TCATProc;
|
||||
DoTuneDown: TCATProc;
|
||||
DoBandByIndex: TCATSetIntProc;
|
||||
// Split / FM (squelch, CTCSS, repeater, step)
|
||||
GetSplit: TCATGetBoolFunc;
|
||||
SetSplit: TCATSetBoolProc;
|
||||
GetSquelchOn: TCATGetBoolFunc;
|
||||
SetSquelchOn: TCATSetBoolProc;
|
||||
GetSquelchLevel: TCATGetIntFunc; // 0..100
|
||||
SetSquelchLevel: TCATSetIntProc; // 0..100
|
||||
GetCTCSSOn: TCATGetBoolFunc;
|
||||
SetCTCSSOn: TCATSetBoolProc;
|
||||
GetCTCSSTone: TCATGetIntFunc; // index 0..37
|
||||
SetCTCSSTone: TCATSetIntProc; // index 0..37
|
||||
GetFMRptDir: TCATGetIntFunc; // 0=none,1=minus,2=plus
|
||||
SetFMRptDir: TCATSetIntProc;
|
||||
GetFMRptOffset: TCATGetDoubleFunc; // Hz
|
||||
SetFMRptOffset: TCATSetDoubleProc; // Hz
|
||||
GetFMStep: TCATGetIntFunc; // index 0..3 (6.25/12.5/20/25 kHz)
|
||||
SetFMStep: TCATSetIntProc; // index 0..3
|
||||
GetAGCTop: TCATGetIntFunc; // AGC-T порог, dB
|
||||
SetAGCTop: TCATSetIntProc; // AGC-T порог, dB
|
||||
GetCTun: TCATGetBoolFunc; // Click-tune (CTUN) вкл/выкл
|
||||
SetCTun: TCATSetBoolProc;
|
||||
end;
|
||||
|
||||
TCATEngine = class
|
||||
@@ -111,6 +136,17 @@ type
|
||||
function SafeGetRunning: Boolean;
|
||||
function SafeGetSMeter: Double;
|
||||
function SafeGetBand: Integer;
|
||||
function SafeGetSplit: Boolean;
|
||||
function SafeGetSquelchOn: Boolean;
|
||||
function SafeGetSquelchLevel: Integer;
|
||||
function SafeGetCTCSSOn: Boolean;
|
||||
function SafeGetCTCSSTone: Integer;
|
||||
function SafeGetFMRptDir: Integer;
|
||||
function SafeGetFMRptOffset: Double;
|
||||
function SafeGetFMStep: Integer;
|
||||
function SafeGetAGCTop: Integer;
|
||||
function SafeGetCTun: Boolean;
|
||||
function StepIdxToHz(Idx: Integer): Double;
|
||||
|
||||
// command implementations
|
||||
function CmdAG(const s: string): string;
|
||||
@@ -165,8 +201,10 @@ type
|
||||
function ZZAR(const s: string): string;
|
||||
function ZZAS(const s: string): string;
|
||||
function ZZAT(const s: string): string;
|
||||
function ZZAU(const s: string): string;
|
||||
function ZZBA: string;
|
||||
function ZZBD: string;
|
||||
function ZZBP(const s: string): string;
|
||||
function ZZBE(const s: string): string;
|
||||
function ZZBI(const s: string): string;
|
||||
function ZZBM(const s: string): string;
|
||||
@@ -241,7 +279,7 @@ type
|
||||
function ZZSP(const s: string): string;
|
||||
function ZZSQ(const s: string): string;
|
||||
function ZZSR(const s: string): string;
|
||||
function ZZST: string;
|
||||
function ZZST(const s: string): string;
|
||||
function ZZSU: string;
|
||||
function ZZTA(const s: string): string;
|
||||
function ZZTB(const s: string): string;
|
||||
@@ -433,6 +471,55 @@ function TCATEngine.SafeGetBand: Integer;
|
||||
begin
|
||||
if Assigned(FCtx.GetCurrentBand) then Result := FCtx.GetCurrentBand else Result := 5;
|
||||
end;
|
||||
function TCATEngine.SafeGetSplit: Boolean;
|
||||
begin
|
||||
if Assigned(FCtx.GetSplit) then Result := FCtx.GetSplit else Result := False;
|
||||
end;
|
||||
function TCATEngine.SafeGetSquelchOn: Boolean;
|
||||
begin
|
||||
if Assigned(FCtx.GetSquelchOn) then Result := FCtx.GetSquelchOn else Result := False;
|
||||
end;
|
||||
function TCATEngine.SafeGetSquelchLevel: Integer;
|
||||
begin
|
||||
if Assigned(FCtx.GetSquelchLevel) then Result := FCtx.GetSquelchLevel else Result := 0;
|
||||
end;
|
||||
function TCATEngine.SafeGetCTCSSOn: Boolean;
|
||||
begin
|
||||
if Assigned(FCtx.GetCTCSSOn) then Result := FCtx.GetCTCSSOn else Result := False;
|
||||
end;
|
||||
function TCATEngine.SafeGetCTCSSTone: Integer;
|
||||
begin
|
||||
if Assigned(FCtx.GetCTCSSTone) then Result := FCtx.GetCTCSSTone else Result := 0;
|
||||
end;
|
||||
function TCATEngine.SafeGetFMRptDir: Integer;
|
||||
begin
|
||||
if Assigned(FCtx.GetFMRptDir) then Result := FCtx.GetFMRptDir else Result := 0;
|
||||
end;
|
||||
function TCATEngine.SafeGetFMRptOffset: Double;
|
||||
begin
|
||||
if Assigned(FCtx.GetFMRptOffset) then Result := FCtx.GetFMRptOffset else Result := 0;
|
||||
end;
|
||||
function TCATEngine.SafeGetFMStep: Integer;
|
||||
begin
|
||||
if Assigned(FCtx.GetFMStep) then Result := FCtx.GetFMStep else Result := 0;
|
||||
end;
|
||||
function TCATEngine.SafeGetAGCTop: Integer;
|
||||
begin
|
||||
if Assigned(FCtx.GetAGCTop) then Result := FCtx.GetAGCTop else Result := 0;
|
||||
end;
|
||||
function TCATEngine.SafeGetCTun: Boolean;
|
||||
begin
|
||||
if Assigned(FCtx.GetCTun) then Result := FCtx.GetCTun else Result := False;
|
||||
end;
|
||||
function TCATEngine.StepIdxToHz(Idx: Integer): Double;
|
||||
// Шаг настройки по индексу (0..14) — зеркало Thetis Step2Freq (в Гц).
|
||||
const Steps: array[0..14] of Double = (
|
||||
1, 10, 25, 50, 100, 250, 500, 1000, 5000, 9000,
|
||||
10000, 100000, 250000, 500000, 1000000);
|
||||
begin
|
||||
if (Idx < 0) or (Idx > 14) then Result := 0
|
||||
else Result := Steps[Idx];
|
||||
end;
|
||||
|
||||
{ ── Constructor / Destructor ─────────────────────────────────────────────── }
|
||||
|
||||
@@ -570,23 +657,27 @@ begin
|
||||
end;
|
||||
|
||||
function TCATEngine.CmdCN(const s: string): string;
|
||||
// CN: CTCSS tone — STUB: FM sub-audible tones not implemented
|
||||
// CN: CTCSS tone number (2 digits, 1-based 01..38 по таблице CTCSS_TONES).
|
||||
var idx: Integer;
|
||||
begin
|
||||
if Length(s) = 2 then
|
||||
Result := ''
|
||||
else if Length(s) = 0 then
|
||||
Result := '00'
|
||||
if Length(s) = 2 then begin
|
||||
idx := EnsureRange(StrToIntDef(s, 1) - 1, 0, 37);
|
||||
if Assigned(FCtx.SetCTCSSTone) then FCtx.SetCTCSSTone(idx);
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := Pad(EnsureRange(SafeGetCTCSSTone, 0, 37) + 1, 2)
|
||||
else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.CmdCT(const s: string): string;
|
||||
// CT: CTCSS enable — STUB: not implemented
|
||||
// CT: CTCSS enable (1=on, 0=off) для FM-передачи.
|
||||
begin
|
||||
if Length(s) = 1 then
|
||||
Result := ''
|
||||
else if Length(s) = 0 then
|
||||
Result := '0'
|
||||
if Length(s) = 1 then begin
|
||||
if Assigned(FCtx.SetCTCSSOn) then FCtx.SetCTCSSOn(s = '1');
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := B2C(SafeGetCTCSSOn)
|
||||
else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
@@ -622,13 +713,20 @@ begin
|
||||
end;
|
||||
|
||||
function TCATEngine.CmdFR(const s: string): string;
|
||||
// FR: receive VFO — always VFO A, dummy for compat
|
||||
// FR: receive VFO (0=VFO A, 1=VFO B) — выбирает активный приёмный VFO.
|
||||
begin
|
||||
if Length(s) = 1 then
|
||||
Result := ''
|
||||
else if Length(s) = 0 then
|
||||
Result := '0'
|
||||
if Length(s) = 1 then begin
|
||||
if (s = '0') or (s = '1') then begin
|
||||
if Assigned(FCtx.SetActiveVfo) then FCtx.SetActiveVfo(StrToIntDef(s, 0));
|
||||
Result := '';
|
||||
end else
|
||||
Result := CAT_ERROR;
|
||||
end else if Length(s) = 0 then begin
|
||||
if Assigned(FCtx.GetActiveVfo) then
|
||||
Result := IntToStr(EnsureRange(FCtx.GetActiveVfo, 0, 1))
|
||||
else
|
||||
Result := '0';
|
||||
end else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
@@ -677,14 +775,14 @@ var
|
||||
m: Integer;
|
||||
begin
|
||||
freq := FreqToStr(SafeGetVfoA);
|
||||
step := '0000'; // step size — STUB: no step control
|
||||
step := '0000'; // step size — FM step не маппится на Kenwood-формат IF (см. ZZST)
|
||||
incr := '+00000'; // RIT/XIT value — STUB: no RIT/XIT
|
||||
rit := '0';
|
||||
xit := '0';
|
||||
m := ModeToK(SafeGetMode);
|
||||
mode := IntToStr(m);
|
||||
tx := B2C(SafeGetTX);
|
||||
split := '0'; // STUB: no split
|
||||
split := B2C(SafeGetSplit);
|
||||
Result := freq + step + incr + rit + xit + '000' + tx + mode + '0' + '0' + split + '0000';
|
||||
end;
|
||||
|
||||
@@ -765,19 +863,38 @@ begin
|
||||
end;
|
||||
|
||||
function TCATEngine.CmdOF(const s: string): string;
|
||||
// OF: FM repeater offset frequency — STUB: no FM repeater
|
||||
// OF: FM repeater offset frequency (9 digits, Hz).
|
||||
begin
|
||||
if Length(s) = 9 then Result := ''
|
||||
else if Length(s) = 0 then Result := '000000000'
|
||||
else Result := CAT_ERROR;
|
||||
if Length(s) = 9 then begin
|
||||
if Assigned(FCtx.SetFMRptOffset) then FCtx.SetFMRptOffset(StrToIntDef(s, 0));
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := Pad(Round(SafeGetFMRptOffset), 9)
|
||||
else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.CmdOS(const s: string): string;
|
||||
// OS: repeater offset direction — STUB
|
||||
// OS: repeater offset direction (Kenwood: 0=simplex, 1=plus, 2=minus).
|
||||
// Внутренние константы: RPT_NONE=0, RPT_MINUS=1, RPT_PLUS=2 → требуется трансляция.
|
||||
var dir: Integer;
|
||||
begin
|
||||
if Length(s) = 1 then Result := ''
|
||||
else if Length(s) = 0 then Result := '0'
|
||||
else Result := CAT_ERROR;
|
||||
if Length(s) = 1 then begin
|
||||
case StrToIntDef(s, 0) of
|
||||
1: dir := RPT_PLUS;
|
||||
2: dir := RPT_MINUS;
|
||||
else dir := RPT_NONE;
|
||||
end;
|
||||
if Assigned(FCtx.SetFMRptDir) then FCtx.SetFMRptDir(dir);
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then begin
|
||||
case SafeGetFMRptDir of
|
||||
RPT_PLUS: Result := '1';
|
||||
RPT_MINUS: Result := '2';
|
||||
else Result := '0';
|
||||
end;
|
||||
end else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.CmdPC(const s: string): string;
|
||||
@@ -888,11 +1005,21 @@ begin
|
||||
end;
|
||||
|
||||
function TCATEngine.CmdSQ(const s: string): string;
|
||||
// SQ: squelch — STUB: no squelch control
|
||||
// SQ: squelch. SET: <sel><lll> (sel=0/1 RX, lll=000–255). GET: <sel> → <sel><lll>.
|
||||
// Шкала Kenwood 0–255 маппится на внутренний уровень 0–100 %.
|
||||
var lvl255, lvl100: Integer;
|
||||
begin
|
||||
if Length(s) = 4 then Result := ''
|
||||
else if Length(s) = 1 then Result := '0000'
|
||||
else Result := CAT_ERROR;
|
||||
if Length(s) = 4 then begin
|
||||
lvl255 := EnsureRange(StrToIntDef(Copy(s, 2, 3), 0), 0, 255);
|
||||
lvl100 := Round(lvl255 * 100 / 255);
|
||||
if Assigned(FCtx.SetSquelchLevel) then FCtx.SetSquelchLevel(lvl100);
|
||||
if Assigned(FCtx.SetSquelchOn) then FCtx.SetSquelchOn(lvl100 > 0);
|
||||
Result := '';
|
||||
end else if Length(s) = 1 then begin
|
||||
lvl255 := Round(EnsureRange(SafeGetSquelchLevel, 0, 100) * 255 / 100);
|
||||
Result := s + Pad(lvl255, 3);
|
||||
end else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.CmdTX(const s: string): string;
|
||||
@@ -936,7 +1063,7 @@ begin
|
||||
else if ext = 'AR' then ans := ZZAR(s)
|
||||
else if ext = 'AS' then ans := ZZAS(s)
|
||||
else if ext = 'AT' then ans := ZZAT(s)
|
||||
else if ext = 'AU' then ans := ZZFA(s) // alias ZZFA
|
||||
else if ext = 'AU' then ans := ZZAU(s)
|
||||
else if ext = 'AY' then ans := '' // STUB: APF type
|
||||
else if ext = 'BA' then ans := ZZBA
|
||||
else if ext = 'BB' then ans := '' // STUB: band button state
|
||||
@@ -946,7 +1073,7 @@ begin
|
||||
else if ext = 'BG' then ans := '' // STUB: sub-RX gain
|
||||
else if ext = 'BI' then ans := ZZBI(s)
|
||||
else if ext = 'BM' then ans := ZZBM(s)
|
||||
else if ext = 'BP' then ans := ZZAP(s) // alias ZZAP
|
||||
else if ext = 'BP' then ans := ZZBP(s)
|
||||
else if ext = 'BR' then ans := '' // STUB: sub-RX enable
|
||||
else if ext = 'BS' then ans := ZZBS(s)
|
||||
else if ext = 'BT' then ans := '' // STUB: band TX
|
||||
@@ -1130,7 +1257,7 @@ begin
|
||||
else if ext = 'SQ' then ans := ZZSQ(s)
|
||||
else if ext = 'SR' then ans := ZZSR(s)
|
||||
else if ext = 'SS' then ans := '' // STUB: sub-RX
|
||||
else if ext = 'ST' then begin ZZST; ans := ''; end
|
||||
else if ext = 'ST' then ans := ZZST(s)
|
||||
else if ext = 'SU' then begin ZZSU; ans := ''; end
|
||||
else if ext = 'SV' then ans := '' // STUB: spectrum value
|
||||
else if ext = 'SW' then ans := '' // STUB: spectrum write
|
||||
@@ -1299,11 +1426,44 @@ function TCATEngine.ZZAP(const s: string): string;
|
||||
begin Result := ZZPC(s); end;
|
||||
|
||||
function TCATEngine.ZZAR(const s: string): string;
|
||||
// ZZAR: RIT enable — STUB: no RIT
|
||||
// ZZAR: RX1 AGC-T порог (RF gain). Формат: знак + 3 цифры (напр. "+020", "+120").
|
||||
// Контроллер клампит в свой диапазон (20..120 dB).
|
||||
var v: Integer;
|
||||
begin
|
||||
if Length(s) = 1 then Result := ''
|
||||
else if Length(s) = 0 then Result := '0'
|
||||
else Result := CAT_ERROR;
|
||||
if Length(s) = 4 then begin
|
||||
v := StrToIntDef(s, 0); // StrToIntDef понимает ведущий +/-
|
||||
if Assigned(FCtx.SetAGCTop) then FCtx.SetAGCTop(v);
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then begin
|
||||
v := SafeGetAGCTop;
|
||||
if v >= 0 then Result := '+' + Pad(v, 3)
|
||||
else Result := '-' + Pad(-v, 3);
|
||||
end else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZAU(const s: string): string;
|
||||
// ZZAU: сдвиг VFO A вверх на один шаг с индексом nn (00..14, см. StepIdxToHz).
|
||||
var idx: Integer;
|
||||
begin
|
||||
if Length(s) = 2 then begin
|
||||
idx := EnsureRange(StrToIntDef(s, 0), 0, 14);
|
||||
if Assigned(FCtx.SetVfoA) then FCtx.SetVfoA(SafeGetVfoA + StepIdxToHz(idx));
|
||||
Result := '';
|
||||
end else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZBP(const s: string): string;
|
||||
// ZZBP: сдвиг VFO B вверх на один шаг с индексом nn (00..14, см. StepIdxToHz).
|
||||
var idx: Integer;
|
||||
begin
|
||||
if Length(s) = 2 then begin
|
||||
idx := EnsureRange(StrToIntDef(s, 0), 0, 14);
|
||||
if Assigned(FCtx.SetVfoB) then FCtx.SetVfoB(SafeGetVfoB + StepIdxToHz(idx));
|
||||
Result := '';
|
||||
end else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZAS(const s: string): string;
|
||||
@@ -1384,9 +1544,13 @@ begin
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZCN(const s: string): string;
|
||||
// ZZCN: compressor on — STUB: TX DSP not implemented
|
||||
// ZZCN: CTUN (click-tune) для RX1 вкл/выкл.
|
||||
begin
|
||||
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
|
||||
if Length(s) = 1 then begin
|
||||
if Assigned(FCtx.SetCTun) then FCtx.SetCTun(s = '1');
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := B2C(SafeGetCTun)
|
||||
else Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
@@ -1712,8 +1876,15 @@ begin
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZNT(const s: string): string;
|
||||
// ZZNT: noise blanker type — STUB: maps to NB mode
|
||||
begin Result := ZZNB(s); end;
|
||||
// ZZNT: ANF (auto-notch) вкл/выкл.
|
||||
begin
|
||||
if Length(s) = 1 then begin
|
||||
if Assigned(FCtx.SetANFEnabled) then FCtx.SetANFEnabled(s = '1');
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := B2C(SafeGetANF)
|
||||
else Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZOA(const s: string): string;
|
||||
// ZZOA: RX1 output level — alias ZZAG
|
||||
@@ -1842,28 +2013,43 @@ begin
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZSO(const s: string): string;
|
||||
// ZZSO: sub-RX on — STUB: no RX2
|
||||
// ZZSO: squelch on/off (расширенный статус шумоподавителя). 0=off, иначе on.
|
||||
begin
|
||||
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
|
||||
if Length(s) = 1 then begin
|
||||
if Assigned(FCtx.SetSquelchOn) then FCtx.SetSquelchOn(s <> '0');
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := B2C(SafeGetSquelchOn)
|
||||
else Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZSP(const s: string): string;
|
||||
// ZZSP: split mode — STUB: split VFO not yet implemented in ewsdr
|
||||
// ZZSP: split mode — 1=TX на VFO-B, 0=TX на VFO-A. TX-частоту радио берёт из
|
||||
// ActiveTXFreqHz (учитывает split), поэтому достаточно выставить флаг.
|
||||
begin
|
||||
if Length(s) = 1 then
|
||||
Result := '' // accepted, ignored — STUB: split VFO requires separate TX freq handling
|
||||
else if Length(s) = 0 then
|
||||
Result := '0'
|
||||
if Length(s) = 1 then begin
|
||||
if Assigned(FCtx.SetSplit) then FCtx.SetSplit(s = '1');
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := B2C(SafeGetSplit)
|
||||
else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZSQ(const s: string): string;
|
||||
// ZZSQ: squelch — STUB
|
||||
// ZZSQ: squelch threshold (3 digits, 000–100 %). Уровень>0 включает шумоподавитель,
|
||||
// 000 — открывает (выключает). Маппится на FM-squelch контроллера.
|
||||
var v: Integer;
|
||||
begin
|
||||
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '000'
|
||||
else Result := CAT_ERROR;
|
||||
if Length(s) = 3 then begin
|
||||
v := EnsureRange(StrToIntDef(s, 0), 0, 100);
|
||||
if Assigned(FCtx.SetSquelchLevel) then FCtx.SetSquelchLevel(v);
|
||||
if Assigned(FCtx.SetSquelchOn) then FCtx.SetSquelchOn(v > 0);
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := Pad(SafeGetSquelchLevel, 3)
|
||||
else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZSR(const s: string): string;
|
||||
@@ -1874,26 +2060,47 @@ begin
|
||||
else Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZST: string;
|
||||
// ZZST: tuning step — STUB: returns 0000 (1 Hz step)
|
||||
begin Result := ''; end;
|
||||
function TCATEngine.ZZST(const s: string): string;
|
||||
// ZZST: FM tuning step index (2 digits): 00=6.25k, 01=12.5k, 02=20k, 03=25k.
|
||||
begin
|
||||
if Length(s) = 2 then begin
|
||||
if Assigned(FCtx.SetFMStep) then FCtx.SetFMStep(EnsureRange(StrToIntDef(s, 0), 0, 3));
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := Pad(EnsureRange(SafeGetFMStep, 0, 3), 2)
|
||||
else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZSU: string;
|
||||
// ZZSU: sub-RX update — STUB
|
||||
begin Result := ''; end;
|
||||
// ZZSU: шаг настройки вверх (как колесо мыши) — сдвигает активный VFO вверх.
|
||||
begin
|
||||
if Assigned(FCtx.DoTuneUp) then FCtx.DoTuneUp;
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZTA(const s: string): string;
|
||||
// ZZTA: CTCSS on/off — STUB: FM CTCSS not implemented
|
||||
// ZZTA: CTCSS on/off (1=on, 0=off) для FM-передачи.
|
||||
begin
|
||||
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
|
||||
else Result := CAT_ERROR;
|
||||
if Length(s) = 1 then begin
|
||||
if Assigned(FCtx.SetCTCSSOn) then FCtx.SetCTCSSOn(s = '1');
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := B2C(SafeGetCTCSSOn)
|
||||
else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZTB(const s: string): string;
|
||||
// ZZTB: CTCSS tone frequency index — STUB
|
||||
// ZZTB: CTCSS tone index (2 digits, 0-based 00..37 в таблице CTCSS_TONES).
|
||||
begin
|
||||
if Length(s) = 2 then Result := '' else if Length(s) = 0 then Result := '01'
|
||||
else Result := CAT_ERROR;
|
||||
if Length(s) = 2 then begin
|
||||
if Assigned(FCtx.SetCTCSSTone) then FCtx.SetCTCSSTone(EnsureRange(StrToIntDef(s, 0), 0, 37));
|
||||
Result := '';
|
||||
end else if Length(s) = 0 then
|
||||
Result := Pad(EnsureRange(SafeGetCTCSSTone, 0, 37), 2)
|
||||
else
|
||||
Result := CAT_ERROR;
|
||||
end;
|
||||
|
||||
function TCATEngine.ZZTX(const s: string): string;
|
||||
|
||||
@@ -513,6 +513,7 @@ type
|
||||
// Передача
|
||||
procedure SetMOX(On_: Boolean);
|
||||
procedure SetTune(On_: Boolean); // TUN
|
||||
procedure SetSplit(TxOnB: Boolean); // split: TX на VFO-B (иначе VFO-A)
|
||||
|
||||
// FM
|
||||
procedure SetFMStep(Idx: Integer);
|
||||
@@ -2686,6 +2687,17 @@ begin
|
||||
Changed(rfFMSQLevel);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSplit(TxOnB: Boolean);
|
||||
// Split: TX-VFO = B (TxOnB) либо A. TX-частота берётся из ActiveTXFreqHz
|
||||
// (он смотрит на FSplitTxB), поэтому достаточно протолкнуть HP-кадр в сеть.
|
||||
// Рендер VFO-дисплеев и подсветки TX-A/TX-B кнопок — через rfActiveVfo.
|
||||
begin
|
||||
if FSplitTxB = TxOnB then Exit;
|
||||
FSplitTxB := TxOnB;
|
||||
PushNetworkState;
|
||||
Changed(rfActiveVfo);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetFMRpt(Dir: Integer);
|
||||
begin
|
||||
FFMRptDir := Dir;
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
# CAT-система EWSDR — статус реализации
|
||||
|
||||
Ветка разработки: `feature/cat-commands-expansion`.
|
||||
Дата последнего обновления: 2026-06-26.
|
||||
|
||||
Документ описывает текущее состояние CAT-подсистемы EWSDR: архитектуру,
|
||||
полный перечень реализованных команд, осознанные заглушки и план дальнейших
|
||||
работ. За эталон протокола взят **Thetis / PowerSDR** (`CATParser.cs`,
|
||||
`CATCommands.cs`, `CATStructs.xml`) — диалект Kenwood TS-2000 + расширения `ZZ*`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Архитектура
|
||||
|
||||
```
|
||||
CAT-транспорты ──► TCATEngine (парсер протокола) ──► TCATContext ──► TCATAdapter ──► TRadioController
|
||||
serial (4×) CATEngine.pas (callbacks) CATAdapter.pas (ядро)
|
||||
TCP-сервер разбор/формат команд мост, маршалинг
|
||||
```
|
||||
|
||||
| Файл | Назначение |
|
||||
|---|---|
|
||||
| `CATEngine.pas` (~2270 строк) | Движок протокола: разбор `<PREFIX><SUFFIX>;`, формирование ответов, реализации всех команд Kenwood + `ZZ*`. Зависит только от RTL. |
|
||||
| `CATAdapter.pas` (~445 строк) | Мост к `TRadioController`. Строит `TCATContext`: геттеры читают поля контроллера напрямую (CAT-поток, read-only), сеттеры/команды маршалятся в поток контроллера через `FController.Invoke`. Владеет движком + транспортами. |
|
||||
| `CATSerial.pas` (~313 строк) | Менеджер до 4 последовательных портов (виртуальные COM-пары для rigctld/N1MM/WSJT-X). |
|
||||
| `CATTcp.pas` (~507 строк) | TCP-сервер (Thetis-совместимый), управление клиентами по GUID (`ZZGA`/`ZZGR`). |
|
||||
|
||||
### 1.1 Принципы
|
||||
|
||||
- **CAT — равноправный клиент контроллера** наряду с `WebAdapter` и GUI. Никаких
|
||||
обращений к `MainForm`. Только `TRadioController`.
|
||||
- **Потокобезопасность.** Геттеры читают поля контроллера напрямую (атомарное
|
||||
чтение в CAT-потоке). Сеттеры пишут параметр в scratch-поля
|
||||
(`FSyncFreq/FSyncInt/FSyncBool`) и зовут `FController.Invoke(@SyncXxx)` —
|
||||
GUI исполняет через `TThread.Synchronize`, демон — своей очередью.
|
||||
- **`TCATContext`** — плоская запись callback'ов (`Get*`/`Set*`/`Do*`). Чтобы
|
||||
пробросить новую возможность контроллера в CAT, достаточно: (1) добавить
|
||||
callback в запись, (2) реализовать в адаптере геттер/сеттер/sync, (3)
|
||||
зарегистрировать в `TCATAdapter.Create`, (4) переписать тело команды в движке.
|
||||
- **Формат команд:**
|
||||
```
|
||||
SET: <PREFIX><SUFFIX>; (suffix = значение)
|
||||
GET: <PREFIX>; (suffix пустой)
|
||||
Resp: <PREFIX><VALUE>;
|
||||
Err: ?; (неверный формат) | E; (ошибка выполнения)
|
||||
```
|
||||
Расширенные `ZZ*` дублируют Kenwood-команды и добавляют то, чего в Kenwood нет.
|
||||
|
||||
### 1.2 Статистика покрытия (ZZ-команды)
|
||||
|
||||
| | Кол-во |
|
||||
|---|---|
|
||||
| Всего `ZZ*` в Thetis | 315 |
|
||||
| Реально реализовано в EWSDR | **98** |
|
||||
| Заглушки (отвечают валидным dummy, функции нет) | ~211 |
|
||||
| Совсем не обрабатываются (вернут `?;`) | 7 |
|
||||
|
||||
Большинство заглушек закрыть **нечем** — в EWSDR физически отсутствует
|
||||
соответствующий тракт (см. §4).
|
||||
|
||||
---
|
||||
|
||||
## 2. Что сделано
|
||||
|
||||
### 2.1 Реализованные команды Kenwood (одиночный префикс)
|
||||
|
||||
| Команда | Функция | Маппинг на контроллер |
|
||||
|---|---|---|
|
||||
| `FA` / `FB` | VFO A / B частота (11 цифр, Гц) | `Set/GetVfoA`, `Set/GetVfoB` |
|
||||
| `FR` | приёмный VFO (0/1) | `Set/GetActiveVfo` |
|
||||
| `FT` | передающий VFO / split | → `ZZSP` |
|
||||
| `FW` / `SH` / `SL` | ширина фильтра (индекс пресета) | `Set/GetFilterIdx` |
|
||||
| `MD` | режим (Kenwood-код) | `Set/GetMode` |
|
||||
| `GT` | режим AGC (0=FAST…4=OFF) | `Set/GetAGCMode` |
|
||||
| `AG` | громкость AF | `Set/GetVolume` |
|
||||
| `PC` | мощность TX 0–100 % | `Set/GetDriveLevel` |
|
||||
| `NB` | шумоподавитель (NB) | `Set/GetNBMode` |
|
||||
| `NT` | автонотч (ANF) | `Set/GetANFEnabled` |
|
||||
| `SM` | S-метр (шкала Kenwood 0000–0030) | `GetSMeter` |
|
||||
| `IF` | составной статус (37 байт) | freq/mode/tx/split |
|
||||
| `ID` | идентификатор (TS-2000 = 019) | const |
|
||||
| `PS` | питание/состояние | `GetRunning` |
|
||||
| `TX` / `RX` | передача / приём | `SetTransmitting` |
|
||||
| `BU` / `BD` | смена диапазона ±1 | `DoBandUp/Down` |
|
||||
| `UP` / `DN` / `RU` / `RD` | шаг настройки ±1 | `DoTuneUp/Down` |
|
||||
| `SQ` | шумоподавитель FM (шкала 0–255) | `Set/GetSquelchLevel`+`On` |
|
||||
| `CN` / `CT` | CTCSS тон / вкл | `Set/GetCTCSSTone`, `…On` |
|
||||
| `OF` / `OS` | репитер: offset / направление | `Set/GetFMRptOffset`, `…Dir` |
|
||||
|
||||
### 2.2 Реализованные расширенные `ZZ*` (выборочно, наиболее значимые)
|
||||
|
||||
VFO/режим/фильтр: `ZZFA ZZFB ZZFI ZZFL ZZFH ZZFM ZZMD ZZFT ZZSP`,
|
||||
диапазоны: `ZZBS ZZBU ZZBD ZZBA ZZBI ZZBM`,
|
||||
AGC/громкость/мощность: `ZZGT ZZAG ZZPC ZZPD ZZAR`,
|
||||
шумодав/нотч: `ZZNA ZZNB ZZNR ZZNS ZZNT ZZNL ZZNN`,
|
||||
FM: `ZZSQ ZZSO ZZTA ZZTB ZZST`, CTUN: `ZZCN`,
|
||||
S-метр/телеметрия: `ZZSM`, шаги VFO: `ZZAU ZZBP ZZSU ZZSA ZZSB`,
|
||||
передача: `ZZTX ZZRX`, сервис: `ZZID ZZRV ZZGA ZZGR`.
|
||||
|
||||
### 2.3 Сделано в ветке `feature/cat-commands-expansion`
|
||||
|
||||
Все команды ниже **были заглушками** и стали рабочими — функционал в
|
||||
`TRadioController` уже существовал, но не был проброшен в CAT.
|
||||
|
||||
**Группа 1 — Split TX**
|
||||
- `ZZSP`, `FT` — split (TX на VFO-B), `FR` — выбор приёмного VFO,
|
||||
split-байт в составном `IF`.
|
||||
- В `TRadioController` добавлен публичный метод `SetSplit(TxOnB)`
|
||||
(флаг `FSplitTxB` + `PushNetworkState` + `Changed(rfActiveVfo)`).
|
||||
TX-частоту радио берёт из `ActiveTXFreqHz` (учитывает split).
|
||||
|
||||
**Группа 2 — FM Squelch**
|
||||
- `SQ` (шкала Kenwood 0–255), `ZZSQ` (0–100 %), `ZZSO` (вкл/выкл).
|
||||
- Маппинг: `SetFMSquelch` / `SetFMSquelchLevel`. Уровень>0 включает
|
||||
шумоподавитель.
|
||||
|
||||
**Группа 3 — FM CTCSS**
|
||||
- `CT`/`ZZTA` (вкл/выкл), `CN` (1-based тон 01–38), `ZZTB` (0-based 00–37).
|
||||
- Маппинг: `SetFMCTCSS` / `SetFMCTCSSTone`, таблица `CTCSS_TONES[38]`.
|
||||
|
||||
**Группа 4 — FM-репитер**
|
||||
- `OF` (offset, 9 цифр Гц), `OS` (направление).
|
||||
- Трансляция направлений Kenwood (0=simplex,1=+,2=−) ↔ внутренние
|
||||
`RPT_NONE/MINUS/PLUS`. Маппинг: `SetFMRpt` / `SetFMRptOffset`.
|
||||
|
||||
**Группа 5 — FM tuning step**
|
||||
- `ZZST` (индекс 00–03: 6.25/12.5/20/25 кГц). Маппинг: `SetFMStep`.
|
||||
|
||||
**Доп. команды (вторая итерация)**
|
||||
| Команда | Что | Примечание |
|
||||
|---|---|---|
|
||||
| `ZZSO` | Squelch on/off | завершает группу squelch |
|
||||
| `ZZNT` | ANF on/off | раньше ошибочно был алиасом NB |
|
||||
| `ZZCN` | CTUN (click-tune) RX1 | новый callback `Get/SetCTun` → `SetCTun` |
|
||||
| `ZZAR` | RX1 AGC-T / RF gain (`±nnn`) | новый callback `Get/SetAGCTop` → `SetAGCTop` (кламп 20–120 dB) |
|
||||
| `ZZAU`/`ZZBP` | сдвиг VFO A/B вверх на шаг `nn` (00–14) | **исправлен баг**: были заалиашены на `ZZFA`/`ZZAP` |
|
||||
| `ZZSU` | шаг активного VFO вверх | `DoTuneUp` |
|
||||
|
||||
Шкала шагов `ZZAU/ZZBP` — `StepIdxToHz` (зеркало Thetis `Step2Freq`):
|
||||
1 Гц, 10, 25, 50, 100, 250, 500, 1к, 5к, 9к, 10к, 100к, 250к, 500к, 1 МГц.
|
||||
|
||||
**Новые callback'и в `TCATContext`** (этой ветки): split, squelch on/level,
|
||||
CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
|
||||
|
||||
---
|
||||
|
||||
## 3. Что нужно сделать (актуально / есть чем закрыть)
|
||||
|
||||
Эти команды — заглушки, но соответствующая функция в EWSDR **уже есть** или
|
||||
легко добавляется. Приоритетные кандидаты:
|
||||
|
||||
| Команда(ы) | Что | Чем закрыть | Сложность |
|
||||
|---|---|---|---|
|
||||
| `ZZCU` | загрузка CPU (read) | телеметрия процесса | низкая |
|
||||
| `ZZMY` / `ZZQR` / `ZZQS` / `ZZQM` | память каналов (запись/чтение/quick) | `ChannelStore` уже есть | средняя |
|
||||
| `ZZFD` | FM-девиация (radio button) | нужен публичный `SetFMDeviation` (сейчас только поле `FFMDeviation`) | низкая |
|
||||
| `ZZDM` / `ZZDA` / `ZZPO` / `ZZDY` | режим/усреднение/пик/порог спектра | UI-параметры спектра | средняя |
|
||||
| `ZZAS` | RX2 AGC-T | — только при появлении RX2 | — |
|
||||
|
||||
> Прежде чем реализовывать новую команду, сверяйся с форматом поля в
|
||||
> `Thetis/.../CAT/CATStructs.xml` (`<nsetparms>` / `<ngetparms>`) и телом
|
||||
> метода в `CATCommands.cs`, чтобы ширина/знак значения совпадали.
|
||||
|
||||
### 3.1 Совсем не обрабатываются (вернут `?;`)
|
||||
|
||||
`ZZCU` (CPU), `ZZDY` (DX threshold), `ZZPK`/`ZZPL` (речевой компрессор),
|
||||
`ZZQA`/`ZZQB` (Quick Play/Rec), `ZZVF` (VAC stereo).
|
||||
Из них реалистично добавить только `ZZCU` и (частично) `ZZDY`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Осознанные заглушки (закрывать нечем)
|
||||
|
||||
Команды отвечают валидным dummy-значением для совместимости с управляющим ПО
|
||||
(rigctld, N1MM, WSJT-X, Log4OM), но функции в EWSDR нет. Это ~70 % всех заглушек.
|
||||
|
||||
| Область | Команды (примеры) | Причина |
|
||||
|---|---|---|
|
||||
| **Второй приёмник RX2 / sub-RX** | `ZZAB ZZAS ZZBA ZZMB ZZOB`, ext `BF BG BR DO DQ DR OF OG OH OL SG SH SS` | один приёмник |
|
||||
| **TX-DSP тракт** | mic gain `ZZMG`/`MG`, монитор `ZZMO`/`MO`, TX-EQ `ZZEB/ER/ET`, компрессор `ZZPK/ZZPL/CF CI CL CM`, two-tone `ZZUS`, VAC `ZZVB/ZZVF` | нет обработки передачи |
|
||||
| **CW-кейер / CWX** | `ZZKM ZZKS ZZKY ZZCB ZZCD ZZCS`, `KY KS` | нет кейера |
|
||||
| **RTTY / DIGI** | `ZZRA ZZRL`, ext `RF RH RM RS RY` | нет RTTY-движка |
|
||||
| **RIT / XIT** | `RT XT RC RU RD` (как RIT), ext `RB RF RH` | нет смещения приёма |
|
||||
| **Diversity** | `ZZDA(div) ZZDB`, ext `DA` | нет диверсити-приёма |
|
||||
| **Спектр/водопад (внутр.)** | ext `SV SW SX SY SZ WT WU WV WW` | не управляется по CAT |
|
||||
|
||||
---
|
||||
|
||||
## 5. Транспорты
|
||||
|
||||
- **Serial** (`CATSerial.pas`): до 4 портов, параметры (порт/скорость/биты/
|
||||
чётность/стоп) из `TGlobalSettings.CATSerial*`, применяются
|
||||
`TCATAdapter.ApplySettings`. На текущей платформе сборки может быть
|
||||
`{$DEFINE CAT_SERIAL_STUB}` (см. начало файла) — проверить перед релизом.
|
||||
- **TCP** (`CATTcp.pas`): сервер на `CATTcpPort`, включается флагом
|
||||
`CATTcpEnabled`. Клиенты идентифицируются GUID через `ZZGA`/`ZZGR`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Как добавить новую CAT-команду (чек-лист)
|
||||
|
||||
1. **Сверить формат** в `CATStructs.xml` + `CATCommands.cs` (Thetis).
|
||||
2. Если нужна новая возможность контроллера — есть ли публичный метод/поле
|
||||
в `TRadioController`? Если нет — добавить (по образцу `SetSplit`).
|
||||
3. В `CATEngine.pas`: добавить callback(и) в `TCATContext`, `SafeGet*`-хелпер
|
||||
для GET, переписать тело команды (`CmdXX` или `ZZXX`).
|
||||
4. В `CATAdapter.pas`: геттер (чтение `FController.F*`), сеттер + `SyncXX`
|
||||
(через `Invoke`), регистрация в `TCATAdapter.Create`.
|
||||
5. Если команда `ZZ*` ещё не в диспетчере — добавить ветку `else if ext = '..'`.
|
||||
6. Сборка-проверка: `lazbuild -B --ws=qt6 ewsdr.lpr` (GUI-граф включает CAT;
|
||||
headless-демон `ewsdrd` CAT **не** содержит).
|
||||
Reference in New Issue
Block a user