Files
ewsdr/CATEngine.pas
T
ew8bakandClaude Sonnet 4.6 efc21b92e6 Add configurable VFO frequency range with GHz support
- Settings > Display: new "VFO Display" control to select max frequency
  range (999 MHz / 9.999 GHz / 99.999 GHz), persisted as freq_mhz_digits
- FreqDisplay: extend digit map [0..10] to support up to 11 digit positions;
  keyboard navigation adapts to MinMhzDigits dynamically
- MainForm/LayoutTopVfoBlock: VFO panel width scales automatically with
  selected digit count using dynamic text measurement
- Web VFO: fix broken HTML (class attr not closed before data-s — wheel and
  hover were completely non-functional); rewrite fmtVfo to use variable MHz
  digit count synced from server state; fix missing </span> for .vfo-hz
- WebServer: publish freq_mhz_digits in state JSON; updated from
  ApplyFreqMhzDigits so desktop and web stay in sync
- Fix Int32 overflow on frequencies above ~2.15 GHz: CATEngine.Pad V param
  Int64, FormatFreq/FormatFreqSV Mhz var Int64 (Mhz*1000000 overflowed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 13:13:52 +03:00

2063 lines
70 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
unit CATEngine;
{
CATEngine.pas — CAT command engine (Kenwood + Thetis ZZ* extensions).
Совместимость с Thetis/PowerSDR: все команды из CATParser.cs + CATCommands.cs
реализованы. Команды, для которых в ewsdr ещё нет функционала, возвращают
заглушку и сопровождены комментарием "STUB: <причина>".
Формат команд:
SET: <PREFIX><SUFFIX>;<CR> (suffix = значение)
GET: <PREFIX>;<CR> (suffix пустой)
Resp: <PREFIX><VALUE>;
Err: ?; (неверный формат) | E; (ошибка выполнения)
Пример:
"FA00014250000;" — set VFO A = 14.250 MHz
"FA;" — get VFO A
"ZZFA00014250000;" — то же расширенной командой
}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
Classes, SysUtils, Math, SyncObjs;
const
CAT_ERROR = '?;';
CAT_EXEC_ERROR = 'E;';
CAT_ID_NUM = '019'; // TS-2000 compatible
// Mode mapping ewsdr → CAT
MODE_LSB = 0; MODE_USB = 1; MODE_DSB = 2;
MODE_CWL = 3; MODE_CWU = 4; MODE_FM = 5;
MODE_AM = 6; MODE_SAM = 7;
type
TCATGetDoubleFunc = function: Double of object;
TCATGetIntFunc = function: Integer of object;
TCATGetBoolFunc = function: Boolean of object;
TCATSetDoubleProc = procedure(V: Double) of object;
TCATSetIntProc = procedure(V: Integer) of object;
TCATSetBoolProc = procedure(V: Boolean) of object;
TCATProc = procedure of object;
TCATContext = record
GetVfoA: TCATGetDoubleFunc;
GetVfoB: TCATGetDoubleFunc;
GetMode: TCATGetIntFunc;
GetActiveVfo: TCATGetIntFunc;
GetAGCMode: TCATGetIntFunc;
GetVolume: TCATGetIntFunc;
GetDriveLevel: TCATGetIntFunc;
GetFilterIdx: TCATGetIntFunc;
GetFilterBW: TCATGetIntFunc;
GetNRMode: TCATGetIntFunc;
GetNBMode: TCATGetIntFunc;
GetSNBEnabled: TCATGetBoolFunc;
GetANFEnabled: TCATGetBoolFunc;
GetTransmitting: TCATGetBoolFunc;
GetRunning: TCATGetBoolFunc;
GetSMeter: TCATGetDoubleFunc;
GetCurrentBand: TCATGetIntFunc;
SetVfoA: TCATSetDoubleProc;
SetVfoB: TCATSetDoubleProc;
SetMode: TCATSetIntProc;
SetActiveVfo: TCATSetIntProc;
SetAGCMode: TCATSetIntProc;
SetVolume: TCATSetIntProc;
SetDriveLevel: TCATSetIntProc;
SetFilterIdx: TCATSetIntProc;
SetNRMode: TCATSetIntProc;
SetNBMode: TCATSetIntProc;
SetSNBEnabled: TCATSetBoolProc;
SetANFEnabled: TCATSetBoolProc;
SetTransmitting: TCATSetBoolProc;
DoBandUp: TCATProc;
DoBandDown: TCATProc;
DoTuneUp: TCATProc;
DoTuneDown: TCATProc;
DoBandByIndex: TCATSetIntProc;
end;
TCATEngine = class
private
FCtx: TCATContext;
FLock: TCriticalSection;
function Pad(V: Int64; W: Integer): string;
function FreqToStr(Hz: Double): string;
function StrToFreq(const S: string): Double;
function ModeToK(M: Integer): Integer;
function KToMode(K: Integer): Integer;
function SMeterRaw(dBm: Double): Integer;
// helpers to safely call context callbacks
function SafeGetVfoA: Double;
function SafeGetVfoB: Double;
function SafeGetMode: Integer;
function SafeGetVolume: Integer;
function SafeGetDriveLevel: Integer;
function SafeGetFilterIdx: Integer;
function SafeGetAGCMode: Integer;
function SafeGetNRMode: Integer;
function SafeGetNBMode: Integer;
function SafeGetSNB: Boolean;
function SafeGetANF: Boolean;
function SafeGetTX: Boolean;
function SafeGetRunning: Boolean;
function SafeGetSMeter: Double;
function SafeGetBand: Integer;
// command implementations
function CmdAG(const s: string): string;
function CmdAI(const s: string): string;
function CmdBD: string;
function CmdBU: string;
function CmdCN(const s: string): string;
function CmdCT(const s: string): string;
function CmdDN: string;
function CmdFA(const s: string): string;
function CmdFB(const s: string): string;
function CmdFR(const s: string): string;
function CmdFT(const s: string): string;
function CmdFW(const s: string): string;
function CmdGT(const s: string): string;
function CmdID: string;
function CmdIF: string;
function CmdKS(const s: string): string;
function CmdKY(const s: string): string;
function CmdMD(const s: string): string;
function CmdMG(const s: string): string;
function CmdMO(const s: string): string;
function CmdNB(const s: string): string;
function CmdNT(const s: string): string;
function CmdOF(const s: string): string;
function CmdOS(const s: string): string;
function CmdPC(const s: string): string;
function CmdPR(const s: string): string;
function CmdPS(const s: string): string;
function CmdQI: string;
function CmdRC: string;
function CmdRD(const s: string): string;
function CmdRT(const s: string): string;
function CmdRU(const s: string): string;
function CmdRX(const s: string): string;
function CmdSH(const s: string): string;
function CmdSL(const s: string): string;
function CmdSM(const s: string): string;
function CmdSQ(const s: string): string;
function CmdTX(const s: string): string;
function CmdUP: string;
function CmdXT(const s: string): string;
// ZZ extended
function ParseZZ(const ext, s: string): string;
function ZZAA(const s: string): string;
function ZZAB(const s: string): string;
function ZZAC(const s: string): string;
function ZZAG(const s: string): string;
function ZZAI(const s: string): string;
function ZZAP(const s: string): string;
function ZZAR(const s: string): string;
function ZZAS(const s: string): string;
function ZZAT(const s: string): string;
function ZZBA: string;
function ZZBD: string;
function ZZBE(const s: string): string;
function ZZBI(const s: string): string;
function ZZBM(const s: string): string;
function ZZBS(const s: string): string;
function ZZBU: string;
function ZZCB(const s: string): string;
function ZZCD(const s: string): string;
function ZZCN(const s: string): string;
function ZZCS(const s: string): string;
function ZZDA(const s: string): string;
function ZZDB(const s: string): string;
function ZZDM(const s: string): string;
function ZZDN(const s: string): string;
function ZZDU: string;
function ZZEA(const s: string): string;
function ZZEB(const s: string): string;
function ZZEM(const s: string): string;
function ZZFA(const s: string): string;
function ZZFB(const s: string): string;
function ZZFD(const s: string): string;
function ZZFI(const s: string): string;
function ZZFL(const s: string): string;
function ZZFH(const s: string): string;
function ZZFM: string;
function ZZFT(const s: string): string;
function ZZGA(const s: string): string;
function ZZGR(const s: string): string;
function ZZGT(const s: string): string;
function ZZID: string;
function ZZIF(const s: string): string;
function ZZIO: string;
function ZZIS(const s: string): string;
function ZZKM(const s: string): string;
function ZZKS(const s: string): string;
function ZZKY(const s: string): string;
function ZZMA(const s: string): string;
function ZZMB(const s: string): string;
function ZZMD(const s: string): string;
function ZZMG(const s: string): string;
function ZZML: string;
function ZZMN(const s: string): string;
function ZZMO(const s: string): string;
function ZZMV: string;
function ZZMY: string;
function ZZNA(const s: string): string;
function ZZNB(const s: string): string;
function ZZNL(const s: string): string;
function ZZNN(const s: string): string;
function ZZNR(const s: string): string;
function ZZNS(const s: string): string;
function ZZNT(const s: string): string;
function ZZOA(const s: string): string;
function ZZOB(const s: string): string;
function ZZPC(const s: string): string;
function ZZPD: string;
function ZZPO(const s: string): string;
function ZZPS(const s: string): string;
function ZZQM: string;
function ZZQR: string;
function ZZQS: string;
function ZZRA(const s: string): string;
function ZZRC: string;
function ZZRD(const s: string): string;
function ZZRL(const s: string): string;
function ZZRU(const s: string): string;
function ZZRV: string;
function ZZRX(const s: string): string;
function ZZSA: string;
function ZZSB: string;
function ZZSM(const s: string): string;
function ZZSO(const s: string): string;
function ZZSP(const s: string): string;
function ZZSQ(const s: string): string;
function ZZSR(const s: string): string;
function ZZST: string;
function ZZSU: string;
function ZZTA(const s: string): string;
function ZZTB(const s: string): string;
function ZZTX(const s: string): string;
function ZZUP(const s: string): string;
function ZZUS: string;
function ZZUT(const s: string): string;
function ZZUX(const s: string): string;
function ZZUY(const s: string): string;
function ZZVA(const s: string): string;
function ZZVB(const s: string): string;
function ZZVG(const s: string): string;
function ZZVN: string;
function ZZVS(const s: string): string;
function ZZWA(const s: string): string;
function ZZWB(const s: string): string;
function ZZWC(const s: string): string;
function ZZWD(const s: string): string;
function ZZWE(const s: string): string;
function ZZWF(const s: string): string;
function ZZWG(const s: string): string;
function ZZWH(const s: string): string;
function ZZWJ(const s: string): string;
function ZZWK(const s: string): string;
function ZZWL(const s: string): string;
function ZZWM(const s: string): string;
function ZZWN(const s: string): string;
function ZZWO(const s: string): string;
function ZZWP(const s: string): string;
function ZZWQ(const s: string): string;
function ZZWR(const s: string): string;
function ZZWS(const s: string): string;
function ZZXC: string;
function ZZXF(const s: string): string;
function ZZXA(const s: string): string;
function ZZYA(const s: string): string;
function ZZYB(const s: string): string;
function ZZYC(const s: string): string;
function ZZYR(const s: string): string;
function ZZZA(const s: string): string;
function ZZZB: string;
function ZZZM(const s: string): string;
function ZZZP(const s: string): string;
function ZZZQ(const s: string): string;
function ZZZR(const s: string): string;
function ZZZS(const s: string): string;
function ZZZT(const s: string): string;
function ZZZU(const s: string): string;
function ZZZV(const s: string): string;
function ZZZW(const s: string): string;
function ZZZZ: string;
function ZZZO(const s: string): string;
public
constructor Create(const Ctx: TCATContext);
destructor Destroy; override;
function Parse(const Cmd: string): string;
end;
implementation
{ ── Local helpers ────────────────────────────────────────────────────────── }
// Convert Boolean to CAT '1'/'0' string — avoids IfThen char-overload ambiguity
function B2C(B: Boolean): string; inline;
begin if B then Result := '1' else Result := '0'; end;
{ ── Helpers ──────────────────────────────────────────────────────────────── }
function TCATEngine.Pad(V: Int64; W: Integer): string;
begin
Result := IntToStr(Abs(V));
while Length(Result) < W do Result := '0' + Result;
if Length(Result) > W then Result := Copy(Result, Length(Result) - W + 1, W);
end;
function TCATEngine.FreqToStr(Hz: Double): string;
begin
Result := Pad(Round(Hz), 11);
end;
function TCATEngine.StrToFreq(const S: string): Double;
var V: Int64;
begin
if TryStrToInt64(S, V) then
Result := V
else
Result := 0;
end;
function TCATEngine.ModeToK(M: Integer): Integer;
begin
case M of
MODE_LSB: Result := 1;
MODE_USB: Result := 2;
MODE_DSB: Result := 2; // treated as USB for CAT compatibility
MODE_CWU: Result := 3;
MODE_FM: Result := 4;
MODE_AM: Result := 5;
MODE_SAM: Result := 5; // treated as AM
MODE_CWL: Result := 7;
else Result := 2;
end;
end;
function TCATEngine.KToMode(K: Integer): Integer;
begin
case K of
1: Result := MODE_LSB;
2: Result := MODE_USB;
3: Result := MODE_CWU;
4: Result := MODE_FM;
5: Result := MODE_AM;
6: Result := MODE_USB; // FSK/RTTY → USB
7: Result := MODE_CWL;
9: Result := MODE_LSB; // FSK-R → LSB
else Result := MODE_USB;
end;
end;
function TCATEngine.SMeterRaw(dBm: Double): Integer;
var v: Double;
begin
v := (dBm + 140.0) * 2.0;
if v < 0 then v := 0;
if v > 260 then v := 260;
Result := Round(v);
end;
{ ── Safe getters (handle nil callbacks gracefully) ───────────────────────── }
function TCATEngine.SafeGetVfoA: Double;
begin
if Assigned(FCtx.GetVfoA) then Result := FCtx.GetVfoA else Result := 14200000;
end;
function TCATEngine.SafeGetVfoB: Double;
begin
if Assigned(FCtx.GetVfoB) then Result := FCtx.GetVfoB else Result := 14200000;
end;
function TCATEngine.SafeGetMode: Integer;
begin
if Assigned(FCtx.GetMode) then Result := FCtx.GetMode else Result := MODE_USB;
end;
function TCATEngine.SafeGetVolume: Integer;
begin
if Assigned(FCtx.GetVolume) then Result := FCtx.GetVolume else Result := 50;
end;
function TCATEngine.SafeGetDriveLevel: Integer;
begin
if Assigned(FCtx.GetDriveLevel) then Result := FCtx.GetDriveLevel else Result := 50;
end;
function TCATEngine.SafeGetFilterIdx: Integer;
begin
if Assigned(FCtx.GetFilterIdx) then Result := FCtx.GetFilterIdx else Result := 0;
end;
function TCATEngine.SafeGetAGCMode: Integer;
begin
if Assigned(FCtx.GetAGCMode) then Result := FCtx.GetAGCMode else Result := 0;
end;
function TCATEngine.SafeGetNRMode: Integer;
begin
if Assigned(FCtx.GetNRMode) then Result := FCtx.GetNRMode else Result := 0;
end;
function TCATEngine.SafeGetNBMode: Integer;
begin
if Assigned(FCtx.GetNBMode) then Result := FCtx.GetNBMode else Result := 0;
end;
function TCATEngine.SafeGetSNB: Boolean;
begin
if Assigned(FCtx.GetSNBEnabled) then Result := FCtx.GetSNBEnabled else Result := False;
end;
function TCATEngine.SafeGetANF: Boolean;
begin
if Assigned(FCtx.GetANFEnabled) then Result := FCtx.GetANFEnabled else Result := False;
end;
function TCATEngine.SafeGetTX: Boolean;
begin
if Assigned(FCtx.GetTransmitting) then Result := FCtx.GetTransmitting else Result := False;
end;
function TCATEngine.SafeGetRunning: Boolean;
begin
if Assigned(FCtx.GetRunning) then Result := FCtx.GetRunning else Result := False;
end;
function TCATEngine.SafeGetSMeter: Double;
begin
if Assigned(FCtx.GetSMeter) then Result := FCtx.GetSMeter else Result := -120;
end;
function TCATEngine.SafeGetBand: Integer;
begin
if Assigned(FCtx.GetCurrentBand) then Result := FCtx.GetCurrentBand else Result := 5;
end;
{ ── Constructor / Destructor ─────────────────────────────────────────────── }
constructor TCATEngine.Create(const Ctx: TCATContext);
begin
inherited Create;
FCtx := Ctx;
FLock := TCriticalSection.Create;
end;
destructor TCATEngine.Destroy;
begin
FLock.Free;
inherited;
end;
{ ══════════════════════════════════════════════════════════════════════════
Parse — main entry point. Thread-safe, can be called from serial/TCP threads.
══════════════════════════════════════════════════════════════════════════ }
function TCATEngine.Parse(const Cmd: string): string;
var
s, pfx, ext, sfx: string;
tpos: Integer;
begin
Result := '';
s := Trim(Cmd);
// strip leading semicolons (some loggers prepend them)
while (Length(s) > 0) and (s[1] = ';') do Delete(s, 1, 1);
if Length(s) < 3 then Exit(CAT_ERROR);
tpos := Pos(';', s);
if tpos < 3 then Exit(CAT_ERROR);
pfx := UpperCase(Copy(s, 1, 2));
FLock.Acquire;
try
if pfx = 'ZZ' then
begin
if tpos < 5 then Exit(CAT_ERROR);
ext := UpperCase(Copy(s, 3, 2));
sfx := Copy(s, 5, tpos - 5);
Result := ParseZZ(ext, sfx);
end else begin
sfx := Copy(s, 3, tpos - 3);
if pfx = 'AG' then Result := CmdAG(sfx)
else if pfx = 'AI' then Result := CmdAI(sfx)
else if pfx = 'BD' then begin CmdBD; Result := ''; end
else if pfx = 'BU' then begin CmdBU; Result := ''; end
else if pfx = 'CN' then Result := CmdCN(sfx)
else if pfx = 'CT' then Result := CmdCT(sfx)
else if pfx = 'DN' then begin CmdDN; Result := ''; end
else if pfx = 'FA' then Result := CmdFA(sfx)
else if pfx = 'FB' then Result := CmdFB(sfx)
else if pfx = 'FR' then Result := CmdFR(sfx)
else if pfx = 'FT' then Result := CmdFT(sfx)
else if pfx = 'FW' then Result := CmdFW(sfx)
else if pfx = 'GT' then Result := CmdGT(sfx)
else if pfx = 'ID' then Result := 'ID' + CmdID + ';'
else if pfx = 'IF' then Result := 'IF' + CmdIF + ';'
else if pfx = 'KS' then Result := CmdKS(sfx)
else if pfx = 'KY' then Result := CmdKY(sfx)
else if pfx = 'MD' then Result := CmdMD(sfx)
else if pfx = 'MG' then Result := CmdMG(sfx)
else if pfx = 'MO' then Result := CmdMO(sfx)
else if pfx = 'NB' then Result := CmdNB(sfx)
else if pfx = 'NT' then Result := CmdNT(sfx)
else if pfx = 'OF' then Result := CmdOF(sfx)
else if pfx = 'OS' then Result := CmdOS(sfx)
else if pfx = 'PC' then Result := CmdPC(sfx)
else if pfx = 'PR' then Result := CmdPR(sfx)
else if pfx = 'PS' then Result := CmdPS(sfx)
else if pfx = 'QI' then begin CmdQI; Result := ''; end
else if pfx = 'RC' then begin CmdRC; Result := ''; end
else if pfx = 'RD' then begin CmdRD(sfx); Result := ''; end
else if pfx = 'RT' then Result := CmdRT(sfx)
else if pfx = 'RU' then begin CmdRU(sfx); Result := ''; end
else if pfx = 'RX' then begin CmdRX(sfx); Result := ''; end
else if pfx = 'SH' then Result := CmdSH(sfx)
else if pfx = 'SL' then Result := CmdSL(sfx)
else if pfx = 'SM' then Result := CmdSM(sfx)
else if pfx = 'SQ' then Result := CmdSQ(sfx)
else if pfx = 'TX' then begin CmdTX(sfx); Result := ''; end
else if pfx = 'UP' then begin CmdUP; Result := ''; end
else if pfx = 'XT' then Result := CmdXT(sfx)
else Result := CAT_ERROR;
// Wrap standard command response
if (Result <> '') and (Result <> CAT_ERROR) and (Result <> CAT_EXEC_ERROR) then
if Copy(Result, 1, 2) <> pfx then
Result := pfx + Result + ';';
end;
finally
FLock.Release;
end;
end;
{ ══════════════════════════════════════════════════════════════════════════
Standard commands (non-ZZ)
══════════════════════════════════════════════════════════════════════════ }
function TCATEngine.CmdAG(const s: string): string;
// AG: SET=4 chars (0xxx), GET=1 char ('0'=RX1, '1'=RX2)
// Scale 0255 (Kenwood) ↔ 0100 (ewsdr)
var v: Integer;
begin
if Length(s) = 4 then begin
v := Round(StrToIntDef(Copy(s,2,3), 0) / 2.55);
v := EnsureRange(v, 0, 100);
if Assigned(FCtx.SetVolume) then FCtx.SetVolume(v);
Result := '';
end else if Length(s) = 1 then begin
v := Round(SafeGetVolume / 0.392);
Result := '0' + Pad(v, 3);
end else
Result := CAT_ERROR;
end;
function TCATEngine.CmdAI(const s: string): string;
// AI: auto-information — maps to ZZAI
begin
Result := ZZAI(s);
end;
function TCATEngine.CmdBD: string;
begin
if Assigned(FCtx.DoBandDown) then FCtx.DoBandDown;
Result := '';
end;
function TCATEngine.CmdBU: string;
begin
if Assigned(FCtx.DoBandUp) then FCtx.DoBandUp;
Result := '';
end;
function TCATEngine.CmdCN(const s: string): string;
// CN: CTCSS tone — STUB: FM sub-audible tones not implemented
begin
if Length(s) = 2 then
Result := ''
else if Length(s) = 0 then
Result := '00'
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdCT(const s: string): string;
// CT: CTCSS enable — STUB: not implemented
begin
if Length(s) = 1 then
Result := ''
else if Length(s) = 0 then
Result := '0'
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdDN: string;
begin
if Assigned(FCtx.DoTuneDown) then FCtx.DoTuneDown;
Result := '';
end;
function TCATEngine.CmdFA(const s: string): string;
// FA: VFO A frequency (11 digits, Hz)
begin
if Length(s) = 11 then begin
if Assigned(FCtx.SetVfoA) then FCtx.SetVfoA(StrToFreq(s));
Result := '';
end else if Length(s) = 0 then
Result := FreqToStr(SafeGetVfoA)
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdFB(const s: string): string;
// FB: VFO B frequency (11 digits, Hz)
begin
if Length(s) = 11 then begin
if Assigned(FCtx.SetVfoB) then FCtx.SetVfoB(StrToFreq(s));
Result := '';
end else if Length(s) = 0 then
Result := FreqToStr(SafeGetVfoB)
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdFR(const s: string): string;
// FR: receive VFO — always VFO A, dummy for compat
begin
if Length(s) = 1 then
Result := ''
else if Length(s) = 0 then
Result := '0'
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdFT(const s: string): string;
// FT: transmit VFO / split — maps to ZZSP
begin
Result := ZZSP(s);
end;
function TCATEngine.CmdFW(const s: string): string;
// FW: filter width (4 digits) — maps to filter index
// Thetis encodes filter by index into 4-digit string 0000-0009
var idx: Integer;
begin
if Length(s) = 4 then begin
idx := EnsureRange(StrToIntDef(s, 0), 0, 9);
if Assigned(FCtx.SetFilterIdx) then FCtx.SetFilterIdx(idx);
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetFilterIdx, 4)
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdGT(const s: string): string;
// GT: AGC mode (3 digits) — 0=FAST,1=MED,2=SLOW,3=LONG,4=OFF
begin
if Length(s) = 3 then begin
if Assigned(FCtx.SetAGCMode) then FCtx.SetAGCMode(EnsureRange(StrToIntDef(s,0),0,4));
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetAGCMode, 3)
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdID: string;
begin
Result := CAT_ID_NUM;
end;
function TCATEngine.CmdIF: string;
// IF: interface status — 37-byte composite response
var
freq, step, incr, rit, xit, tx, mode, split: string;
m: Integer;
begin
freq := FreqToStr(SafeGetVfoA);
step := '0000'; // step size — STUB: no step control
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
Result := freq + step + incr + rit + xit + '000' + tx + mode + '0' + '0' + split + '0000';
end;
function TCATEngine.CmdKS(const s: string): string;
// KS: CW keying speed (3 digits, WPM) — STUB: no CW keyer
begin
if Length(s) = 3 then
Result := '' // accepted but ignored
else if Length(s) = 0 then
Result := '025' // default 25 WPM
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdKY(const s: string): string;
// KY: send CW text — STUB: no CW keyer implemented
begin
Result := '0'; // queue not full
end;
function TCATEngine.CmdMD(const s: string): string;
// MD: mode (1 char Kenwood) — 1=LSB,2=USB,3=CW,4=FM,5=AM,7=CW-R
var k: Integer;
begin
if Length(s) = 1 then begin
k := StrToIntDef(s, 0);
if (k >= 1) and Assigned(FCtx.SetMode) then
FCtx.SetMode(KToMode(k));
Result := '';
end else if Length(s) = 0 then
Result := IntToStr(ModeToK(SafeGetMode))
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdMG(const s: string): string;
// MG: mic gain (3 digits, 0-100) — STUB: TX DSP not yet implemented
begin
if Length(s) = 3 then
Result := ''
else if Length(s) = 0 then
Result := '050'
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdMO(const s: string): string;
// MO: monitor — STUB
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.CmdNB(const s: string): string;
// NB: noise blanker on/off → maps to NBMode
begin
if Length(s) = 1 then begin
if Assigned(FCtx.SetNBMode) then
FCtx.SetNBMode(StrToIntDef(s, 0));
Result := '';
end else if Length(s) = 0 then
Result := IntToStr(EnsureRange(SafeGetNBMode, 0, 1))
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdNT(const s: string): string;
// NT: notch (ANF) on/off
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.CmdOF(const s: string): string;
// OF: FM repeater offset frequency — STUB: no FM repeater
begin
if Length(s) = 9 then Result := ''
else if Length(s) = 0 then Result := '000000000'
else Result := CAT_ERROR;
end;
function TCATEngine.CmdOS(const s: string): string;
// OS: repeater offset direction — STUB
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.CmdPC(const s: string): string;
// PC: TX power 0-100 % (3 digits) → DriveLevel
var v: Integer;
begin
if Length(s) = 3 then begin
v := EnsureRange(StrToIntDef(s,0), 0, 100);
if Assigned(FCtx.SetDriveLevel) then FCtx.SetDriveLevel(v);
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetDriveLevel, 3)
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdPR(const s: string): string;
// PR: speech compressor — STUB: always off
begin
if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.CmdPS(const s: string): string;
// PS: power status
begin
if Length(s) = 1 then Result := '' // accept but ignore
else if Length(s) = 0 then
Result := B2C(SafeGetRunning)
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdQI: string;
// QI: store VFO A to quick memory — STUB
begin
Result := '';
end;
function TCATEngine.CmdRC: string;
// RC: clear RIT — STUB: no RIT
begin
Result := '';
end;
function TCATEngine.CmdRD(const s: string): string;
// RD: decrease RIT — maps to tune down
begin
if Assigned(FCtx.DoTuneDown) then FCtx.DoTuneDown;
Result := '';
end;
function TCATEngine.CmdRT(const s: string): string;
// RT: RIT on/off — STUB: no RIT
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.CmdRU(const s: string): string;
// RU: increase RIT — maps to tune up
begin
if Assigned(FCtx.DoTuneUp) then FCtx.DoTuneUp;
Result := '';
end;
function TCATEngine.CmdRX(const s: string): string;
// RX: switch to receive
begin
if Assigned(FCtx.SetTransmitting) then FCtx.SetTransmitting(False);
Result := '';
end;
function TCATEngine.CmdSH(const s: string): string;
// SH: high pass filter setting (2 digits, 0-based index) — maps to filter index
var idx: Integer;
begin
if Length(s) = 2 then begin
idx := EnsureRange(StrToIntDef(s, 0), 0, 9);
if Assigned(FCtx.SetFilterIdx) then FCtx.SetFilterIdx(idx);
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetFilterIdx, 2)
else
Result := CAT_ERROR;
end;
function TCATEngine.CmdSL(const s: string): string;
// SL: low pass filter setting — mirrors SH
begin
Result := CmdSH(s);
end;
function TCATEngine.CmdSM(const s: string): string;
// SM: S-meter read (1 char VFO selector, response 4 chars 0000-0015)
// Kenwood SM answer: 00000015 → S0S9+60dB
// We return 4-digit raw 0000-0030 compatible format
var sm: Integer;
begin
if (Length(s) = 1) and ((s = '0') or (s = '1')) then begin
sm := SMeterRaw(SafeGetSMeter);
// Scale 0-260 → 0-30 (Kenwood SM scale 0000-0030)
sm := Round(sm * 30 / 260);
Result := Pad(sm, 4);
end else
Result := CAT_ERROR;
end;
function TCATEngine.CmdSQ(const s: string): string;
// SQ: squelch — STUB: no squelch control
begin
if Length(s) = 4 then Result := ''
else if Length(s) = 1 then Result := '0000'
else Result := CAT_ERROR;
end;
function TCATEngine.CmdTX(const s: string): string;
// TX: switch to transmit
begin
if Assigned(FCtx.SetTransmitting) then FCtx.SetTransmitting(True);
Result := '';
end;
function TCATEngine.CmdUP: string;
begin
if Assigned(FCtx.DoTuneUp) then FCtx.DoTuneUp;
Result := '';
end;
function TCATEngine.CmdXT(const s: string): string;
// XT: XIT on/off — STUB: no XIT
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
{ ══════════════════════════════════════════════════════════════════════════
ZZ* extended commands dispatcher
══════════════════════════════════════════════════════════════════════════ }
function TCATEngine.ParseZZ(const ext, s: string): string;
var ans: string;
begin
ans := '';
if ext = 'AA' then ans := ZZAA(s)
else if ext = 'AB' then ans := ZZAB(s)
else if ext = 'AC' then ans := ZZAC(s)
else if ext = 'AD' then ans := '' // STUB: TX freq display
else if ext = 'AE' then ans := '' // STUB: RIT value
else if ext = 'AF' then ans := '' // STUB: XIT value
else if ext = 'AG' then ans := ZZAG(s)
else if ext = 'AI' then ans := ZZAI(s)
else if ext = 'AP' then ans := ZZAP(s)
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 = 'AY' then ans := '' // STUB: APF type
else if ext = 'BA' then ans := ZZBA
else if ext = 'BB' then ans := '' // STUB: band button state
else if ext = 'BD' then begin ZZBD; ans := ''; end
else if ext = 'BE' then ans := ZZBE(s)
else if ext = 'BF' then ans := '' // STUB: sub-RX filter
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 = 'BR' then ans := '' // STUB: sub-RX enable
else if ext = 'BS' then ans := ZZBS(s)
else if ext = 'BT' then ans := '' // STUB: band TX
else if ext = 'BU' then begin ZZBU; ans := ''; end
else if ext = 'BY' then ans := '' // STUB: VFO bypass
else if ext = 'CB' then ans := ZZCB(s)
else if ext = 'CD' then ans := ZZCD(s)
else if ext = 'CF' then ans := '' // STUB: compressor freq
else if ext = 'CI' then ans := '' // STUB: compressor in
else if ext = 'CL' then ans := '' // STUB: compressor level
else if ext = 'CM' then ans := '' // STUB: compressor mode
else if ext = 'CN' then ans := ZZCN(s)
else if ext = 'CO' then ans := '' // STUB: compressor out
else if ext = 'CP' then ans := '' // STUB: compressor peak
else if ext = 'CS' then ans := ZZCS(s)
else if ext = 'CT' then ans := '' // STUB: compressor threshold
else if ext = 'CU' then begin ZZDU; ans := ''; end
else if ext = 'DA' then ans := ZZDA(s)
else if ext = 'DB' then ans := ZZDB(s)
else if ext = 'DC' then ans := '' // STUB: display contrast
else if ext = 'DD' then ans := '' // STUB: DSP disable
else if ext = 'DE' then ans := '' // STUB: DSP enable
else if ext = 'DF' then ans := '' // STUB: TX filter low
else if ext = 'DG' then ans := '' // STUB: TX filter high
else if ext = 'DH' then ans := '' // STUB: TX DSP high cut
else if ext = 'DM' then ans := ZZDM(s)
else if ext = 'DN' then ans := ZZDN(s)
else if ext = 'DO' then ans := '' // STUB: sub-RX on
else if ext = 'DP' then ans := '' // STUB: display position
else if ext = 'DQ' then ans := '' // STUB: sub-RX
else if ext = 'DR' then ans := '' // STUB: sub-RX
else if ext = 'DU' then begin ZZDU; ans := ''; end
else if ext = 'DX' then ans := '' // STUB: sub NB
else if ext = 'EA' then ans := ZZEA(s)
else if ext = 'EB' then ans := ZZEB(s)
else if ext = 'EM' then ans := ZZEM(s)
else if ext = 'ER' then ans := '' // STUB: EQ reset
else if ext = 'ET' then ans := '' // STUB: EQ TX
else if ext = 'FA' then ans := ZZFA(s)
else if ext = 'FB' then ans := ZZFB(s)
else if ext = 'FD' then ans := ZZFD(s)
else if ext = 'FH' then ans := ZZFH(s)
else if ext = 'FI' then ans := ZZFI(s)
else if ext = 'FJ' then ans := '' // STUB: RX2 filter
else if ext = 'FL' then ans := ZZFL(s)
else if ext = 'FM' then ans := ZZFM
else if ext = 'FR' then ans := '' // STUB: filter RX
else if ext = 'FS' then ans := '' // STUB: sub filter
else if ext = 'FT' then ans := ZZFT(s)
else if ext = 'FV' then ans := '' // STUB: filter var
else if ext = 'FW' then ans := '' // STUB: filter write
else if ext = 'FX' then ans := '' // STUB: filter extended
else if ext = 'FY' then ans := '' // STUB: filter Y
else if ext = 'GA' then ans := ZZGA(s)
else if ext = 'GE' then ans := '' // STUB: gain enable
else if ext = 'GL' then ans := '' // STUB: gain level
else if ext = 'GR' then ans := ZZGR(s)
else if ext = 'GT' then ans := ZZGT(s)
else if ext = 'GU' then ans := '' // STUB: gain update
else if ext = 'HA' then ans := '' // STUB: hardware A
else if ext = 'HR' then ans := '' // STUB: hardware read
else if ext = 'HT' then ans := '' // STUB: hardware TX
else if ext = 'HU' then ans := '' // STUB: hardware update
else if ext = 'HV' then ans := '' // STUB: hardware value
else if ext = 'HW' then ans := '' // STUB: hardware write
else if ext = 'HX' then ans := '' // STUB: hardware extended
else if ext = 'ID' then ans := ZZID
else if ext = 'IF' then ans := ZZIF(s)
else if ext = 'IO' then ans := ZZIO
else if ext = 'IS' then ans := ZZIS(s)
else if ext = 'IT' then ans := '' // STUB: initial TX
else if ext = 'IU' then ans := '' // STUB: initial update
else if ext = 'JO' then ans := '' // STUB: JSON
else if ext = 'JP' then ans := '' // STUB: JSON preset
else if ext = 'JQ' then ans := '' // STUB: JSON query
else if ext = 'JR' then ans := '' // STUB: JSON read
else if ext = 'JS' then ans := '' // STUB: JSON status
else if ext = 'KM' then ans := ZZKM(s)
else if ext = 'KO' then ans := '' // STUB: key on
else if ext = 'KS' then ans := ZZKS(s)
else if ext = 'KY' then ans := ZZKY(s)
else if ext = 'LA' then ans := '' // STUB: level A
else if ext = 'LB' then ans := '' // STUB: level B
else if ext = 'LC' then ans := '' // STUB: level C
else if ext = 'LD' then ans := '' // STUB: level D
else if ext = 'LE' then ans := '' // STUB: level E
else if ext = 'LF' then ans := '' // STUB: level F
else if ext = 'LG' then ans := '' // STUB: level G
else if ext = 'LH' then ans := '' // STUB: level H
else if ext = 'LI' then ans := '' // STUB: level I
else if ext = 'MA' then ans := ZZMA(s)
else if ext = 'MB' then ans := ZZMB(s)
else if ext = 'MD' then ans := ZZMD(s)
else if ext = 'ME' then ans := '' // STUB: mode enable
else if ext = 'MF' then ans := '' // STUB: mode freq
else if ext = 'MG' then ans := ZZMG(s)
else if ext = 'ML' then ans := ZZML
else if ext = 'MN' then ans := ZZMN(s)
else if ext = 'MO' then ans := ZZMO(s)
else if ext = 'MR' then ans := '' // STUB: mode RX
else if ext = 'MS' then ans := '' // STUB: mode status
else if ext = 'MT' then ans := '' // STUB: mode TX
else if ext = 'MU' then ans := '' // STUB: mode update
else if ext = 'MV' then ans := ZZMV
else if ext = 'MW' then ans := '' // STUB: mode write
else if ext = 'MX' then ans := '' // STUB: mode extended
else if ext = 'MY' then ans := ZZMY
else if ext = 'MZ' then ans := '' // STUB: mode Z
else if ext = 'NA' then ans := ZZNA(s)
else if ext = 'NB' then ans := ZZNB(s)
else if ext = 'NC' then ans := '' // STUB: NR C
else if ext = 'ND' then ans := '' // STUB: NR D
else if ext = 'NE' then ans := '' // STUB: NR E
else if ext = 'NF' then ans := '' // STUB: NR F
else if ext = 'NG' then ans := '' // STUB: noise gate
else if ext = 'NH' then ans := '' // STUB: NR H
else if ext = 'NL' then ans := ZZNL(s)
else if ext = 'NM' then ans := '' // STUB: NR mode
else if ext = 'NN' then ans := ZZNN(s)
else if ext = 'NO' then ans := '' // STUB: sub NR
else if ext = 'NR' then ans := ZZNR(s)
else if ext = 'NS' then ans := ZZNS(s)
else if ext = 'NT' then ans := ZZNT(s)
else if ext = 'NU' then ans := '' // STUB: NR update
else if ext = 'NV' then ans := '' // STUB: NR value
else if ext = 'NW' then ans := '' // STUB: NR write
else if ext = 'OA' then ans := ZZOA(s)
else if ext = 'OB' then ans := ZZOB(s)
else if ext = 'OC' then ans := '' // STUB: output compression
else if ext = 'OD' then ans := '' // STUB: output DSP
else if ext = 'OE' then ans := '' // STUB: output E
else if ext = 'OF' then ans := '' // STUB: sub-RX freq
else if ext = 'OG' then ans := '' // STUB: output gain
else if ext = 'OH' then ans := '' // STUB: output high
else if ext = 'OJ' then ans := '' // STUB: output J
else if ext = 'OL' then ans := '' // STUB: output level
else if ext = 'OS' then ans := '' // STUB: output status
else if ext = 'OT' then ans := '' // STUB: FM repeater offset
else if ext = 'OU' then ans := '' // STUB: output update
else if ext = 'OV' then ans := '' // STUB: output value
else if ext = 'OW' then ans := '' // STUB: output write
else if ext = 'OX' then ans := '' // STUB: oxide extra
else if ext = 'OZ' then ans := '' // STUB: oxide Z
else if ext = 'PA' then ans := '' // STUB: sub-RX pan
else if ext = 'PB' then ans := '' // STUB: power B
else if ext = 'PC' then ans := ZZPC(s)
else if ext = 'PD' then ans := ZZPD
else if ext = 'PE' then ans := '' // STUB: power enable
else if ext = 'PO' then ans := ZZPO(s)
else if ext = 'PS' then ans := ZZPS(s)
else if ext = 'PY' then ans := '' // STUB: power Y
else if ext = 'PZ' then ans := '' // STUB: power Z
else if ext = 'QK' then ans := '' // STUB: query keep
else if ext = 'QM' then ans := ZZQM
else if ext = 'QR' then ans := ZZQR
else if ext = 'QS' then ans := ZZQS
else if ext = 'RA' then ans := ZZRA(s)
else if ext = 'RB' then ans := '' // STUB: RIT B
else if ext = 'RC' then begin ZZRC; ans := ''; end
else if ext = 'RD' then begin ZZRD(s); ans := ''; end
else if ext = 'RF' then ans := '' // STUB: RIT freq
else if ext = 'RH' then ans := '' // STUB: RIT high
else if ext = 'RL' then ans := ZZRL(s)
else if ext = 'RM' then ans := '' // STUB: RIT mode
else if ext = 'RS' then ans := '' // STUB: RIT status
else if ext = 'RT' then ans := '' // STUB: RIT transmit
else if ext = 'RU' then begin ZZRU(s); ans := ''; end
else if ext = 'RV' then ans := ZZRV
else if ext = 'RX' then ans := ZZRX(s)
else if ext = 'RY' then ans := '' // STUB: RIT Y
else if ext = 'SA' then begin ZZSA; ans := ''; end
else if ext = 'SB' then begin ZZSB; ans := ''; end
else if ext = 'SD' then ans := '' // STUB: sub-RX display
else if ext = 'SF' then ans := '' // STUB: sweep freq
else if ext = 'SG' then ans := '' // STUB: sub gain
else if ext = 'SH' then ans := '' // STUB: sub high cut
else if ext = 'SM' then ans := ZZSM(s)
else if ext = 'SN' then ans := '' // STUB: serial number
else if ext = 'SO' then ans := ZZSO(s)
else if ext = 'SP' then ans := ZZSP(s)
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 = 'SU' then begin ZZSU; ans := ''; end
else if ext = 'SV' then ans := '' // STUB: spectrum value
else if ext = 'SW' then ans := '' // STUB: spectrum write
else if ext = 'SX' then ans := '' // STUB: spectrum X
else if ext = 'SY' then ans := '' // STUB: spectrum Y
else if ext = 'SZ' then ans := '' // STUB: spectrum Z
else if ext = 'TA' then ans := ZZTA(s)
else if ext = 'TB' then ans := ZZTB(s)
else if ext = 'TF' then ans := '' // STUB: tone freq
else if ext = 'TH' then ans := '' // STUB: tone high
else if ext = 'TI' then ans := '' // STUB: tone input
else if ext = 'TL' then ans := '' // STUB: tone level
else if ext = 'TM' then ans := '' // STUB: tone mode
else if ext = 'TO' then ans := '' // STUB: tone on/off
else if ext = 'TP' then ans := '' // STUB: tone preset
else if ext = 'TS' then ans := '' // STUB: tone status
else if ext = 'TU' then ans := '' // STUB: tone update
else if ext = 'TV' then ans := '' // STUB: tone value
else if ext = 'TX' then ans := ZZTX(s)
else if ext = 'UA' then ans := '' // STUB: update A
else if ext = 'UP' then ans := ZZUP(s)
else if ext = 'US' then ans := ZZUS
else if ext = 'UT' then ans := ZZUT(s)
else if ext = 'UX' then ans := ZZUX(s)
else if ext = 'UY' then ans := ZZUY(s)
else if ext = 'VA' then ans := ZZVA(s)
else if ext = 'VB' then ans := ZZVB(s)
else if ext = 'VC' then ans := '' // STUB: voice compression
else if ext = 'VD' then ans := '' // STUB: VFO DSP
else if ext = 'VE' then ans := '' // STUB: VFO enable
else if ext = 'VF' then ans := ZZFA(s) // alias ZZFA
else if ext = 'VG' then ans := ZZVG(s)
else if ext = 'VH' then ans := '' // STUB: VFO high
else if ext = 'VI' then ans := '' // STUB: VFO input
else if ext = 'VJ' then ans := '' // STUB: VFO J
else if ext = 'VK' then ans := '' // STUB: VFO K
else if ext = 'VL' then ans := '' // STUB: VFO level
else if ext = 'VM' then ans := '' // STUB: VFO mode
else if ext = 'VN' then ans := ZZVN
else if ext = 'VO' then ans := '' // STUB: VFO on/off
else if ext = 'VP' then ans := '' // STUB: VFO preset
else if ext = 'VQ' then ans := '' // STUB: VFO Q
else if ext = 'VR' then ans := '' // STUB: VFO read
else if ext = 'VS' then ans := ZZVS(s)
else if ext = 'VT' then ans := '' // STUB: VFO TX
else if ext = 'VU' then ans := '' // STUB: VFO update
else if ext = 'VV' then ans := '' // STUB: VFO value
else if ext = 'VW' then ans := '' // STUB: VFO write
else if ext = 'VX' then ans := '' // STUB: VFO extended
else if ext = 'VY' then ans := '' // STUB: VFO Y
else if ext = 'VZ' then ans := '' // STUB: VFO Z
else if ext = 'WA' then ans := ZZWA(s)
else if ext = 'WB' then ans := ZZWB(s)
else if ext = 'WC' then ans := ZZWC(s)
else if ext = 'WD' then ans := ZZWD(s)
else if ext = 'WE' then ans := ZZWE(s)
else if ext = 'WF' then ans := ZZWF(s)
else if ext = 'WG' then ans := ZZWG(s)
else if ext = 'WH' then ans := ZZWH(s)
else if ext = 'WJ' then ans := ZZWJ(s)
else if ext = 'WK' then ans := ZZWK(s)
else if ext = 'WL' then ans := ZZWL(s)
else if ext = 'WM' then ans := ZZWM(s)
else if ext = 'WN' then ans := ZZWN(s)
else if ext = 'WO' then ans := ZZWO(s)
else if ext = 'WP' then ans := ZZWP(s)
else if ext = 'WQ' then ans := ZZWQ(s)
else if ext = 'WR' then ans := ZZWR(s)
else if ext = 'WS' then ans := ZZWS(s)
else if ext = 'WT' then ans := '' // STUB: waterfall T
else if ext = 'WU' then ans := '' // STUB: waterfall U
else if ext = 'WV' then ans := '' // STUB: waterfall V
else if ext = 'WW' then ans := '' // STUB: waterfall W
else if ext = 'XA' then ans := ZZXA(s)
else if ext = 'XC' then ans := ZZXC
else if ext = 'XD' then ans := '' // STUB: extended D
else if ext = 'XF' then ans := ZZXF(s)
else if ext = 'XH' then ans := '' // STUB: extended H
else if ext = 'XN' then ans := '' // STUB: extended N
else if ext = 'XO' then ans := '' // STUB: extended O
else if ext = 'XS' then ans := '' // STUB: extended S
else if ext = 'XT' then ans := '' // STUB: extended T
else if ext = 'XU' then ans := '' // STUB: extended U
else if ext = 'XV' then ans := '' // STUB: extended V
else if ext = 'YA' then ans := ZZYA(s)
else if ext = 'YB' then ans := ZZYB(s)
else if ext = 'YC' then ans := ZZYC(s)
else if ext = 'YR' then ans := ZZYR(s)
else if ext = 'ZA' then ans := ZZZA(s)
else if ext = 'ZB' then begin ZZZB; ans := ''; end
else if ext = 'ZD' then ans := '' // STUB: system D
else if ext = 'ZE' then ans := '' // STUB: system E
else if ext = 'ZM' then ans := ZZZM(s)
else if ext = 'ZN' then ans := '' // STUB: system N
else if ext = 'ZO' then ans := ZZZO(s)
else if ext = 'ZP' then ans := ZZZP(s)
else if ext = 'ZQ' then ans := ZZZQ(s)
else if ext = 'ZR' then ans := ZZZR(s)
else if ext = 'ZS' then ans := ZZZS(s)
else if ext = 'ZT' then ans := ZZZT(s)
else if ext = 'ZU' then ans := ZZZU(s)
else if ext = 'ZV' then ans := ZZZV(s)
else if ext = 'ZW' then ans := ZZZW(s)
else if ext = 'ZZ' then begin ZZZZ; ans := ''; end
else begin Result := CAT_ERROR; Exit; end;
if (ans = '') or (ans = CAT_ERROR) or (ans = CAT_EXEC_ERROR) then
Result := ans
else
Result := 'ZZ' + ext + s + ans + ';';
end;
{ ══════════════════════════════════════════════════════════════════════════
ZZ command implementations
══════════════════════════════════════════════════════════════════════════ }
function TCATEngine.ZZAA(const s: string): string;
// ZZAA: RX1 AF gain — alias ZZAG
begin Result := ZZAG(s); end;
function TCATEngine.ZZAB(const s: string): string;
// ZZAB: RX2 AF gain — STUB: no RX2
begin
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '050'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZAC(const s: string): string;
// ZZAC: band selection (2-digit band index)
var idx: Integer;
begin
if Length(s) = 2 then begin
idx := EnsureRange(StrToIntDef(s, 0), 0, 10);
if Assigned(FCtx.DoBandByIndex) then FCtx.DoBandByIndex(idx);
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetBand, 2)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZAG(const s: string): string;
// ZZAG: AF gain 0100
var v: Integer;
begin
if Length(s) = 3 then begin
v := EnsureRange(StrToIntDef(s, 0), 0, 100);
if Assigned(FCtx.SetVolume) then FCtx.SetVolume(v);
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetVolume, 3)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZAI(const s: string): string;
// ZZAI: auto-information broadcast — STUB: not applicable for CAT-only mode
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZAP(const s: string): string;
// ZZAP: TX power 0100
begin Result := ZZPC(s); end;
function TCATEngine.ZZAR(const s: string): string;
// ZZAR: RIT enable — STUB: no RIT
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZAS(const s: string): string;
// ZZAS: sub-RX enable — STUB: no RX2
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZAT(const s: string): string;
// ZZAT: antenna tuner — STUB: no ATU
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZBA: string;
// ZZBA: sub-RX AF gain — STUB: no RX2
begin Result := '050'; end;
function TCATEngine.ZZBD: string;
// ZZBD: band down
begin
if Assigned(FCtx.DoBandDown) then FCtx.DoBandDown;
Result := '';
end;
function TCATEngine.ZZBE(const s: string): string;
// ZZBE: band enable — STUB: all bands always enabled
begin
if Length(s) = 3 then Result := ''
else if Length(s) = 2 then Result := '1'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZBI(const s: string): string;
// ZZBI: filter index (2-digit, 0-9)
var idx: Integer;
begin
if Length(s) = 2 then begin
idx := EnsureRange(StrToIntDef(s, 0), 0, 9);
if Assigned(FCtx.SetFilterIdx) then FCtx.SetFilterIdx(idx);
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetFilterIdx, 2)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZBM(const s: string): string;
// ZZBM: band mode — set mode for current band
begin Result := ZZMD(s); end;
function TCATEngine.ZZBS(const s: string): string;
// ZZBS: band select — same as ZZAC
begin Result := ZZAC(s); end;
function TCATEngine.ZZBU: string;
begin
if Assigned(FCtx.DoBandUp) then FCtx.DoBandUp;
Result := '';
end;
function TCATEngine.ZZCB(const s: string): string;
// ZZCB: compression bypass — STUB: TX DSP not implemented
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZCD(const s: string): string;
// ZZCD: compressor drive — STUB: TX DSP not implemented
begin
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '050'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZCN(const s: string): string;
// ZZCN: compressor on — STUB: TX DSP not implemented
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZCS(const s: string): string;
// ZZCS: compressor status — STUB
begin
if Length(s) = 0 then Result := '0' else Result := CAT_ERROR;
end;
function TCATEngine.ZZDA(const s: string): string;
// ZZDA: diversity A — STUB: diversity not implemented
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZDB(const s: string): string;
// ZZDB: diversity B — STUB
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZDM(const s: string): string;
// ZZDM: display mode — STUB: no separate display mode setting
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZDN(const s: string): string;
// ZZDN: drive level — alias ZZPC
begin Result := ZZPC(s); end;
function TCATEngine.ZZDU: string;
// ZZDU: display units update — STUB
begin Result := ''; end;
function TCATEngine.ZZEM(const s: string): string;
// ZZEM: error message — returns error code info (Verbose mode)
begin Result := CAT_ERROR; end;
function TCATEngine.ZZEA(const s: string): string;
// ZZEA: RX EQ profile name — STUB: no EQ
begin
if Length(s) > 0 then Result := '' else Result := ''; // no EQ
end;
function TCATEngine.ZZEB(const s: string): string;
// ZZEB: TX EQ profile name — STUB: TX DSP not implemented
begin
if Length(s) > 0 then Result := '' else Result := '';
end;
function TCATEngine.ZZFA(const s: string): string;
// ZZFA: VFO A frequency (11 digits, Hz)
begin
if Length(s) = 11 then begin
if Assigned(FCtx.SetVfoA) then FCtx.SetVfoA(StrToFreq(s));
Result := '';
end else if Length(s) = 0 then
Result := FreqToStr(SafeGetVfoA)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZFB(const s: string): string;
// ZZFB: VFO B frequency
begin
if Length(s) = 11 then begin
if Assigned(FCtx.SetVfoB) then FCtx.SetVfoB(StrToFreq(s));
Result := '';
end else if Length(s) = 0 then
Result := FreqToStr(SafeGetVfoB)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZFD(const s: string): string;
// ZZFD: FM deviation — STUB: FM deviation not configurable
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '1'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZFI(const s: string): string;
// ZZFI: filter index (2-digit)
begin Result := ZZBI(s); end;
function TCATEngine.ZZFL(const s: string): string;
// ZZFL: filter low cut (signed Hz, e.g. "-150")
// STUB: ewsdr uses indexed filters, not arbitrary Hz cutoffs
begin
if Length(s) >= 4 then
Result := '' // accept but ignore — STUB
else if Length(s) = 0 then begin
if SafeGetMode in [MODE_CWL, MODE_CWU] then Result := '-0500'
else Result := '-1500';
end else
Result := CAT_ERROR;
end;
function TCATEngine.ZZFH(const s: string): string;
// ZZFH: filter high cut (signed Hz)
// STUB: ewsdr uses indexed filters, not arbitrary Hz cutoffs
begin
if Length(s) >= 4 then
Result := '' // accept but ignore — STUB
else if Length(s) = 0 then begin
if SafeGetMode in [MODE_CWL, MODE_CWU] then Result := '+0500'
else Result := '+3000';
end else
Result := CAT_ERROR;
end;
function TCATEngine.ZZFM: string;
// ZZFM: filter mode — read-only, returns current filter index as string
begin
Result := Pad(SafeGetFilterIdx, 2);
end;
function TCATEngine.ZZFT(const s: string): string;
// ZZFT: TX frequency — same as ZZFA (no split yet)
begin Result := ZZFA(s); end;
function TCATEngine.ZZGA(const s: string): string;
// ZZGA: GUID add (TCP client ID management) — handled by TCP server layer
// If called from serial, just acknowledge
begin
if Length(s) = 36 then Result := s
else Result := CAT_ERROR;
end;
function TCATEngine.ZZGR(const s: string): string;
// ZZGR: GUID remove (TCP client ID) — handled by TCP server layer
begin
if Length(s) = 36 then Result := s
else Result := CAT_ERROR;
end;
function TCATEngine.ZZGT(const s: string): string;
// ZZGT: AGC mode (1 char: 0=FAST,1=MED,2=SLOW,3=LONG,4=OFF) → 3-digit response
var m: Integer;
begin
if Length(s) = 1 then begin
m := EnsureRange(StrToIntDef(s, 0), 0, 4);
if Assigned(FCtx.SetAGCMode) then FCtx.SetAGCMode(m);
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetAGCMode, 3)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZID: string;
begin Result := CAT_ID_NUM; end;
function TCATEngine.ZZIF(const s: string): string;
// ZZIF: IF — STUB: secondary interface (returns same as IF)
begin
if Length(s) = 0 then Result := CmdIF
else Result := CAT_ERROR;
end;
function TCATEngine.ZZIO: string;
// ZZIO: I/O status — STUB
begin Result := '0'; end;
function TCATEngine.ZZIS(const s: string): string;
// ZZIS: initial setup flag — STUB
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '1'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZKM(const s: string): string;
// ZZKM: CW key mode — STUB: no CW keyer
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZKS(const s: string): string;
// ZZKS: CW speed (WPM) — STUB: no CW keyer
begin
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '025'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZKY(const s: string): string;
// ZZKY: CW text to send — STUB: no CW keyer implemented
begin
Result := '0'; // buffer not full
end;
function TCATEngine.ZZMA(const s: string): string;
// ZZMA: RX1 mode
begin Result := ZZMD(s); end;
function TCATEngine.ZZMB(const s: string): string;
// ZZMB: RX2 mode — STUB: no RX2; returns same as RX1
begin Result := ZZMD(s); end;
function TCATEngine.ZZMD(const s: string): string;
// ZZMD: mode (1 digit Kenwood code, same as MD)
var k: Integer;
begin
if Length(s) = 1 then begin
k := StrToIntDef(s, 0);
if (k >= 1) and Assigned(FCtx.SetMode) then FCtx.SetMode(KToMode(k));
Result := '';
end else if Length(s) = 0 then
Result := IntToStr(ModeToK(SafeGetMode))
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZMG(const s: string): string;
// ZZMG: microphone gain 070 (Thetis scale) — STUB: TX DSP not implemented
begin
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '050'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZML: string;
// ZZML: mode list (returns all mode names)
begin Result := 'LSB USB DSB CWL CWU FM AM SAM '; end;
function TCATEngine.ZZMN(const s: string): string;
// ZZMN: mode name — returns mode name for given mode number
var k: Integer;
names: array[0..7] of string;
begin
names[0]:='LSB'; names[1]:='USB'; names[2]:='DSB'; names[3]:='CWL';
names[4]:='CWU'; names[5]:='FM'; names[6]:='AM'; names[7]:='SAM';
if Length(s) = 1 then begin
k := EnsureRange(StrToIntDef(s,0),0,7);
Result := names[k];
end else Result := CAT_ERROR;
end;
function TCATEngine.ZZMO(const s: string): string;
// ZZMO: monitor on/off — STUB: no TX monitor
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZMV: string;
// ZZMV: mode value — returns current mode index (ewsdr 0-7)
begin Result := Pad(SafeGetMode, 2); end;
function TCATEngine.ZZMY: string;
// ZZMY: mode Y — STUB
begin Result := ''; end;
function TCATEngine.ZZNA(const s: string): string;
// ZZNA: noise blanker A (NB1) on/off
var v: Integer;
begin
if Length(s) = 1 then begin
v := StrToIntDef(s, 0);
if Assigned(FCtx.SetNBMode) then FCtx.SetNBMode(v);
Result := '';
end else if Length(s) = 0 then
Result := IntToStr(EnsureRange(SafeGetNBMode,0,1))
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZNB(const s: string): string;
// ZZNB: noise blanker mode (extended, 0-4)
var v: Integer;
begin
if Length(s) = 1 then begin
v := EnsureRange(StrToIntDef(s,0),0,4);
if Assigned(FCtx.SetNBMode) then FCtx.SetNBMode(v);
Result := '';
end else if Length(s) = 0 then
Result := IntToStr(SafeGetNBMode)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZNL(const s: string): string;
// ZZNL: NB level — STUB: NB level not configurable
begin
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '050'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZNN(const s: string): string;
// ZZNN: notch (manual) on/off — STUB: no manual notch
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZNR(const s: string): string;
// ZZNR: noise reduction on/off (0=off, 1-4=NR mode)
var v: Integer;
begin
if Length(s) = 1 then begin
v := EnsureRange(StrToIntDef(s,0),0,4);
if Assigned(FCtx.SetNRMode) then FCtx.SetNRMode(v);
Result := '';
end else if Length(s) = 0 then
Result := IntToStr(SafeGetNRMode)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZNS(const s: string): string;
// ZZNS: spectral noise blanker (SNB) on/off
begin
if Length(s) = 1 then begin
if Assigned(FCtx.SetSNBEnabled) then FCtx.SetSNBEnabled(s = '1');
Result := '';
end else if Length(s) = 0 then
Result := B2C(SafeGetSNB)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZNT(const s: string): string;
// ZZNT: noise blanker type — STUB: maps to NB mode
begin Result := ZZNB(s); end;
function TCATEngine.ZZOA(const s: string): string;
// ZZOA: RX1 output level — alias ZZAG
begin Result := ZZAG(s); end;
function TCATEngine.ZZOB(const s: string): string;
// ZZOB: RX2 output level — STUB: no RX2
begin
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '050'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZPC(const s: string): string;
// ZZPC: TX power 0100 → DriveLevel
var v: Integer;
begin
if Length(s) = 3 then begin
v := EnsureRange(StrToIntDef(s,0),0,100);
if Assigned(FCtx.SetDriveLevel) then FCtx.SetDriveLevel(v);
Result := '';
end else if Length(s) = 0 then
Result := Pad(SafeGetDriveLevel, 3)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZPD: string;
// ZZPD: power default — return nominal 100
begin Result := '100'; end;
function TCATEngine.ZZPO(const s: string): string;
// ZZPO: power on/off — STUB: always on when running
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := B2C(SafeGetRunning)
else Result := CAT_ERROR;
end;
function TCATEngine.ZZPS(const s: string): string;
// ZZPS: power status
begin
if Length(s) = 1 then Result := ''
else if Length(s) = 0 then Result := B2C(SafeGetRunning)
else Result := CAT_ERROR;
end;
function TCATEngine.ZZQM: string;
// ZZQM: current mode query
begin Result := IntToStr(ModeToK(SafeGetMode)); end;
function TCATEngine.ZZQR: string;
// ZZQR: quick recall — STUB
begin Result := ''; end;
function TCATEngine.ZZQS: string;
// ZZQS: quick store — STUB
begin Result := ''; end;
function TCATEngine.ZZRA(const s: string): string;
// ZZRA: RIT A value — STUB: no RIT
begin
if Length(s) = 5 then Result := '' else if Length(s) = 0 then Result := '+0000'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZRC: string;
begin Result := ''; end; // clear RIT — STUB
function TCATEngine.ZZRD(const s: string): string;
// ZZRD: tune/RIT down
begin
if Assigned(FCtx.DoTuneDown) then FCtx.DoTuneDown;
Result := '';
end;
function TCATEngine.ZZRL(const s: string): string;
// ZZRL: RF gain — STUB: no RF gain control
begin
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '100'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZRU(const s: string): string;
// ZZRU: tune/RIT up
begin
if Assigned(FCtx.DoTuneUp) then FCtx.DoTuneUp;
Result := '';
end;
function TCATEngine.ZZRV: string;
// ZZRV: software version
begin Result := '001000'; end; // version 0.1.0
function TCATEngine.ZZRX(const s: string): string;
// ZZRX: switch to receive
begin
if Assigned(FCtx.SetTransmitting) then FCtx.SetTransmitting(False);
if Length(s) = 0 then Result := B2C(not SafeGetTX)
else Result := '';
end;
function TCATEngine.ZZSA: string;
// ZZSA: VFO tune down (maps to step down)
begin
if Assigned(FCtx.DoTuneDown) then FCtx.DoTuneDown;
Result := '';
end;
function TCATEngine.ZZSB: string;
// ZZSB: VFO tune up (maps to step up)
begin
if Assigned(FCtx.DoTuneUp) then FCtx.DoTuneUp;
Result := '';
end;
function TCATEngine.ZZSM(const s: string): string;
// ZZSM: S-meter (0=RX1, 1=RX2)
// Response: 3-digit 000-260 (calculated as (dBm+140)*2)
var sm: Integer;
begin
if (Length(s) = 1) and ((s = '0') or (s = '1')) then begin
sm := SMeterRaw(SafeGetSMeter);
Result := Pad(sm, 3);
end else
Result := CAT_ERROR;
end;
function TCATEngine.ZZSO(const s: string): string;
// ZZSO: sub-RX on — STUB: no RX2
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZSP(const s: string): string;
// ZZSP: split mode — STUB: split VFO not yet implemented in ewsdr
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'
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZSQ(const s: string): string;
// ZZSQ: squelch — STUB
begin
if Length(s) = 3 then Result := '' else if Length(s) = 0 then Result := '000'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZSR(const s: string): string;
// ZZSR: spectrum reference level (dBm, signed 4 chars e.g. "-020")
begin
if Length(s) = 4 then Result := '' // STUB: spec ref not settable via CAT
else if Length(s) = 0 then Result := '-020'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZST: string;
// ZZST: tuning step — STUB: returns 0000 (1 Hz step)
begin Result := ''; end;
function TCATEngine.ZZSU: string;
// ZZSU: sub-RX update — STUB
begin Result := ''; end;
function TCATEngine.ZZTA(const s: string): string;
// ZZTA: CTCSS on/off — STUB: FM CTCSS not implemented
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZTB(const s: string): string;
// ZZTB: CTCSS tone frequency index — STUB
begin
if Length(s) = 2 then Result := '' else if Length(s) = 0 then Result := '01'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZTX(const s: string): string;
// ZZTX: TX on/off (0=RX, 1=TX, 2=TUNE)
begin
if Length(s) = 1 then begin
if Assigned(FCtx.SetTransmitting) then FCtx.SetTransmitting(s <> '0');
Result := '';
end else if Length(s) = 0 then
Result := B2C(SafeGetTX)
else
Result := CAT_ERROR;
end;
function TCATEngine.ZZUP(const s: string): string;
// ZZUP: xPA on/off — STUB: no external PA control
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZUS: string;
// ZZUS: two-tone test — STUB: TX DSP not implemented
begin Result := ''; end;
function TCATEngine.ZZUT(const s: string): string;
// ZZUT: TX update — STUB
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZUX(const s: string): string;
// ZZUX: VFO A lock — STUB
begin
if Length(s) = 1 then Result := '' else if Length(s) = 0 then Result := '0'
else Result := CAT_ERROR;
end;
function TCATEngine.ZZUY(const s: string): string;
// ZZUY: VFO B lock — STUB
begin Result := ZZUX(s); end;
function TCATEngine.ZZVA(const s: string): string;
// ZZVA: swap VFO A↔B
var tmp: Double;
begin
if Length(s) = 0 then begin
if Assigned(FCtx.GetVfoA) and Assigned(FCtx.GetVfoB) and
Assigned(FCtx.SetVfoA) and Assigned(FCtx.SetVfoB) then begin
tmp := FCtx.GetVfoA;
FCtx.SetVfoA(FCtx.GetVfoB);
FCtx.SetVfoB(tmp);
end;
Result := '';
end else Result := CAT_ERROR;
end;
function TCATEngine.ZZVB(const s: string): string;
// ZZVB: copy VFO A to B — STUB: needs explicit operation
begin
if Length(s) = 0 then begin
if Assigned(FCtx.GetVfoA) and Assigned(FCtx.SetVfoB) then
FCtx.SetVfoB(FCtx.GetVfoA);
Result := '';
end else Result := CAT_ERROR;
end;
function TCATEngine.ZZVG(const s: string): string;
// ZZVG: volume/gain — alias ZZAG
begin Result := ZZAG(s); end;
function TCATEngine.ZZVN: string;
// ZZVN: software version string
begin Result := 'EWSDR 0.1.0'; end;
function TCATEngine.ZZVS(const s: string): string;
// ZZVS: VFO split — STUB
begin Result := ZZSP(s); end;
// WA..WS: Waterfall settings — STUB: waterfall not controllable via CAT
// These are Thetis-specific waterfall display controls
function TCATEngine.ZZWA(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWB(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWC(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWD(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWE(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWF(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWG(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWH(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWJ(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWK(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWL(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWM(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWN(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWO(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWP(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWQ(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWR(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZWS(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
// XA, XC, XF: extended controls — STUB: Thetis-specific hardware
function TCATEngine.ZZXA(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZXC: string;
begin Result := '0'; end;
function TCATEngine.ZZXF(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
// YA..YR: Y-axis / spectrum controls — STUB
function TCATEngine.ZZYA(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZYB(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZYC(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZYR(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
// ZA..ZZ: System commands — STUB (most are Thetis-specific internal controls)
function TCATEngine.ZZZA(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZB: string; begin Result := ''; end;
function TCATEngine.ZZZM(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZP(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZQ(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZR(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZS(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZT(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZU(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZV(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZW(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZZ: string;
// ZZZZ: system reset — STUB: would require restarting DSP engine
begin Result := ''; end;
function TCATEngine.ZZZO(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
end.