mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
WFM использует новые wfmd/wfmmod из нашего форка WDSP. Демодулятор —
квадратурный дискриминатор на dsp_rate, поэтому при штатных 48 кГц
девиация 75 кГц не помещается в полосу. В WFM цепь RXA поднимается до
WFMDSPRate (наибольший FAudioRate*2^k <= 192 кГц, делящий SampleRate
нацело), rsmpin децимирует вход сам. Выше 192 кГц смысла нет: энергия
демодулированного тона выходит на полку (33.5k при 192 кГц против 31.8k
при 576 кГц), а FFT дорожает.
Две ловушки WDSP, найденные по дороге:
* firmin.c: nfor = nc / size, idxmask = nfor - 1. Все fircore создаются
с nc = max(2048, dsp_size), поэтому смена dsp_size без RXASetNC даёт
nfor = 0 — пустой массив планов и падение в fftw_execute.
* fir.c/bandpass.c планируют с FFTW_PATIENT, а wisdom.c пишет в
wdspWisdom00 только степени двойки. При SampleRate 576 кГц наивный
dsp_size = in_size = 12288 требовал FFT на 24576 точек — FFTW мерил
план 25 секунд. Отсюда потолок 192 кГц и dsp_size = степень двойки.
RXA и TXA гейтятся раздельно: рейты TXA (48 кГц вход, 192 кГц выход) от
SampleRate не зависят, поэтому wfmmod уходит на широкий dsp_rate даже
когда приёмник остался на 48 кГц.
Заодно исправлен WDSP_SAM: было 12, а RXA_SAM = 10. Раньше 12 было вне
диапазона и WDSP молча игнорировал (SAM работал как DSB); после появления
RXA_WFM = 12 это включало бы WFM-демодулятор вместо SAM. txaMode
расходится с rxaMode после DRM (WFM: RXA 12, TXA 14) — отсюда ModeToWDSPTX.
DIGI-U/DIGI-L получили свою таблицу фильтров (3.0k…300) и полосу от нуля,
а не от среза на 100 Гц, как у SSB. В CAT они замаплены на коды Kenwood 6
(FSK) и 9 (FSK-R) — раньше эти коды сваливались в USB/LSB.
Новые индексы дописаны в конец (DIGU=8, DIGL=9, WFM=10): индекс режима
уходит в settings.json, band-cache, CAT и web-API. В VfoOverlay пришлось
развести хит-коды — режимы кодировались как -(I+1) и налезли на HIT_NR/
HIT_NB/HIT_SNB, а полосы кодировались значением BW, и WFM-овские
120000/160000/200000 налезли на HIT_AGC_PICK_BASE/HIT_DEV_BASE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2346 lines
82 KiB
ObjectPascal
2346 lines
82 KiB
ObjectPascal
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_* из WDSPEngine)
|
||
MODE_LSB = 0; MODE_USB = 1; MODE_DSB = 2;
|
||
MODE_CWL = 3; MODE_CWU = 4; MODE_FM = 5;
|
||
MODE_AM = 6; MODE_SAM = 7;
|
||
MODE_DIGU = 8; MODE_DIGL = 9; MODE_WFM = 10;
|
||
CAT_MODE_COUNT = 11;
|
||
|
||
// 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;
|
||
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;
|
||
// Andromeda front panel input
|
||
TCATPanelEncoderProc = procedure(Enc, Step: Integer) of object;
|
||
TCATPanelButtonProc = procedure(Btn: Integer; State, LongPress: Boolean) 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;
|
||
// 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;
|
||
// Andromeda front panel input (ZZZD/ZZZU/ZZZE/ZZZP/ZZZS)
|
||
OnPanelVfoStep: TCATSetIntProc; // знаковое число шагов энкодера VFO
|
||
OnPanelEncoder: TCATPanelEncoderProc; // номер энкодера (0-based) + знаковый шаг
|
||
OnPanelButton: TCATPanelButtonProc; // номер кнопки (0-based) + state/long-press
|
||
OnPanelVersion: TCATSetIntProc; // версия HW/SW панели (рукопожатие)
|
||
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;
|
||
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;
|
||
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 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;
|
||
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(const s: string): 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 ZZZD(const s: string): string;
|
||
function ZZZE(const s: string): 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_WFM: Result := 4; // treated as FM
|
||
MODE_AM: Result := 5;
|
||
MODE_SAM: Result := 5; // treated as AM
|
||
MODE_DIGU: Result := 6; // Kenwood FSK
|
||
MODE_CWL: Result := 7;
|
||
MODE_DIGL: Result := 9; // Kenwood FSK-R
|
||
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_DIGU; // FSK/RTTY
|
||
7: Result := MODE_CWL;
|
||
9: Result := MODE_DIGL; // FSK-R
|
||
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;
|
||
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 ─────────────────────────────────────────────── }
|
||
|
||
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 0–255 (Kenwood) ↔ 0–100 (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 number (2 digits, 1-based 01..38 по таблице CTCSS_TONES).
|
||
var idx: Integer;
|
||
begin
|
||
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 (1=on, 0=off) для FM-передачи.
|
||
begin
|
||
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.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 (0=VFO A, 1=VFO B) — выбирает активный приёмный VFO.
|
||
begin
|
||
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;
|
||
|
||
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 — 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 := B2C(SafeGetSplit);
|
||
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 (9 digits, Hz).
|
||
begin
|
||
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 (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 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;
|
||
// 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: 0000–0015 → S0–S9+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. 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 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;
|
||
// 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 := 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
|
||
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 := 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
|
||
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 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
|
||
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 := ZZZD(s)
|
||
else if ext = 'ZE' then ans := ZZZE(s)
|
||
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 0–100
|
||
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 0–100
|
||
begin Result := ZZPC(s); end;
|
||
|
||
function TCATEngine.ZZAR(const s: string): string;
|
||
// ZZAR: RX1 AGC-T порог (RF gain). Формат: знак + 3 цифры (напр. "+020", "+120").
|
||
// Контроллер клампит в свой диапазон (20..120 dB).
|
||
var v: Integer;
|
||
begin
|
||
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;
|
||
// 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: CTUN (click-tune) для RX1 вкл/выкл.
|
||
begin
|
||
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;
|
||
|
||
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 0–70 (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 DIGUDIGLWFM '; end;
|
||
|
||
function TCATEngine.ZZMN(const s: string): string;
|
||
// ZZMN: mode name — returns mode name for given mode number.
|
||
// Индекс режима теперь двузначный (0..10), но односимвольный запрос
|
||
// продолжаем принимать — старые клиенты шлют одну цифру.
|
||
var k: Integer;
|
||
names: array[0..CAT_MODE_COUNT-1] 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';
|
||
names[8]:='DIGU'; names[9]:='DIGL'; names[10]:='WFM';
|
||
if (Length(s) = 1) or (Length(s) = 2) then begin
|
||
k := EnsureRange(StrToIntDef(s,0),0,CAT_MODE_COUNT-1);
|
||
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: 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
|
||
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 0–100 → 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: squelch on/off (расширенный статус шумоподавителя). 0=off, иначе on.
|
||
begin
|
||
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 — 1=TX на VFO-B, 0=TX на VFO-A. TX-частоту радио берёт из
|
||
// ActiveTXFreqHz (учитывает split), поэтому достаточно выставить флаг.
|
||
begin
|
||
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 threshold (3 digits, 000–100 %). Уровень>0 включает шумоподавитель,
|
||
// 000 — открывает (выключает). Маппится на FM-squelch контроллера.
|
||
var v: Integer;
|
||
begin
|
||
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;
|
||
// 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(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: шаг настройки вверх (как колесо мыши) — сдвигает активный VFO вверх.
|
||
begin
|
||
if Assigned(FCtx.DoTuneUp) then FCtx.DoTuneUp;
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZTA(const s: string): string;
|
||
// ZZTA: CTCSS on/off (1=on, 0=off) для FM-передачи.
|
||
begin
|
||
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 index (2 digits, 0-based 00..37 в таблице CTCSS_TONES).
|
||
begin
|
||
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;
|
||
// 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.ZZZD(const s: string): string;
|
||
// ZZZD: Andromeda — энкодер VFO повёрнут вниз на nn шагов (write-only).
|
||
begin
|
||
if Length(s) > 0 then begin
|
||
if Assigned(FCtx.OnPanelVfoStep) then FCtx.OnPanelVfoStep(-StrToIntDef(s, 0));
|
||
Result := '';
|
||
end else
|
||
Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZZE(const s: string): string;
|
||
// ZZZE: Andromeda — поворот энкодера. Кодировка Thetis: верхние 2 цифры — номер
|
||
// энкодера (01..20 = +шаг, 51..70 = −шаг), нижняя цифра — величина шага.
|
||
var raw, enc, step: Integer;
|
||
begin
|
||
if Length(s) > 0 then begin
|
||
raw := StrToIntDef(s, 0);
|
||
step := raw mod 10;
|
||
enc := raw div 10;
|
||
if (enc >= 1) and (enc <= 20) then begin
|
||
if Assigned(FCtx.OnPanelEncoder) then FCtx.OnPanelEncoder(enc - 1, step);
|
||
end else if (enc >= 51) and (enc <= 70) then begin
|
||
if Assigned(FCtx.OnPanelEncoder) then FCtx.OnPanelEncoder(enc - 51, -step);
|
||
end;
|
||
Result := '';
|
||
end else
|
||
Result := CAT_ERROR;
|
||
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;
|
||
// ZZZP: Andromeda — нажатие кнопки (write-only). Кодировка: nn*10 + state,
|
||
// state: 0=отпущена, 1=нажата, 2=long-press. nn — номер кнопки (1-based).
|
||
var raw, btn: Integer; state, long: Boolean;
|
||
begin
|
||
if Length(s) > 0 then begin
|
||
raw := StrToIntDef(s, 0);
|
||
state := (raw mod 10) = 1;
|
||
long := (raw mod 10) = 2;
|
||
btn := raw div 10;
|
||
if Assigned(FCtx.OnPanelButton) then FCtx.OnPanelButton(btn - 1, state, long);
|
||
Result := '';
|
||
end else
|
||
Result := CAT_ERROR;
|
||
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;
|
||
// ZZZS: Andromeda — панель сообщает версию HW/SW (write-only, рукопожатие).
|
||
begin
|
||
if Length(s) > 0 then begin
|
||
if Assigned(FCtx.OnPanelVersion) then FCtx.OnPanelVersion(StrToIntDef(s, 0));
|
||
Result := '';
|
||
end else
|
||
Result := CAT_ERROR;
|
||
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;
|
||
// ZZZU: Andromeda — энкодер VFO повёрнут вверх на nn шагов (write-only).
|
||
begin
|
||
if Length(s) > 0 then begin
|
||
if Assigned(FCtx.OnPanelVfoStep) then FCtx.OnPanelVfoStep(StrToIntDef(s, 0));
|
||
Result := '';
|
||
end else
|
||
Result := CAT_ERROR;
|
||
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.
|