mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
Три замечания по79132f1. 1. [P2] Лимит памяти рекордеров обходился при SAVE. Take собирал сплошную копию записи, а DropData тут же возвращал исходные куски в бюджет — копия жила дальше в асинхронном TTCIWavWriter уже неучтённой. Один SAVE поднимал настоящее потребление примерно вдвое, а на медленном или зависшем сетевом каталоге очередь writer-потоков и их буферов росла мимо потолка вовсе. Теперь копия НАСЛЕДУЕТ резерв кусков, из которых собрана: Take отдаёт его out-параметром Reserved, писатель держит до конца записи и отпускает в ReleaseData — ровно один раз, сколько бы путей выхода ни было у Execute. Новых денег у бюджета копия не берёт, так что потолок теперь считает и очередь сохранений тоже. 2. [P2] Пять ZZ-команд проверяли длину поля, но не содержимое: StrToIntDef(s, 0) превращал любую нечисловую пару символов в индекс 0. ZZBSxx; переключал диапазон на нулевой вместо ?;, ZZBMxx; и ZZAUxx;/ZZBPxx; двигали VFO, ZZFIxx; выбирал фильтр 0. Разбор приведён к идиоме ZZFL/ZZFH: TryStrToInt, иначе ошибка формата. 3. [P3] Сообщение об отказе запуска TCI звало в Settings → Advanced, а настройки там уже не живут — вкладка CAT (переезд был вde0830f). Стенд 194/194: Take отдаёт резерв размером с копию, после Take занят ровно он, писатель возвращает его по окончании. Без фикса первая проверка краснеет. GUI (--ws=qt6) и демон зелёные. doc/TCI.md §2.5 и сводка стенда, doc/CAT_STATUS.md — таблица разбора. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2888 lines
124 KiB
ObjectPascal
2888 lines
124 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, RadioModes;
|
||
|
||
const
|
||
CAT_ERROR = '?;';
|
||
CAT_EXEC_ERROR = 'E;';
|
||
CAT_ID_NUM = '019'; // TS-2000 compatible
|
||
|
||
CAT_MODE_COUNT = RadioModes.MODE_COUNT;
|
||
|
||
// FM repeater offset directions — зеркало FMRepeater.RPT_* (значения должны
|
||
// совпадать: контроллер хранит FFMRptDir в этих же кодах).
|
||
RPT_NONE = 0; RPT_MINUS = 1; RPT_PLUS = 2;
|
||
|
||
// Границы TX/CW-полей. Совпадают с контролами вкладок Transmit и CW: движок
|
||
// клампует сам, потому что по CAT прилетает что угодно, а контроллер эти
|
||
// поля принимает записью целиком и уже не знает, кто их правил.
|
||
MIC_GAIN_MIN = -20; MIC_GAIN_MAX = 60; // дБ
|
||
TX_FILT_LO_MIN = 0; TX_FILT_LO_MAX = 8000; // Гц
|
||
TX_FILT_HI_MIN = 100; TX_FILT_HI_MAX = 12000; // Гц
|
||
COMP_GAIN_MAX = 20; // дБ
|
||
EQ_GAIN_MIN = -20; EQ_GAIN_MAX = 20; // дБ
|
||
CW_PITCH_MIN = 200; CW_PITCH_MAX = 1200; // Гц
|
||
CW_HANG_MAX = 2000; // мс
|
||
CW_WPM_MIN = 5; CW_WPM_MAX = 60;
|
||
CW_TEXT_FIELD = 25; // ширина поля текста KY/ZZKY
|
||
FM_DEV_NARROW = 2500; FM_DEV_WIDE = 5000; // Гц (ZZFD — радиокнопка)
|
||
|
||
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;
|
||
// TX-эквалайзер (ZZEB): [0] — преамп, 1..10 — полосы, дБ. Формат провода
|
||
// (счётчик полос + знаковые тройки) знает движок, состав записи — адаптер.
|
||
TCATEQGains = array[0..10] of Integer;
|
||
TCATGetEQFunc = function(out Gains: TCATEQGains): Integer of object; // → число полос
|
||
TCATSetEQProc = procedure(NumBands: Integer; const Gains: TCATEQGains) 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;
|
||
// Кромки фильтра в Гц (ZZFL/ZZFH). Главный приёмник настраивается пресетами
|
||
// (callback'и nil → команды отвечают дефолтом режима), слайс — кромками.
|
||
GetFilterLow: TCATGetIntFunc;
|
||
GetFilterHigh: TCATGetIntFunc;
|
||
SetFilterLow: TCATSetIntProc;
|
||
SetFilterHigh: TCATSetIntProc;
|
||
// Телеграф (KS/KY/ZZKS/ZZKY/ZZKM). Скорость — WPM 5..60; SendCWText ставит
|
||
// текст в очередь программной передачи, CWTextBusy = «очередь занята».
|
||
GetCWSpeed: TCATGetIntFunc;
|
||
SetCWSpeed: TCATSetIntProc;
|
||
GetCWKeyerMode: TCATGetIntFunc; // 0=прямой ключ, 1=iambic A, 2=iambic B
|
||
SetCWKeyerMode: TCATSetIntProc;
|
||
SendCWText: procedure(const S: string) of object;
|
||
CWTextBusy: TCATGetBoolFunc;
|
||
// Телеграф, продолжение: тон приёма/сайдтона (ZZCL), break-in прошивки
|
||
// (ZZCB) и его hang-time (ZZCD), сайдтон активного источника манипуляции
|
||
// (ZZCM — у Thetis это «monitor DISABLE», инверсия делается в движке).
|
||
GetCWPitch: TCATGetIntFunc; // Гц
|
||
SetCWPitch: TCATSetIntProc;
|
||
GetCWBreakIn: TCATGetBoolFunc;
|
||
SetCWBreakIn: TCATSetBoolProc;
|
||
GetCWHangTime: TCATGetIntFunc; // мс
|
||
SetCWHangTime: TCATSetIntProc;
|
||
GetCWMonitor: TCATGetBoolFunc;
|
||
SetCWMonitor: TCATSetBoolProc;
|
||
// ── TX-тракт (вкладка Transmit / активный TX-профиль) ──
|
||
GetMicGain: TCATGetIntFunc; // дБ (у EWSDR -20..+60)
|
||
SetMicGain: TCATSetIntProc;
|
||
GetTXFilterLow: TCATGetIntFunc; // Гц
|
||
SetTXFilterLow: TCATSetIntProc;
|
||
GetTXFilterHigh: TCATGetIntFunc; // Гц
|
||
SetTXFilterHigh: TCATSetIntProc;
|
||
GetCompOn: TCATGetBoolFunc; // речевой компрессор (WDSP COMP)
|
||
SetCompOn: TCATSetBoolProc;
|
||
GetCompGain: TCATGetIntFunc; // дБ 0..20
|
||
SetCompGain: TCATSetIntProc;
|
||
GetTXEQOn: TCATGetBoolFunc;
|
||
SetTXEQOn: TCATSetBoolProc;
|
||
GetTXEQ: TCATGetEQFunc;
|
||
SetTXEQ: TCATSetEQProc;
|
||
GetTUNLevel: TCATGetIntFunc; // мощность настройки, 0..100 %
|
||
SetTUNLevel: TCATSetIntProc;
|
||
GetFMDeviation: TCATGetIntFunc; // девиация передачи, Гц
|
||
SetFMDeviation: TCATSetIntProc;
|
||
GetTXProfile: TCATGetIntFunc; // индекс активного профиля
|
||
SetTXProfile: TCATSetIntProc;
|
||
GetTXProfileCount: TCATGetIntFunc;
|
||
// MON: у EWSDR монитор передачи — это self-monitor даунлинка (DUP + RX MUTE
|
||
// off), уровень у него свой, отдельный от RX-громкости.
|
||
GetMonitorOn: TCATGetBoolFunc;
|
||
SetMonitorOn: TCATSetBoolProc;
|
||
GetMonitorLevel: TCATGetIntFunc; // 0..100
|
||
SetMonitorLevel: TCATSetIntProc;
|
||
GetTuning: TCATGetBoolFunc; // кнопка TUN
|
||
SetTuning: TCATSetBoolProc;
|
||
GetTwoTone: TCATGetBoolFunc; // 2TON
|
||
SetTwoTone: TCATSetBoolProc;
|
||
GetPureSignal: TCATGetBoolFunc; // PS-A
|
||
SetPureSignal: TCATSetBoolProc;
|
||
DoPSSingleCal: TCATProc; // ZZUS — одиночная калибровка PS
|
||
// ── Приёмник и сервис ──
|
||
GetMuted: TCATGetBoolFunc; // кнопка MUT (ZZMA)
|
||
SetMuted: TCATSetBoolProc;
|
||
GetAtten: TCATGetIntFunc; // шаговый аттенюатор ADC, дБ (ZZRX)
|
||
SetAtten: TCATSetIntProc;
|
||
GetSupplyVolts: TCATGetDoubleFunc; // напряжение питания PA (ZZRV); <0 = нет данных
|
||
DoVfoSwap: TCATProc; // обмен VFO A↔B (ZZVS)
|
||
GetMemoryCount: TCATGetIntFunc; // число записей памяти каналов (ZZMV)
|
||
SendCWMacro: TCATSetIntProc; // запустить CW-макрос (сообщение) номер n (ZZKM)
|
||
// 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 SignedPad(V: Int64; W: Integer): string; // '+nnn' / '-nnn'
|
||
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 SafeGetCWSpeed: 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;
|
||
// TX-тракт / телеграф
|
||
function SafeGetCWKeyerMode: Integer;
|
||
function SafeGetCWPitch: Integer;
|
||
function SafeGetCWHangTime: Integer;
|
||
function SafeGetMicGain: Integer;
|
||
function SafeGetTXFilterLow: Integer;
|
||
function SafeGetTXFilterHigh: Integer;
|
||
function SafeGetCompGain: Integer;
|
||
function SafeGetTUNLevel: Integer;
|
||
function SafeGetFMDeviation: Integer;
|
||
function SafeGetTXProfile: Integer;
|
||
function SafeGetMonitorLevel: Integer;
|
||
function SafeGetBool(const F: TCATGetBoolFunc): Boolean;
|
||
// Общий разбор «1 символ 0/1» для двухпозиционных команд: SET дёргает Setter
|
||
// (если он есть), GET отдаёт Getter. Пустой Setter = команда только читает.
|
||
function OnOff(const s: string; const Getter: TCATGetBoolFunc;
|
||
const Setter: TCATSetBoolProc): string;
|
||
// То же для числового поля фиксированной ширины с клампом.
|
||
function NumField(const s: string; Width, Lo, Hi: Integer;
|
||
const Getter: TCATGetIntFunc;
|
||
const Setter: TCATSetIntProc): string;
|
||
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 ZZOS(const s: string): string;
|
||
function ZZOT(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;
|
||
// ── TX-тракт и телеграф (проброшены в контроллер, см. doc/CAT_STATUS.md) ──
|
||
function ZZCI(const s: string): string;
|
||
function ZZCL(const s: string): string;
|
||
function ZZCM(const s: string): string;
|
||
function ZZET(const s: string): string;
|
||
function ZZLI(const s: string): string;
|
||
function ZZPK(const s: string): string;
|
||
function ZZPL(const s: string): string;
|
||
function ZZTH(const s: string): string;
|
||
function ZZTL(const s: string): string;
|
||
function ZZTM(const s: string): string;
|
||
function ZZTO(const s: string): string;
|
||
function ZZTP(const s: string): string;
|
||
function ZZTU(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;
|
||
|
||
function IsParamless(const Pfx: string): Boolean;
|
||
// Кенвудовские команды, у которых и SET, и GET идут с пустым суффиксом
|
||
// (nsetparms=0 / ngetparms=0 в CATStructs.xml). Любой хвост у них — ошибка
|
||
// формата, а не значение.
|
||
const
|
||
NoArg: array[0..9] of string = (
|
||
'BD', 'BU', 'DN', 'UP', // диапазон и шаг настройки
|
||
'TX', 'RX', // передача/приём — тут цена ошибки самая высокая
|
||
'QI', 'RC', // быстрая память, сброс RIT
|
||
'ID', 'IF'); // только чтение, аргумента не бывает
|
||
var i: Integer;
|
||
begin
|
||
for i := Low(NoArg) to High(NoArg) do
|
||
if Pfx = NoArg[i] then Exit(True);
|
||
Result := False;
|
||
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.SignedPad(V: Int64; W: Integer): string;
|
||
// Знаковое поле фиксированной ширины (ZZAR/ZZFL/ZZFH): знак + W цифр.
|
||
begin
|
||
if V >= 0 then Result := '+' + Pad(V, W)
|
||
else Result := '-' + Pad(-V, 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_DMR: Result := 4; // DMR RF transport is narrow FM
|
||
MODE_FMRAW: Result := 4; // raw discriminator is still FM transport
|
||
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.SafeGetCWKeyerMode: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetCWKeyerMode) then Result := FCtx.GetCWKeyerMode else Result := 0;
|
||
end;
|
||
function TCATEngine.SafeGetCWPitch: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetCWPitch) then Result := FCtx.GetCWPitch else Result := 600;
|
||
end;
|
||
function TCATEngine.SafeGetCWHangTime: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetCWHangTime) then Result := FCtx.GetCWHangTime else Result := 300;
|
||
end;
|
||
function TCATEngine.SafeGetMicGain: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetMicGain) then Result := FCtx.GetMicGain else Result := 10;
|
||
end;
|
||
function TCATEngine.SafeGetTXFilterLow: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetTXFilterLow) then Result := FCtx.GetTXFilterLow else Result := 100;
|
||
end;
|
||
function TCATEngine.SafeGetTXFilterHigh: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetTXFilterHigh) then Result := FCtx.GetTXFilterHigh else Result := 3100;
|
||
end;
|
||
function TCATEngine.SafeGetCompGain: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetCompGain) then Result := FCtx.GetCompGain else Result := 0;
|
||
end;
|
||
function TCATEngine.SafeGetTUNLevel: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetTUNLevel) then Result := FCtx.GetTUNLevel else Result := 10;
|
||
end;
|
||
function TCATEngine.SafeGetFMDeviation: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetFMDeviation) then Result := FCtx.GetFMDeviation else Result := 5000;
|
||
end;
|
||
function TCATEngine.SafeGetTXProfile: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetTXProfile) then Result := FCtx.GetTXProfile else Result := 0;
|
||
end;
|
||
function TCATEngine.SafeGetMonitorLevel: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetMonitorLevel) then Result := FCtx.GetMonitorLevel else Result := 0;
|
||
end;
|
||
function TCATEngine.SafeGetBool(const F: TCATGetBoolFunc): Boolean;
|
||
begin
|
||
if Assigned(F) then Result := F else Result := False;
|
||
end;
|
||
|
||
function TCATEngine.OnOff(const s: string; const Getter: TCATGetBoolFunc;
|
||
const Setter: TCATSetBoolProc): string;
|
||
// Двухпозиционная команда «<PREFIX>0/1;». Формы SET и GET у всех одинаковы —
|
||
// держим разбор в одном месте, чтобы тела команд остались про смысл.
|
||
begin
|
||
if Length(s) = 0 then Exit(B2C(SafeGetBool(Getter)));
|
||
if (s <> '0') and (s <> '1') then Exit(CAT_ERROR);
|
||
if Assigned(Setter) then Setter(s = '1');
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.NumField(const s: string; Width, Lo, Hi: Integer;
|
||
const Getter: TCATGetIntFunc; const Setter: TCATSetIntProc): string;
|
||
// Числовое поле фиксированной ширины: SET клампует в [Lo..Hi] (как Thetis —
|
||
// выход за границы не ошибка, а подрезка), GET дополняет нулями слева.
|
||
var v: Integer;
|
||
begin
|
||
if Length(s) = 0 then
|
||
begin
|
||
if Assigned(Getter) then v := Getter else v := Lo;
|
||
Exit(Pad(EnsureRange(v, Lo, Hi), Width));
|
||
end;
|
||
if Length(s) <> Width then Exit(CAT_ERROR);
|
||
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
|
||
if Assigned(Setter) then Setter(EnsureRange(v, Lo, Hi));
|
||
Result := '';
|
||
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);
|
||
// Команды без параметров: эталон отбраковывает лишний суффикс в парсере,
|
||
// по таблице ширин, ДО вызова команды. Таблицы у нас нет — держим список
|
||
// здесь, иначе «TXfoo;» дошёл бы до CmdTX и поднял передачу.
|
||
if (sfx <> '') and IsParamless(pfx) then Exit(CAT_ERROR);
|
||
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 Result := CmdRD(sfx)
|
||
else if pfx = 'RT' then Result := CmdRT(sfx)
|
||
else if pfx = 'RU' then Result := CmdRU(sfx)
|
||
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 — обёртка над ZZTA, как и в эталоне.
|
||
begin
|
||
Result := ZZTA(s);
|
||
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: составной статус — 35 символов ответа (как у Thetis; «37 байт» из
|
||
// прежнего комментария — это вся посылка вместе с префиксом и ';').
|
||
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.SafeGetCWSpeed: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetCWSpeed) then Result := FCtx.GetCWSpeed else Result := 20;
|
||
end;
|
||
|
||
function TCATEngine.CmdKS(const s: string): string;
|
||
// KS: CW keying speed (3 digits, WPM).
|
||
var v: Integer;
|
||
begin
|
||
if Length(s) = 3 then
|
||
begin
|
||
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetCWSpeed) then FCtx.SetCWSpeed(v);
|
||
Result := '';
|
||
end
|
||
else if Length(s) = 0 then
|
||
Result := Format('%.3d', [SafeGetCWSpeed])
|
||
else
|
||
Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.CmdKY(const s: string): string;
|
||
// KY: передать текст телеграфом. Kenwood-семантика: первый символ — пробел-
|
||
// заполнитель признака «не занято», далее сам текст; пустой аргумент = опрос
|
||
// занятости ('1' — очередь занята, логгер ждёт).
|
||
var Txt: string;
|
||
begin
|
||
if Length(s) = 0 then
|
||
begin
|
||
if Assigned(FCtx.CWTextBusy) and FCtx.CWTextBusy then Result := '1'
|
||
else Result := '0';
|
||
Exit;
|
||
end;
|
||
// Поле текста фиксированной ширины: 25 символов, добито пробелами справа.
|
||
// Длиннее — ошибка формата: очередь передачи не должна расти произвольно,
|
||
// иначе один пакет уводит станцию в эфир на неопределённое время.
|
||
if Length(s) > CW_TEXT_FIELD then Exit(CAT_ERROR);
|
||
Txt := s;
|
||
if (Length(Txt) > 0) and (Txt[1] = ' ') then Delete(Txt, 1, 1);
|
||
Txt := TrimRight(Txt); // иначе хвост набивки ушёл бы в эфир паузами
|
||
if Txt = '' then Exit('');
|
||
if Assigned(FCtx.SendCWText) then FCtx.SendCWText(Txt);
|
||
Result := '';
|
||
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: усиление микрофона по-кенвудовски (000..100). Шкала 100:70 к дБ — как в
|
||
// Thetis (n/1.43), дальше кламп под диапазон вкладки Transmit. Отрицательные дБ
|
||
// в кенвудовскую шкалу не ложатся: на чтении подрезаем к нулю.
|
||
var v: Integer;
|
||
begin
|
||
if Length(s) = 0 then
|
||
Result := Pad(EnsureRange(Round(SafeGetMicGain / 0.7), 0, 100), 3)
|
||
else if Length(s) = 3 then
|
||
begin
|
||
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetMicGain) then
|
||
FCtx.SetMicGain(EnsureRange(Round(EnsureRange(v, 0, 100) / 1.43),
|
||
MIC_GAIN_MIN, MIC_GAIN_MAX));
|
||
Result := '';
|
||
end
|
||
else
|
||
Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.CmdMO(const s: string): string;
|
||
// MO: монитор передачи — то же, что ZZMO.
|
||
begin
|
||
Result := ZZMO(s);
|
||
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: сдвиг репитера — обёртка над ZZOT, как и в эталоне.
|
||
begin
|
||
Result := ZZOT(s);
|
||
end;
|
||
|
||
function TCATEngine.CmdOS(const s: string): string;
|
||
// OS: направление сдвига репитера — обёртка над ZZOS, как и в эталоне.
|
||
begin
|
||
Result := ZZOS(s);
|
||
end;
|
||
|
||
function TCATEngine.ZZOS(const s: string): string;
|
||
// ZZOS: направление сдвига репитера. Снаружи (и у Thetis) 0=симплекс, 1=вверх,
|
||
// 2=вниз; внутри у нас RPT_NONE/MINUS/PLUS — отсюда трансляция.
|
||
// ★Мусор эталон молча трактует как симплекс (default в String2OffsetDirection),
|
||
// и мы повторяем это намеренно: клиенты рассчитывают на такое поведение.
|
||
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.ZZOT(const s: string): string;
|
||
// ZZOT: величина сдвига репитера, 9 цифр. Эталон читает поле как МГц с шестью
|
||
// знаками после запятой (вставляет точку после третьего разряда) — численно это
|
||
// ровно те же герцы, что и у нас. Знак не передаётся, направление — в ZZOS.
|
||
var v: Int64;
|
||
begin
|
||
if Length(s) = 9 then begin
|
||
// Нечисловое поле — ошибка формата: StrToIntDef молча обнулил бы сдвиг.
|
||
if not TryStrToInt64(s, v) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetFMRptOffset) then FCtx.SetFMRptOffset(v);
|
||
Result := '';
|
||
end else if Length(s) = 0 then
|
||
Result := Pad(Round(SafeGetFMRptOffset), 9)
|
||
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: речевой компрессор вкл/выкл. У Thetis команда только читает (всегда '0');
|
||
// у нас компрессор настоящий, поэтому SET тоже работает — расширение вверх,
|
||
// кенвудовские клиенты от него не ломаются.
|
||
begin
|
||
Result := OnOff(s, FCtx.GetCompOn, FCtx.SetCompOn);
|
||
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: убавить RIT (аргумент — 5 цифр смещения) — STUB: расстройки приёма нет.
|
||
// Шаг настройки вниз — это DN, перестройка — FA/FB.
|
||
begin
|
||
if (Length(s) = 0) or (Length(s) = 5) then Result := '' else Result := CAT_ERROR;
|
||
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: прибавить RIT — STUB, см. RD.
|
||
begin
|
||
if (Length(s) = 0) or (Length(s) = 5) then Result := '' else Result := CAT_ERROR;
|
||
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: верхняя кромка фильтра, индекс пресета (2 цифры). ⚠ У нас SH и SL ходят
|
||
// в ОДИН и тот же индекс пресета: раздельных «сторон» у главного приёмника нет.
|
||
// Кромки в герцах — это ZZFL/ZZFH.
|
||
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: нижняя кромка фильтра — тот же индекс пресета, что и SH (см. оговорку там).
|
||
begin
|
||
Result := CmdSH(s);
|
||
end;
|
||
|
||
function TCATEngine.CmdSM(const s: string): string;
|
||
// SM: S-метр. Запрос — один символ-селектор приёмника, ответ — ПЯТЬ цифр
|
||
// (шкала Kenwood 00000..00030: до 00015 это S0..S9, дальше +1 за каждый дБ).
|
||
// Селектор Thetis шлёт как '0' или '2', TS-2000 — как '0'/'1'; принимаем все:
|
||
// второго приёмника нет, отвечаем одним и тем же.
|
||
var sm: Integer;
|
||
begin
|
||
if (Length(s) = 1) and (s[1] in ['0', '1', '2']) then begin
|
||
sm := SMeterRaw(SafeGetSMeter);
|
||
sm := Round(sm * 30 / 260); // 0..260 (шкала ZZSM) → 0..30
|
||
Result := Pad(sm, 5);
|
||
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: сдвиг VFO A вниз на nn шагов — см. ZZAU (вверх)
|
||
else if ext = 'AE' then ans := '' // STUB: VFO A вниз на nn пресетных шагов — нет глобального шага, см. ZZAC
|
||
else if ext = 'AF' then ans := '' // STUB: VFO A вверх на nn пресетных шагов — см. ZZAE
|
||
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 — нет аудиопикового фильтра
|
||
else if ext = 'BA' then ans := ZZBA
|
||
else if ext = 'BB' then ans := '' // STUB: bandswitch RX2 вверх — RX2 нет
|
||
else if ext = 'BD' then begin ZZBD; ans := ''; end
|
||
else if ext = 'BE' then ans := ZZBE(s)
|
||
else if ext = 'BF' then ans := '' // STUB: VFO B вверх на nn пресетных шагов — см. ZZAE
|
||
else if ext = 'BG' then ans := '' // STUB: группа диапазонов HF/VHF — не разделяем
|
||
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: кнопка BCI Rejection — такого фильтра нет
|
||
else if ext = 'BS' then ans := ZZBS(s)
|
||
else if ext = 'BT' then ans := '' // STUB: диапазон RX2 — RX2 нет
|
||
else if ext = 'BU' then begin ZZBU; ans := ''; end
|
||
else if ext = 'BY' then ans := '' // STUB: выключение консоли — по CAT не гасимся
|
||
else if ext = 'CB' then ans := ZZCB(s)
|
||
else if ext = 'CD' then ans := ZZCD(s)
|
||
else if ext = 'CF' then ans := '' // STUB: показывать частоту телеграфа (UI Thetis)
|
||
else if ext = 'CI' then ans := ZZCI(s)
|
||
else if ext = 'CL' then ans := ZZCL(s)
|
||
else if ext = 'CM' then ans := ZZCM(s)
|
||
else if ext = 'CN' then ans := ZZCN(s)
|
||
else if ext = 'CO' then ans := '' // STUB: CTUN для RX2 — RX2 нет (CTUN RX1 — это ZZCN)
|
||
else if ext = 'CP' then ans := '' // STUB: компандер (у нас его нет — есть COMP, см. ZZPK)
|
||
else if ext = 'CS' then ans := ZZCS(s)
|
||
else if ext = 'CT' then ans := '' // STUB: порог компандера
|
||
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: усиление RX2 в окне Diversity — диверсити нет
|
||
else if ext = 'DD' then ans := '' // STUB: фаза в окне Diversity — диверсити нет
|
||
else if ext = 'DE' then ans := '' // STUB: кнопка Enable в окне Diversity — диверсити нет
|
||
else if ext = 'DF' then ans := '' // STUB: открыть/закрыть окно Diversity — диверсити нет
|
||
else if ext = 'DG' then ans := '' // STUB: усиление RX в окне Diversity — диверсити нет
|
||
else if ext = 'DH' then ans := '' // STUB: источник RX в окне Diversity — диверсити нет
|
||
else if ext = 'DM' then ans := ZZDM(s)
|
||
else if ext = 'DN' then ans := ZZDN(s)
|
||
else if ext = 'DO' then ans := '' // STUB: верхний уровень водопада — не проброшено (см. ZZDN)
|
||
else if ext = 'DP' then ans := '' // STUB: верх сетки спектра — не проброшено
|
||
else if ext = 'DQ' then ans := '' // STUB: низ сетки спектра — не проброшено
|
||
else if ext = 'DR' then ans := '' // STUB: шаг сетки спектра — не проброшено
|
||
else if ext = 'DU' then begin ZZDU; ans := ''; end
|
||
else if ext = 'DX' then ans := '' // STUB: кнопка DX (споты кластера) — флаг живёт в UI, не в контроллере
|
||
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: кнопка RXEQ — эквалайзер приёма
|
||
else if ext = 'ET' then ans := ZZET(s)
|
||
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 — RX2 нет
|
||
else if ext = 'FL' then ans := ZZFL(s)
|
||
else if ext = 'FM' then ans := ZZFM
|
||
else if ext = 'FR' then ans := '' // STUB: верхняя кромка фильтра RX2 — RX2 нет
|
||
else if ext = 'FS' then ans := '' // STUB: нижняя кромка фильтра RX2 — RX2 нет
|
||
else if ext = 'FT' then ans := ZZFT(s)
|
||
else if ext = 'FV' then ans := '' // STUB: чтение FlexWire (1 байт) — шины нет
|
||
else if ext = 'FW' then ans := '' // STUB: чтение FlexWire (2 байта) — шины нет
|
||
else if ext = 'FX' then ans := '' // STUB: запись FlexWire (1 байт) — шины нет
|
||
else if ext = 'FY' then ans := '' // STUB: запись FlexWire (2 байта) — шины нет
|
||
else if ext = 'GA' then ans := ZZGA(s)
|
||
else if ext = 'GE' then ans := '' // STUB: кнопка noise gate — такого блока нет
|
||
else if ext = 'GL' then ans := '' // STUB: уровень noise gate — такого блока нет
|
||
else if ext = 'GR' then ans := ZZGR(s)
|
||
else if ext = 'GT' then ans := ZZGT(s)
|
||
else if ext = 'GU' then ans := '' // STUB: постоянная AGC для RX2 — RX2 нет
|
||
else if ext = 'HA' then ans := '' // STUB: размер аудиобуфера — по CAT не крутим
|
||
else if ext = 'HR' then ans := '' // STUB: размер буфера DSP RX (телефония) — по CAT не крутим
|
||
else if ext = 'HT' then ans := '' // STUB: размер буфера DSP TX (телефония) — по CAT не крутим
|
||
else if ext = 'HU' then ans := '' // STUB: размер буфера DSP RX (CW) — по CAT не крутим
|
||
else if ext = 'HV' then ans := '' // STUB: размер буфера DSP TX (CW) — по CAT не крутим
|
||
else if ext = 'HW' then ans := '' // STUB: размер буфера DSP RX (digital) — по CAT не крутим
|
||
else if ext = 'HX' then ans := '' // STUB: размер буфера DSP TX (digital) — по CAT не крутим
|
||
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: сдвиг IF — полосу двигаем кромками (ZZFL/ZZFH)
|
||
else if ext = 'IU' then ans := '' // STUB: сброс сдвига фильтра — см. ZZIT
|
||
else if ext = 'JO' then ans := '' // STUB: нет такой команды у Thetis
|
||
else if ext = 'JP' then ans := '' // STUB: служебная JSON-команда Thetis
|
||
else if ext = 'JQ' then ans := '' // STUB: служебная JSON-команда Thetis
|
||
else if ext = 'JR' then ans := '' // STUB: служебная JSON-команда Thetis
|
||
else if ext = 'JS' then ans := '' // STUB: служебная JSON-команда Thetis
|
||
else if ext = 'KM' then ans := ZZKM(s)
|
||
else if ext = 'KO' then ans := '' // STUB: без описания у Thetis
|
||
else if ext = 'KS' then ans := ZZKS(s)
|
||
else if ext = 'KY' then ans := ZZKY(s)
|
||
else if ext = 'LA' then ans := '' // STUB: уровень RX0 — микшер Thetis
|
||
else if ext = 'LB' then ans := '' // STUB: стереобаланс главного приёмника — микшера нет
|
||
else if ext = 'LC' then ans := '' // STUB: уровень sub-RX — sub-RX нет
|
||
else if ext = 'LD' then ans := '' // STUB: стереобаланс sub-RX — sub-RX нет
|
||
else if ext = 'LE' then ans := '' // STUB: уровень RX2 — RX2 нет
|
||
else if ext = 'LF' then ans := '' // STUB: стереобаланс RX2 — RX2 нет
|
||
else if ext = 'LG' then ans := '' // STUB: автомьют RX1 при TX на VFO B — такой автоматики нет
|
||
else if ext = 'LH' then ans := '' // STUB: автомьют RX2 при TX на VFO A — RX2 нет
|
||
else if ext = 'LI' then ans := ZZLI(s)
|
||
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: без описания у Thetis
|
||
else if ext = 'MF' then ans := '' // STUB: текст на LCD мультифункционального энкодера Andromeda — не реализовано
|
||
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: режим измерителя приёма — метр один
|
||
else if ext = 'MS' then ans := '' // STUB: галка MultiRX Swap — MultiRX нет
|
||
else if ext = 'MT' then ans := '' // STUB: режим измерителя передачи — метр один
|
||
else if ext = 'MU' then ans := '' // STUB: кнопка MultiRX — MultiRX нет
|
||
else if ext = 'MV' then ans := ZZMV
|
||
else if ext = 'MW' then ans := '' // STUB: удалить канал памяти — память в CAT не проброшена
|
||
else if ext = 'MX' then ans := '' // STUB: восстановить канал памяти n — см. ZZMW
|
||
else if ext = 'MY' then ans := ZZMY
|
||
else if ext = 'MZ' then ans := '' // STUB: записать конфигурацию в канал памяти n — см. ZZMW
|
||
else if ext = 'NA' then ans := ZZNA(s)
|
||
else if ext = 'NB' then ans := ZZNB(s)
|
||
else if ext = 'NC' then ans := '' // STUB: NB для RX2 — RX2 нет
|
||
else if ext = 'ND' then ans := '' // STUB: NB2 для RX2 — RX2 нет
|
||
else if ext = 'NE' then ans := '' // STUB: статус NR RX1 (0..4) — дублирует ZZNR, не проброшено
|
||
else if ext = 'NF' then ans := '' // STUB: статус NR для RX2 — RX2 нет
|
||
else if ext = 'NG' then ans := '' // STUB: глубина подавления NR4 — такого алгоритма нет
|
||
else if ext = 'NH' then ans := '' // STUB: глубина подавления NR4 (вторая) — см. ZZNG
|
||
else if ext = 'NL' then ans := ZZNL(s)
|
||
else if ext = 'NM' then ans := '' // STUB: порог NB2 — порог бланкера не вынесен в настройки
|
||
else if ext = 'NN' then ans := ZZNN(s)
|
||
else if ext = 'NO' then ans := '' // STUB: спектральный бланкер RX2 — RX2 нет (RX1 — это ZZNN)
|
||
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: ANF для RX2 — RX2 нет
|
||
else if ext = 'NV' then ans := '' // STUB: статус шумоподавителя — дублирует ZZNR
|
||
else if ext = 'NW' then ans := '' // STUB: кнопка NR2 для RX2 — RX2 нет
|
||
else if ext = 'OA' then ans := ZZOA(s)
|
||
else if ext = 'OB' then ans := ZZOB(s)
|
||
else if ext = 'OC' then ans := '' // STUB: антенна передачи — порты AD936x в CAT не проброшены
|
||
else if ext = 'OD' then ans := '' // STUB: режим антенн — см. ZZOC
|
||
else if ext = 'OE' then ans := '' // STUB: галка внешней антенны RX1 — см. ZZOC
|
||
else if ext = 'OF' then ans := '' // STUB: реле TX на разъёме RCA — управления реле нет
|
||
else if ext = 'OG' then ans := '' // STUB: разрешение задержек реле TX — см. ZZOF
|
||
else if ext = 'OH' then ans := '' // STUB: величины задержек реле TX — см. ZZOF
|
||
else if ext = 'OJ' then ans := '' // STUB: без описания у Thetis
|
||
else if ext = 'OL' then ans := '' // STUB: click-tune офсет для DIGL — офсетов режима нет
|
||
else if ext = 'OS' then ans := ZZOS(s)
|
||
else if ext = 'OT' then ans := ZZOT(s)
|
||
else if ext = 'OU' then ans := '' // STUB: click-tune офсет для DIGU — см. ZZOL
|
||
else if ext = 'OV' then ans := '' // STUB: кнопка ATU — тюнера нет
|
||
else if ext = 'OW' then ans := '' // STUB: кнопка ATU Bypass — тюнера нет
|
||
else if ext = 'OX' then ans := '' // STUB: состояние настройки ATU Aries — тюнера нет
|
||
else if ext = 'OZ' then ans := '' // STUB: сброс состояния ATU Aries — тюнера нет
|
||
else if ext = 'PA' then ans := '' // STUB: преамп/аттенюатор — не проброшено (аттенюатор RX1 — ZZRX)
|
||
else if ext = 'PB' then ans := '' // STUB: преамп RX2 — RX2 нет
|
||
else if ext = 'PC' then ans := ZZPC(s)
|
||
else if ext = 'PD' then ans := ZZPD
|
||
else if ext = 'PE' then ans := '' // STUB: пан дисплея — не проброшено
|
||
else if ext = 'PK' then ans := ZZPK(s)
|
||
else if ext = 'PL' then ans := ZZPL(s)
|
||
else if ext = 'PO' then ans := ZZPO(s)
|
||
else if ext = 'PS' then ans := ZZPS(s)
|
||
else if ext = 'PY' then ans := '' // STUB: зум дисплея — не проброшено
|
||
else if ext = 'PZ' then ans := '' // STUB: кнопки зума дисплея — не проброшено
|
||
// ZZQK: у Thetis break-in делится на semi и QSK; у нас break-in один
|
||
// (прошивочный), а разница semi/QSK выражается hang-time'ом — ZZCD.
|
||
else if ext = 'QK' then ans := '' // STUB: нет отдельного переключателя semi/QSK
|
||
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: галка RTTY-офсета для VFO B — RTTY-движка нет
|
||
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 — расстройки приёма нет
|
||
else if ext = 'RH' then ans := '' // STUB: частота офсета RTTY DIGH — RTTY-движка нет
|
||
else if ext = 'RL' then ans := ZZRL(s)
|
||
else if ext = 'RM' then ans := '' // STUB: показание измерителя приёма — читается через ZZSM
|
||
else if ext = 'RS' then ans := '' // STUB: кнопка RX2 — RX2 нет
|
||
else if ext = 'RT' then ans := '' // STUB: кнопка RIT — расстройки приёма нет
|
||
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: шаговый аттенюатор RX2 — RX2 нет (RX1 — это ZZRX)
|
||
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: шаг настройки вниз колесом — это ZZSA
|
||
else if ext = 'SF' then ans := '' // STUB: фильтр «центр+ширина» одной командой — не проброшено
|
||
else if ext = 'SG' then ans := '' // STUB: VFO B вниз на один шаг — не проброшено (по индексу — ZZBM)
|
||
else if ext = 'SH' then ans := '' // STUB: VFO B вверх на один шаг — не проброшено (по индексу — ZZBP)
|
||
else if ext = 'SM' then ans := ZZSM(s)
|
||
else if ext = 'SN' then ans := '' // STUB: серийный номер радио — не проброшено
|
||
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: без описания у Thetis
|
||
else if ext = 'ST' then ans := ZZST(s)
|
||
else if ext = 'SU' then begin ZZSU; ans := ''; end
|
||
else if ext = 'SV' then ans := '' // STUB: шумоподавитель RX2 — RX2 нет
|
||
else if ext = 'SW' then ans := '' // STUB: обмен кнопок TX VFO A/B — не проброшено (обмен VFO — ZZVS)
|
||
else if ext = 'SX' then ans := '' // STUB: порог шумоподавителя RX2 — RX2 нет
|
||
else if ext = 'SY' then ans := '' // STUB: кнопка VFO Sync — синхронизации VFO нет
|
||
else if ext = 'SZ' then ans := '' // STUB: подгон VFO под шаг настройки — см. ZZSY
|
||
else if ext = 'TA' then ans := ZZTA(s)
|
||
else if ext = 'TB' then ans := ZZTB(s)
|
||
else if ext = 'TF' then ans := '' // STUB: показывать полосу TX на спектре (UI Thetis)
|
||
else if ext = 'TH' then ans := ZZTH(s)
|
||
else if ext = 'TI' then ans := '' // STUB: запрет выхода мощности (внешние антенны/тюнер)
|
||
else if ext = 'TL' then ans := ZZTL(s)
|
||
else if ext = 'TM' then ans := ZZTM(s)
|
||
else if ext = 'TO' then ans := ZZTO(s)
|
||
else if ext = 'TP' then ans := ZZTP(s)
|
||
else if ext = 'TS' then ans := '' // STUB: датчик температуры Flex 5000
|
||
else if ext = 'TU' then ans := ZZTU(s)
|
||
else if ext = 'TV' then ans := '' // STUB: TX-частота при split с RX2
|
||
else if ext = 'TX' then ans := ZZTX(s)
|
||
else if ext = 'UA' then ans := '' // STUB: имена диапазонов трансвертера — не проброшено
|
||
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: усиление передачи в VAC — VAC нет
|
||
else if ext = 'VD' then ans := '' // STUB: частота дискретизации VAC — VAC нет
|
||
else if ext = 'VE' then ans := '' // STUB: кнопка VOX — VOX нет
|
||
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: галка I/Q в VAC — VAC нет
|
||
else if ext = 'VI' then ans := '' // STUB: входной кабель VAC — VAC нет
|
||
else if ext = 'VJ' then ans := '' // STUB: галка Direct I/Q Use RX2 — RX2 нет
|
||
else if ext = 'VK' then ans := '' // STUB: галка VAC2 Enable — VAC нет
|
||
else if ext = 'VL' then ans := '' // STUB: кнопка блокировки VFO — не проброшено (SetVfoLock есть)
|
||
else if ext = 'VM' then ans := '' // STUB: драйвер VAC — VAC нет
|
||
else if ext = 'VN' then ans := ZZVN
|
||
else if ext = 'VO' then ans := '' // STUB: выходной кабель VAC — VAC нет
|
||
else if ext = 'VP' then ans := '' // STUB: галка VAC1 IQ Calibrate — VAC нет
|
||
else if ext = 'VQ' then ans := '' // STUB: драйвер VAC2 — VAC нет
|
||
else if ext = 'VR' then ans := '' // STUB: входной кабель VAC2 — VAC нет
|
||
else if ext = 'VS' then ans := ZZVS(s)
|
||
else if ext = 'VT' then ans := '' // STUB: выходной кабель VAC2 — VAC нет
|
||
else if ext = 'VU' then ans := '' // STUB: частота дискретизации VAC2 — VAC нет
|
||
else if ext = 'VV' then ans := '' // STUB: галка VAC2 Stereo — VAC нет
|
||
else if ext = 'VW' then ans := '' // STUB: без описания у Thetis
|
||
else if ext = 'VX' then ans := '' // STUB: усиление передачи в VAC2 — VAC нет
|
||
else if ext = 'VY' then ans := '' // STUB: размер буфера VAC1 — VAC нет
|
||
else if ext = 'VZ' then ans := '' // STUB: размер буфера VAC1 (второй) — VAC нет
|
||
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: уровень микрофона в микшере F1500 — железо другого вендора
|
||
else if ext = 'WU' then ans := '' // STUB: уровень входа FireWire в микшере F1500 — см. ZZWT
|
||
else if ext = 'WV' then ans := '' // STUB: уровень наушников в микшере F1500 — см. ZZWT
|
||
else if ext = 'WW' then ans := '' // STUB: уровень выхода FlexWire в микшере F1500 — см. ZZWT
|
||
else if ext = 'XA' then ans := ZZXA(s)
|
||
else if ext = 'XC' then ans := ZZXC
|
||
else if ext = 'XD' then ans := '' // STUB: убавить XIT — расстройки передачи нет
|
||
else if ext = 'XF' then ans := ZZXF(s)
|
||
else if ext = 'XH' then ans := '' // STUB: задержка VOX — VOX нет
|
||
else if ext = 'XN' then ans := '' // STUB: составной статус RX1 — не собираем
|
||
else if ext = 'XO' then ans := '' // STUB: составной статус RX2 — RX2 нет
|
||
else if ext = 'XS' then ans := '' // STUB: кнопка XIT — расстройки передачи нет
|
||
else if ext = 'XT' then ans := '' // STUB: кнопка X2TR — такого режима нет
|
||
else if ext = 'XU' then ans := '' // STUB: прибавить XIT — расстройки передачи нет
|
||
else if ext = 'XV' then ans := '' // STUB: составной статус VFO — не собираем
|
||
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: быстрый split вкл/выкл — не проброшено
|
||
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: усиление APF — STUB: аудиопикового фильтра в EWSDR нет.
|
||
// (Громкость — это ZZAG; раньше ZZAA был ошибочно заалиашен на неё.)
|
||
begin
|
||
if Length(s) = 4 then Result := '' else if Length(s) = 0 then Result := '0000'
|
||
else Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZAB(const s: string): string;
|
||
// ZZAB: полоса APF — STUB: аудиопикового фильтра нет.
|
||
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: размер шага настройки консоли (индекс) — STUB: у EWSDR глобального
|
||
// шага нет, колесо/UP/DN ходят фиксированным шагом.
|
||
// (Выбор диапазона — это ZZBS; раньше он был заалиашен сюда.)
|
||
begin
|
||
if Length(s) = 2 then Result := '' else if Length(s) = 0 then Result := '00'
|
||
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: кнопка APF — STUB: аудиопикового фильтра нет.
|
||
// (Мощность — это ZZPC; раньше ZZAP был ошибочно заалиашен на неё.)
|
||
begin
|
||
Result := OnOff(s, nil, nil);
|
||
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
|
||
Result := SignedPad(SafeGetAGCTop, 3);
|
||
end else
|
||
Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZAU(const s: string): string;
|
||
// ZZAU: сдвиг VFO A вверх на один шаг с индексом nn (00..14, см. StepIdxToHz).
|
||
// ★Нечисловое поле — ошибка формата: StrToIntDef молча дал бы индекс 0, то
|
||
// есть «ZZAUxx;» двигал бы VFO вместо честного «?;» (как в ZZFL/ZZFH).
|
||
var idx: Integer;
|
||
begin
|
||
if Length(s) = 2 then begin
|
||
if not TryStrToInt(s, idx) then Exit(CAT_ERROR);
|
||
idx := EnsureRange(idx, 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).
|
||
// Разбор поля — как у ZZAU: нечисловое значит ошибку, а не нулевой индекс.
|
||
var idx: Integer;
|
||
begin
|
||
if Length(s) = 2 then begin
|
||
if not TryStrToInt(s, idx) then Exit(CAT_ERROR);
|
||
idx := EnsureRange(idx, 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: AGC-T второго приёмника — STUB: 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: настройка APF — STUB: аудиопикового фильтра нет.
|
||
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: bandswitch второго приёмника вниз — STUB: 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: VFO B вниз на nn пресетных шагов — STUB: глобального размера шага у
|
||
// EWSDR нет (см. ZZAC). Шаг по индексу — это ZZBM/ZZBP.
|
||
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: кнопка BIN (бинауральный приём) — STUB: такого режима нет.
|
||
// (Индекс фильтра — это ZZFI; раньше он жил здесь.)
|
||
begin
|
||
Result := OnOff(s, nil, nil);
|
||
end;
|
||
|
||
function TCATEngine.ZZBM(const s: string): string;
|
||
// ZZBM: сдвиг VFO B ВНИЗ на один шаг с индексом nn (00..14) — пара к ZZBP.
|
||
// (Режим — это ZZMD; раньше ZZBM был ошибочно заалиашен на него.)
|
||
// Разбор поля — как у ZZAU: нечисловое значит ошибку, а не нулевой индекс.
|
||
var idx: Integer;
|
||
begin
|
||
if Length(s) = 2 then begin
|
||
if not TryStrToInt(s, idx) then Exit(CAT_ERROR);
|
||
idx := EnsureRange(idx, 0, 14);
|
||
if Assigned(FCtx.SetVfoB) then FCtx.SetVfoB(SafeGetVfoB - StepIdxToHz(idx));
|
||
Result := '';
|
||
end else
|
||
Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZBS(const s: string): string;
|
||
// ZZBS: выбор диапазона. ⚠ Отклонение от Thetis: там поле трёхсимвольное и
|
||
// несёт КОД диапазона ("160"/"040"/"WWV"), у нас — двузначный ИНДЕКС в списке
|
||
// диапазонов EWSDR. Менять поздно: на этом формате уже сидят клиенты.
|
||
// ★Нечисловое поле — ошибка формата: StrToIntDef переключал бы на диапазон 0,
|
||
// то есть уводил бы оператора с рабочего бэнда по опечатке клиента.
|
||
var idx: Integer;
|
||
begin
|
||
if Length(s) = 2 then begin
|
||
if not TryStrToInt(s, idx) then Exit(CAT_ERROR);
|
||
idx := EnsureRange(idx, 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.ZZBU: string;
|
||
begin
|
||
if Assigned(FCtx.DoBandUp) then FCtx.DoBandUp;
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZCB(const s: string): string;
|
||
// ZZCB: break-in — прошивка сама поднимает PTT на нажатие ключа.
|
||
begin
|
||
Result := OnOff(s, FCtx.GetCWBreakIn, FCtx.SetCWBreakIn);
|
||
end;
|
||
|
||
function TCATEngine.ZZCD(const s: string): string;
|
||
// ZZCD: задержка отпускания break-in (hang time), мс. Эталон принимает
|
||
// 150..5000, у нас потолок 2000 — это НАШ предел, а не описка: столько же
|
||
// стоит в настройках телеграфа и уходит в поле HangDelay пакета DUC Specific.
|
||
// Значение выше подрезается (как и всё прочее у Thetis), формат — те же 4 цифры.
|
||
begin
|
||
Result := NumField(s, 4, 0, CW_HANG_MAX, FCtx.GetCWHangTime, FCtx.SetCWHangTime);
|
||
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: скорость кейера, WPM (два разряда). Та же величина, что у KS/ZZKS —
|
||
// у Kenwood-команды поле шире, вот и всё различие.
|
||
begin
|
||
Result := NumField(s, 2, CW_WPM_MIN, CW_WPM_MAX, FCtx.GetCWSpeed, FCtx.SetCWSpeed);
|
||
end;
|
||
|
||
function TCATEngine.ZZDA(const s: string): string;
|
||
// ZZDA: усреднение спектра вкл/выкл — STUB: в CAT не проброшено.
|
||
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 — 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: режим дисплея (панадаптер/водопад/…) — STUB: в CAT не проброшено.
|
||
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: нижний уровень водопада, dBm — STUB: уровни водопада живут в per-pan
|
||
// конфиге, в контроллер пока не проброшены (см. §3 doc/CAT_STATUS.md).
|
||
// (Мощность — это ZZPC; раньше ZZDN был ошибочно заалиашен на неё.)
|
||
begin
|
||
if Length(s) = 4 then Result := '' else if Length(s) = 0 then Result := '-120'
|
||
else Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZDU: string;
|
||
// ZZDU: составное слово состояния для DDUtil — 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: значения эквалайзера ПРИЁМА — STUB: RX-эквалайзера нет (TX — ZZEB).
|
||
begin
|
||
if Length(s) > 0 then Result := '' else Result := ''; // no EQ
|
||
end;
|
||
|
||
function TCATEngine.ZZEB(const s: string): string;
|
||
// ZZEB: значения TX-эквалайзера одной строкой (формат Thetis).
|
||
// nnn + (nnn){nb+1}, добито нулями до 36 символов;
|
||
// nb — число полос, следом преамп и полосы, отрицательные как '-nn'.
|
||
var
|
||
G: TCATEQGains;
|
||
nb, i, v: Integer;
|
||
ans, fld: string;
|
||
begin
|
||
if Length(s) = 0 then
|
||
begin
|
||
FillChar(G, SizeOf(G), 0);
|
||
if Assigned(FCtx.GetTXEQ) then nb := FCtx.GetTXEQ(G) else nb := 0;
|
||
nb := EnsureRange(nb, 0, High(G));
|
||
ans := Pad(nb, 3);
|
||
for i := 0 to nb do
|
||
begin
|
||
v := EnsureRange(G[i], EQ_GAIN_MIN, EQ_GAIN_MAX);
|
||
if v < 0 then ans := ans + '-' + Pad(-v, 2)
|
||
else ans := ans + Pad(v, 3);
|
||
end;
|
||
while Length(ans) < 36 do ans := ans + '0';
|
||
Exit(Copy(ans, 1, 36));
|
||
end;
|
||
if Length(s) <> 36 then Exit(CAT_ERROR);
|
||
if not TryStrToInt(Copy(s, 1, 3), nb) then Exit(CAT_ERROR);
|
||
if (nb < 0) or (nb > High(G)) then Exit(CAT_ERROR);
|
||
FillChar(G, SizeOf(G), 0);
|
||
for i := 0 to nb do
|
||
begin
|
||
fld := Copy(s, 4 + i * 3, 3);
|
||
if not TryStrToInt(fld, v) then Exit(CAT_ERROR);
|
||
G[i] := EnsureRange(v, EQ_GAIN_MIN, EQ_GAIN_MAX);
|
||
end;
|
||
if Assigned(FCtx.SetTXEQ) then FCtx.SetTXEQ(nb, G);
|
||
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: девиация передачи — радиокнопка «узкая/широкая» (2.5/5 кГц). Поле в
|
||
// профиле непрерывное, поэтому на чтении делим по середине: всё, что ниже
|
||
// 5 кГц, отвечает узкой.
|
||
begin
|
||
if Length(s) = 0 then
|
||
Exit(B2C(SafeGetFMDeviation >= FM_DEV_WIDE));
|
||
if (s <> '0') and (s <> '1') then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetFMDeviation) then
|
||
begin
|
||
if s = '1' then FCtx.SetFMDeviation(FM_DEV_WIDE)
|
||
else FCtx.SetFMDeviation(FM_DEV_NARROW);
|
||
end;
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZFI(const s: string): string;
|
||
// ZZFI: индекс текущего фильтра (2 цифры).
|
||
// Разбор поля — как у ZZFL/ZZFH: нечисловое значит ошибку, а не фильтр 0.
|
||
var idx: Integer;
|
||
begin
|
||
if Length(s) = 2 then begin
|
||
if not TryStrToInt(s, idx) then Exit(CAT_ERROR);
|
||
idx := EnsureRange(idx, 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.ZZFL(const s: string): string;
|
||
// ZZFL: нижняя кромка фильтра, Гц со знаком — поле фиксированное, ровно пять
|
||
// символов («-0150»). Нечисловой ввод раньше проваливался через StrToIntDef в
|
||
// ноль и МОЛЧА схлопывал кромку; теперь это ошибка формата.
|
||
var v: Integer;
|
||
begin
|
||
if Length(s) = 5 then begin
|
||
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetFilterLow) then FCtx.SetFilterLow(v);
|
||
Result := '';
|
||
end else if Length(s) = 0 then begin
|
||
if Assigned(FCtx.GetFilterLow) then Result := SignedPad(FCtx.GetFilterLow, 4)
|
||
else 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: верхняя кромка фильтра, Гц со знаком — формат и разбор как у ZZFL.
|
||
var v: Integer;
|
||
begin
|
||
if Length(s) = 5 then begin
|
||
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetFilterHigh) then FCtx.SetFilterHigh(v);
|
||
Result := '';
|
||
end else if Length(s) = 0 then begin
|
||
if Assigned(FCtx.GetFilterHigh) then Result := SignedPad(FCtx.GetFilterHigh, 4)
|
||
else 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: частота передачи. Со split'ом передача идёт с VFO B — туда и адресуем,
|
||
// иначе на VFO A. (Раньше команда всегда била в VFO A: split ещё не было.)
|
||
begin
|
||
if SafeGetSplit then Result := ZZFB(s) else Result := ZZFA(s);
|
||
end;
|
||
|
||
function TCATEngine.ZZGA(const s: string): string;
|
||
// ZZGA: регистрация GUID клиента — адресацией ответов ведает слой TCP.
|
||
// 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 клиента — см. ZZGA.
|
||
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.
|
||
begin
|
||
if Length(s) = 0 then Result := CmdIF
|
||
else Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZIO: string;
|
||
// ZZIO: список установленных опций — STUB: опций не заводим.
|
||
begin Result := '0'; end;
|
||
|
||
function TCATEngine.ZZIS(const s: string): string;
|
||
// ZZIS: ширина IF — STUB: полоса задаётся кромками (ZZFL/ZZFH) и пресетами.
|
||
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-макрос номер n (1..9) — у нас это ячейка памяти сообщений
|
||
// (окно CW Messages, F1..F8). Write-only, как в Thetis.
|
||
// Раньше здесь сидел режим кейера — своей команды у него в протоколе нет,
|
||
// иамбик вкл/выкл доступен через ZZCI.
|
||
var v: Integer;
|
||
begin
|
||
if Length(s) <> 1 then Exit(CAT_ERROR);
|
||
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
|
||
if (v < 1) or (v > 9) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SendCWMacro) then FCtx.SendCWMacro(v);
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZKS(const s: string): string;
|
||
// ZZKS: скорость кейера, WPM.
|
||
begin
|
||
Result := CmdKS(s);
|
||
end;
|
||
|
||
function TCATEngine.ZZKY(const s: string): string;
|
||
// ZZKY: передать текст телеграфом (то же, что KY).
|
||
begin
|
||
Result := CmdKY(s);
|
||
end;
|
||
|
||
function TCATEngine.ZZMA(const s: string): string;
|
||
// ZZMA: кнопка MUT (заглушить приём).
|
||
// (Режим — это ZZMD; раньше ZZMA был ошибочно заалиашен на него.)
|
||
begin
|
||
Result := OnOff(s, FCtx.GetMuted, FCtx.SetMuted);
|
||
end;
|
||
|
||
function TCATEngine.ZZMB(const s: string): string;
|
||
// ZZMB: кнопка MUT второго приёмника — STUB: RX2 нет.
|
||
begin
|
||
Result := OnOff(s, nil, nil);
|
||
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: усиление микрофона в дБ. Формат Thetis: SET — три цифры либо знак+две
|
||
// ('+10'/'-05'), GET всегда отдаёт знак+две. Значение уезжает в активный
|
||
// TX-профиль (MicGainDB), поэтому подрезаем под диапазон вкладки Transmit.
|
||
var v: Integer;
|
||
begin
|
||
if Length(s) = 0 then
|
||
Exit(SignedPad(EnsureRange(SafeGetMicGain, MIC_GAIN_MIN, MIC_GAIN_MAX), 2));
|
||
// Три символа (`025`, `+10`, `-05`) либо четыре со знаком (`-050`) — ровно
|
||
// как у Thetis (nSet и nSet+1). Более короткие поля — ошибка формата, а не
|
||
// «маленькое значение»: молча крутить усиление по ним нельзя.
|
||
if (Length(s) < 3) or (Length(s) > 4) then Exit(CAT_ERROR);
|
||
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetMicGain) then
|
||
FCtx.SetMicGain(EnsureRange(v, MIC_GAIN_MIN, MIC_GAIN_MAX));
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZML: string;
|
||
// ZZML: mode list (returns all mode names)
|
||
begin Result := 'LSB USB DSB CWL CWU FM AM SAM DIGUDIGLWFM DMR FMRAW'; end;
|
||
|
||
function TCATEngine.ZZMN(const s: string): string;
|
||
// ZZMN: имя режима по номеру. ⚠ Отклонение от Thetis: там ZZMN отдаёт пресеты
|
||
// DSP-фильтров (180 символов). Имён режимов в протоколе нет вовсе, а список
|
||
// режимов (ZZML) на эту команду опирается — оставляем как расширение EWSDR.
|
||
// Индекс режима двузначный (MODE_MIN..MODE_MAX), но односимвольный запрос
|
||
// продолжаем принимать — старые клиенты шлют одну цифру.
|
||
var k: Integer;
|
||
names: array[0..CAT_MODE_COUNT-1] of string;
|
||
begin
|
||
names[MODE_LSB]:='LSB'; names[MODE_USB]:='USB';
|
||
names[MODE_DSB]:='DSB'; names[MODE_CWL]:='CWL';
|
||
names[MODE_CWU]:='CWU'; names[MODE_FM]:='FM';
|
||
names[MODE_AM]:='AM'; names[MODE_SAM]:='SAM';
|
||
names[MODE_DIGU]:='DIGU'; names[MODE_DIGL]:='DIGL';
|
||
names[MODE_WFM]:='WFM'; names[MODE_DMR]:='DMR';
|
||
names[MODE_FMRAW]:='FM RAW';
|
||
if (Length(s) = 1) or (Length(s) = 2) then begin
|
||
k := EnsureRange(StrToIntDef(s, MODE_MIN), MODE_MIN, MODE_MAX);
|
||
Result := names[k];
|
||
end else Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZMO(const s: string): string;
|
||
// ZZMO: кнопка MON. У EWSDR монитор передачи — это self-monitor даунлинка
|
||
// (RX MUTE снят); слышно его, разумеется, только в дуплексе. Уровень — ZZTM.
|
||
begin
|
||
Result := OnOff(s, FCtx.GetMonitorOn, FCtx.SetMonitorOn);
|
||
end;
|
||
|
||
function TCATEngine.ZZMV: string;
|
||
// ZZMV: число записей в памяти каналов (read-only).
|
||
// (Текущий режим — это ZZMD; раньше ZZMV отдавал его индекс.)
|
||
var n: Integer;
|
||
begin
|
||
if Assigned(FCtx.GetMemoryCount) then n := FCtx.GetMemoryCount else n := 0;
|
||
Result := Pad(n, 3);
|
||
end;
|
||
|
||
function TCATEngine.ZZMY: string;
|
||
// ZZMY: mode Y — STUB
|
||
begin Result := ''; end;
|
||
|
||
function TCATEngine.ZZNA(const s: string): string;
|
||
// ZZNA: кнопка NB1 (у нас режимы NB нумерованы, 1 = первый бланкер).
|
||
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: порог NB1 — STUB: порог бланкера не вынесен в настройки.
|
||
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: спектральный бланкер (SNB) RX1.
|
||
// (Раньше здесь стояла заглушка «ручной нотч», а SNB сидел в ZZNS.)
|
||
begin
|
||
Result := OnOff(s, FCtx.GetSNBEnabled, FCtx.SetSNBEnabled);
|
||
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: кнопка NR2 — у нас шумоподавитель один и режимный, NR2 = режим 2.
|
||
// Включение ставит режим 2, выключение — 0; прочие режимы читаются как '0'.
|
||
// (SNB переехал в ZZNN, где ему и место по протоколу.)
|
||
const NR2 = 2;
|
||
begin
|
||
if Length(s) = 0 then Exit(B2C(SafeGetNRMode = NR2));
|
||
if (s <> '0') and (s <> '1') then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetNRMode) then
|
||
begin
|
||
if s = '1' then FCtx.SetNRMode(NR2)
|
||
else if SafeGetNRMode = NR2 then FCtx.SetNRMode(0);
|
||
end;
|
||
Result := '';
|
||
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 — STUB: порты AD936x в контроллере есть (SetAnt936x), но в
|
||
// CAT не проброшены (см. §3 doc/CAT_STATUS.md).
|
||
// (Громкость — это ZZAG; раньше ZZOA был ошибочно заалиашен на неё.)
|
||
begin
|
||
Result := OnOff(s, nil, nil);
|
||
end;
|
||
|
||
function TCATEngine.ZZOB(const s: string): string;
|
||
// ZZOB: антенна RX2 — STUB: 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: центрировать пан дисплея — STUB: пан/зум в CAT не проброшены.
|
||
// (Раньше команда отдавала «номинальную мощность 100» — выдумка.)
|
||
begin Result := ''; end;
|
||
|
||
function TCATEngine.ZZPO(const s: string): string;
|
||
// ZZPO: кнопка Peak на дисплее — STUB: удержания пика в CAT нет.
|
||
// (Питание/состояние — это ZZPS; раньше ZZPO отвечал за него.)
|
||
begin
|
||
Result := OnOff(s, nil, nil);
|
||
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: частота из быстрой памяти (11 цифр) — STUB: быстрая память не заведена.
|
||
// (Текущий режим — это ZZMD; раньше ZZQM отдавал его.)
|
||
begin Result := Pad(0, 11); 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: галка RTTY-офсета для VFO A — STUB: RTTY-движка нет.
|
||
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: убавить RIT — STUB: расстройки приёма в EWSDR нет.
|
||
// (Шаг настройки вниз — это ZZSA/ZZSD и кенвудовская RD.)
|
||
begin
|
||
if (Length(s) = 0) or (Length(s) = 5) then Result := '' else Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZRL(const s: string): string;
|
||
// ZZRL: частота офсета RTTY DIGL — STUB: RTTY-движка нет.
|
||
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: прибавить RIT — STUB: расстройки приёма в EWSDR нет.
|
||
// (Шаг настройки вверх — это ZZSB/ZZSU и кенвудовская RU.)
|
||
begin
|
||
if (Length(s) = 0) or (Length(s) = 5) then Result := '' else Result := CAT_ERROR;
|
||
end;
|
||
|
||
function TCATEngine.ZZRV: string;
|
||
// ZZRV: напряжение питания PA, формат Thetis «NN.N» (4 символа).
|
||
// (Версия ПО — это ZZVN; раньше ZZRV отдавал её.)
|
||
var v: Double;
|
||
begin
|
||
if Assigned(FCtx.GetSupplyVolts) then v := FCtx.GetSupplyVolts else v := -1;
|
||
if v < 0 then v := 0; // телеметрии нет — отвечаем нулём, как Thetis
|
||
if v > 99.9 then v := 99.9;
|
||
Result := Format('%04.1f', [v]);
|
||
end;
|
||
|
||
function TCATEngine.ZZRX(const s: string): string;
|
||
// ZZRX: шаговый аттенюатор RX1, дБ (00..31).
|
||
// (Переход на приём — это ZZTX0 и кенвудовская RX; раньше ZZRX делал его.)
|
||
begin
|
||
Result := NumField(s, 2, 0, 31, FCtx.GetAtten, FCtx.SetAtten);
|
||
end;
|
||
|
||
function TCATEngine.ZZSA: string;
|
||
// ZZSA: шаг настройки вниз. У Thetis шагает именно VFO A, у нас — активный VFO
|
||
// (отдельного «шага VFO A» в контроллере нет).
|
||
begin
|
||
if Assigned(FCtx.DoTuneDown) then FCtx.DoTuneDown;
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZSB: string;
|
||
// ZZSB: шаг настройки вверх — см. оговорку в ZZSA.
|
||
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: кнопка Spur Reduction — STUB: такого блока в тракте нет.
|
||
// (Опорный уровень спектра — это ZZDP; раньше ZZSR отвечал за него.)
|
||
begin
|
||
Result := OnOff(s, nil, nil);
|
||
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 вкл/выкл для FM-передачи. Значение строго 0/1 — иначе «CT9;»
|
||
// молча выключал бы тон вместо ответа об ошибке.
|
||
begin
|
||
Result := OnOff(s, FCtx.GetCTCSSOn, FCtx.SetCTCSSOn);
|
||
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: кнопка MOX — только 0 (приём) и 1 (передача). Никакого «2=TUNE» в
|
||
// протоколе нет (настройка — это ZZTU): на любом другом значении отвечаем
|
||
// ошибкой и НЕ поднимаем передачу.
|
||
begin
|
||
Result := OnOff(s, FCtx.GetTransmitting, FCtx.SetTransmitting);
|
||
end;
|
||
|
||
function TCATEngine.ZZUP(const s: string): string;
|
||
// ZZUP: внешний PA вкл/выкл — STUB: управления внешним усилителем нет.
|
||
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: запустить одиночную калибровку PureSignal (write-only).
|
||
begin
|
||
if Assigned(FCtx.DoPSSingleCal) then FCtx.DoPSSingleCal;
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZUT(const s: string): string;
|
||
// ZZUT: двухтональник (2TON).
|
||
begin
|
||
Result := OnOff(s, FCtx.GetTwoTone, FCtx.SetTwoTone);
|
||
end;
|
||
|
||
function TCATEngine.ZZUX(const s: string): string;
|
||
// ZZUX: блокировка VFO A — STUB: у нас лок общий (SetVfoLock), раздельного нет.
|
||
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 — STUB: см. ZZUX.
|
||
begin Result := ZZUX(s); end;
|
||
|
||
function TCATEngine.ZZVA(const s: string): string;
|
||
// ZZVA: включение VAC — STUB: виртуального аудиокабеля в EWSDR нет.
|
||
// (Обмен VFO — это ZZVS; раньше он жил здесь.)
|
||
begin
|
||
Result := OnOff(s, nil, nil);
|
||
end;
|
||
|
||
function TCATEngine.ZZVB(const s: string): string;
|
||
// ZZVB: усиление приёма в VAC — STUB: виртуального аудиокабеля нет.
|
||
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: усиление VOX — STUB: VOX'а нет.
|
||
// (Громкость — это ZZAG; раньше ZZVG был ошибочно заалиашен на неё.)
|
||
begin
|
||
if Length(s) = 4 then Result := '' else if Length(s) = 0 then Result := '0000'
|
||
else Result := CAT_ERROR;
|
||
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 (write-only). Аргумент — НОМЕР операции, а не
|
||
// флаг: 0 = A→B, 1 = B→A, 2 = обмен, 3 = IF→VFO.
|
||
// (Split — это ZZSP; раньше ZZVS был заалиашен на него.)
|
||
begin
|
||
if Length(s) <> 1 then Exit(CAT_ERROR);
|
||
case s[1] of
|
||
'0': if Assigned(FCtx.SetVfoB) then FCtx.SetVfoB(SafeGetVfoA);
|
||
'1': if Assigned(FCtx.SetVfoA) then FCtx.SetVfoA(SafeGetVfoB);
|
||
'2': if Assigned(FCtx.DoVfoSwap) then FCtx.DoVfoSwap;
|
||
// IF→VFO переносит расстройку приёма в частоту VFO. Расстройки у нас нет,
|
||
// переносить нечего — команду принимаем и ничего не делаем.
|
||
'3': ;
|
||
else
|
||
Exit(CAT_ERROR);
|
||
end;
|
||
Result := '';
|
||
end;
|
||
|
||
// ZZWA..ZZWS: микшер Flex 5000 / FLEX-1500 (уровни и галки входов/выходов) —
|
||
// STUB: железо другого вендора, аналога нет. Водопад тут ни при чём: его
|
||
// уровни живут в ZZDN/ZZDO.
|
||
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;
|
||
|
||
// ZZXA: включение аудиоусилителя G2; ZZXC/ZZXF: сброс и значение XIT —
|
||
// STUB: усилителя не касаемся, расстройки передачи (XIT) в EWSDR нет.
|
||
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;
|
||
|
||
// ZZYA/ZZYB: галки Direct I/Q и IQ Calibrate для VAC2 — STUB: VAC нет.
|
||
// ZZYC: отдельное усиление микрофона в FM — STUB: у нас оно одно на все режимы
|
||
// (ZZMG). ZZYR: переключатель RX1/RX2 — STUB: RX2 нет.
|
||
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;
|
||
|
||
// ZZZA: трип-состояние усилителя Ganymede; ZZZB, ZZZQ, ZZZR: внутренние
|
||
// служебные Thetis; ZZZM/ZZZV: строки модели и версии железа; ZZZT: зум на
|
||
// диапазон; ZZZW: обмен «колёс» VFO в midi2cat; ZZZO: быстрый split.
|
||
// Всё — STUB: либо чужое железо, либо в CAT не проброшено.
|
||
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: без описания у Thetis — STUB.
|
||
begin Result := ''; end;
|
||
function TCATEngine.ZZZO(const s: string): string;
|
||
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
|
||
|
||
{ ── TX-тракт и телеграф ──────────────────────────────────────────────────── }
|
||
|
||
function TCATEngine.ZZCI(const s: string): string;
|
||
// ZZCI: иамбический манипулятор вкл/выкл. У нас режим кейера трёхпозиционный
|
||
// (прямой ключ / iambic A / iambic B) — включение выбирает B, как в Thetis по
|
||
// умолчанию, а если иамбик уже стоит, ранее выбранная буква сохраняется.
|
||
const KEYER_STRAIGHT = 0; KEYER_IAMBIC_B = 2;
|
||
begin
|
||
if Length(s) = 0 then Exit(B2C(SafeGetCWKeyerMode <> KEYER_STRAIGHT));
|
||
if (s <> '0') and (s <> '1') then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetCWKeyerMode) then
|
||
begin
|
||
if s = '0' then FCtx.SetCWKeyerMode(KEYER_STRAIGHT)
|
||
else if SafeGetCWKeyerMode = KEYER_STRAIGHT then FCtx.SetCWKeyerMode(KEYER_IAMBIC_B);
|
||
end;
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZCL(const s: string): string;
|
||
// ZZCL: тон телеграфа (он же сайдтон), Гц.
|
||
begin
|
||
Result := NumField(s, 4, CW_PITCH_MIN, CW_PITCH_MAX, FCtx.GetCWPitch, FCtx.SetCWPitch);
|
||
end;
|
||
|
||
function TCATEngine.ZZCM(const s: string): string;
|
||
// ZZCM: у Thetis это «CW Monitor DISABLE» — значение инвертировано к сайдтону.
|
||
begin
|
||
if Length(s) = 0 then Exit(B2C(not SafeGetBool(FCtx.GetCWMonitor)));
|
||
if (s <> '0') and (s <> '1') then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetCWMonitor) then FCtx.SetCWMonitor(s = '0');
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZET(const s: string): string;
|
||
// ZZET: кнопка TXEQ.
|
||
begin
|
||
Result := OnOff(s, FCtx.GetTXEQOn, FCtx.SetTXEQOn);
|
||
end;
|
||
|
||
function TCATEngine.ZZLI(const s: string): string;
|
||
// ZZLI: кнопка PS-A (автокалибровка PureSignal).
|
||
begin
|
||
Result := OnOff(s, FCtx.GetPureSignal, FCtx.SetPureSignal);
|
||
end;
|
||
|
||
function TCATEngine.ZZPK(const s: string): string;
|
||
// ZZPK: речевой компрессор (WDSP COMP) вкл/выкл. Пара к ZZPL.
|
||
begin
|
||
Result := OnOff(s, FCtx.GetCompOn, FCtx.SetCompOn);
|
||
end;
|
||
|
||
function TCATEngine.ZZPL(const s: string): string;
|
||
// ZZPL: усиление речевого компрессора, дБ.
|
||
begin
|
||
Result := NumField(s, 2, 0, COMP_GAIN_MAX, FCtx.GetCompGain, FCtx.SetCompGain);
|
||
end;
|
||
|
||
function TCATEngine.ZZTH(const s: string): string;
|
||
// ZZTH: верхняя кромка TX-фильтра, Гц.
|
||
begin
|
||
Result := NumField(s, 5, TX_FILT_HI_MIN, TX_FILT_HI_MAX,
|
||
FCtx.GetTXFilterHigh, FCtx.SetTXFilterHigh);
|
||
end;
|
||
|
||
function TCATEngine.ZZTL(const s: string): string;
|
||
// ZZTL: нижняя кромка TX-фильтра, Гц.
|
||
begin
|
||
Result := NumField(s, 4, TX_FILT_LO_MIN, TX_FILT_LO_MAX,
|
||
FCtx.GetTXFilterLow, FCtx.SetTXFilterLow);
|
||
end;
|
||
|
||
function TCATEngine.ZZTM(const s: string): string;
|
||
// ZZTM: громкость монитора передачи, 0..100.
|
||
begin
|
||
Result := NumField(s, 3, 0, 100, FCtx.GetMonitorLevel, FCtx.SetMonitorLevel);
|
||
end;
|
||
|
||
function TCATEngine.ZZTO(const s: string): string;
|
||
// ZZTO: мощность настройки (уровень TUN), % от полной шкалы Drive.
|
||
begin
|
||
Result := NumField(s, 3, 0, 100, FCtx.GetTUNLevel, FCtx.SetTUNLevel);
|
||
end;
|
||
|
||
function TCATEngine.ZZTP(const s: string): string;
|
||
// ZZTP: активный TX-профиль по индексу. Индекса вне списка нет — это ошибка
|
||
// выполнения, а не подрезка: клиент выбирает из того, что ему отдал ZZTP;.
|
||
var v, Cnt: Integer;
|
||
begin
|
||
if Length(s) = 0 then Exit(Pad(Max(0, SafeGetTXProfile), 2));
|
||
if Length(s) <> 2 then Exit(CAT_ERROR);
|
||
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.GetTXProfileCount) then Cnt := FCtx.GetTXProfileCount else Cnt := 0;
|
||
if (v < 0) or (v >= Cnt) then Exit(CAT_ERROR);
|
||
if Assigned(FCtx.SetTXProfile) then FCtx.SetTXProfile(v);
|
||
Result := '';
|
||
end;
|
||
|
||
function TCATEngine.ZZTU(const s: string): string;
|
||
// ZZTU: кнопка TUN.
|
||
begin
|
||
Result := OnOff(s, FCtx.GetTuning, FCtx.SetTuning);
|
||
end;
|
||
|
||
end.
|