Files
ewsdr/doc/CAT_STATUS.md
T
ew8bakandClaude Opus 5 4081ea0e57 fix(cat): нечисловое поле = индекс 0 ещё в семнадцати командах, ZZGT и ZZBE
Всё найдено новым стендом (test/cat), все правки им же и закрыты.

Нечисловое поле. Правку «TryStrToInt вместо StrToIntDef(s, 0)» однажды получили
пять команд (ZZAU, ZZBP, ZZBM, ZZBS, ZZFI), а остальные остались как были — в
том числе КЕНВУДОВСКИЕ ДВОЙНИКИ тех же самых величин, ходящие в те же сеттеры:
FWxxxx;, SHxx; и SLxx; ставили фильтр 0 (тот же индекс, что чинили у ZZFI),
AG0xxx; и SQ0xxx; — громкость и порог шумоподавителя в ноль, GTxxx; — АРУ в
FAST, PCxxx; и ZZPCxxx; — мощность в ноль. Всего семнадцать команд: CN, FW, GT,
NB, PC, SH/SL, AG, SQ и ZZAG, ZZAR, ZZNA, ZZNB, ZZNR, ZZPC, ZZSQ, ZZST, ZZTB.
Разбор везде приведён к идиоме ZZFL/ZZFH: не число — ошибка формата.

Не тронуты три места, где мягкий разбор безвреден или намеренный: FR сам
сверяет поле с '0'/'1' до преобразования; MD и ZZMD от нечислового получают 0, а
установка идёт от 1, то есть ничего не делают; ZZOS трактует мусор как симплекс
по эталону (default в String2OffsetDirection), и клиенты на это рассчитывают.

ZZGT. Опрос отвечал тремя цифрами (как кенвудовская GT), а установка принимала
ровно один символ: клиент, прочитавший «ZZGT000;» и написавший его назад,
получал «?;» — а читать значение и писать его обратно умеет любой логгер.
Принимаются обе ширины, поле разбирается строго.

ZZBE. Формы были перевёрнуты: опрос «ZZBE;» отвечал «?;», а установка
«ZZBE01;» возвращала данные ('1'), причём саму установку никто не исполнял.
Вся семья «сдвиг VFO на nn шагов» (ZZAD ZZAE ZZAF ZZBF ZZSG ZZSH) — однострочные
заглушки в таблице, и документ числит ZZBE среди нереализованных; приведено к
ним.

ZZEB. Выдача клампится туда же, куда и приём: сетка эквалайзера бывает только
трёх- или десятиполосной. Иначе опрос отдавал число полос, которое разбор той же
команды отвергал.

Стенд: 50 проверок, все зелёные; на коде до этого коммита падают 19.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:21:38 +03:00

398 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CAT-система EWSDR — статус реализации
Ветка разработки: `feature/cat-tx-cw`.
Дата последнего обновления: 2026-08-17.
Документ описывает текущее состояние CAT-подсистемы EWSDR: архитектуру,
полный перечень реализованных команд, осознанные заглушки и план дальнейших
работ. За эталон протокола взят **Thetis / PowerSDR** (`CATParser.cs`,
`CATCommands.cs`, `CATStructs.xml`) — диалект Kenwood TS-2000 + расширения `ZZ*`.
---
## 1. Архитектура
```
CAT-транспорты ──► TCATEngine (парсер протокола) ──► TCATContext ──► TCATAdapter ──► TRadioController
serial (4×) CATEngine.pas (callbacks) CATAdapter.pas (ядро)
TCP-сервер разбор/формат команд мост, маршалинг
```
| Файл | Назначение |
|---|---|
| `CATEngine.pas` (~2740 строк) | Движок протокола: разбор `<PREFIX><SUFFIX>;`, формирование ответов, реализации всех команд Kenwood + `ZZ*`. Зависит только от RTL. |
| `CATAdapter.pas` (~910 строк) | Мост к `TRadioController`. Строит `TCATContext`: геттеры читают поля контроллера напрямую (CAT-поток, read-only), сеттеры/команды маршалятся в поток контроллера через `FController.Invoke`. Владеет движком + транспортами. |
| `CATSerial.pas` (~330 строк) | Менеджер до 4 последовательных портов (виртуальные COM-пары для rigctld/N1MM/WSJT-X). |
| `CATTcp.pas` (~507 строк) | TCP-сервер (Thetis-совместимый), управление клиентами по GUID (`ZZGA`/`ZZGR`). |
### 1.1 Принципы
- **CAT — равноправный клиент контроллера** наряду с `WebAdapter` и GUI. Никаких
обращений к `MainForm`. Только `TRadioController`.
- **Потокобезопасность.** Геттеры читают поля контроллера напрямую (атомарное
чтение в CAT-потоке). Сеттеры пишут параметр в scratch-поля
(`FSyncFreq/FSyncInt/FSyncBool`) и зовут `FController.Invoke(@SyncXxx)`
GUI исполняет через `TThread.Synchronize`, демон — своей очередью.
- **`TCATContext`** — плоская запись callback'ов (`Get*`/`Set*`/`Do*`). Чтобы
пробросить новую возможность контроллера в CAT, достаточно: (1) добавить
callback в запись, (2) реализовать в адаптере геттер/сеттер/sync, (3)
зарегистрировать в `TCATAdapter.Create`, (4) переписать тело команды в движке.
- **Формат команд:**
```
SET: <PREFIX><SUFFIX>; (suffix = значение)
GET: <PREFIX>; (suffix пустой)
Resp: <PREFIX><VALUE>;
Err: ?; (неверный формат) | E; (ошибка выполнения)
```
Расширенные `ZZ*` дублируют Kenwood-команды и добавляют то, чего в Kenwood нет.
### 1.2 Статистика покрытия (ZZ-команды)
| | Кол-во |
|---|---|
| Всего `ZZ*` в Thetis | 315 |
| Реально реализовано в EWSDR | **~120** |
| Заглушки (отвечают валидным dummy, функции нет) | ~190 |
| Совсем не обрабатываются (вернут `?;`) | 5 |
Подписи всех 315 команд сверены с `CATCommands.cs` (см. §2.5) — комментарию у
заглушки теперь можно верить. Оставшиеся заглушки в большинстве закрыть
**нечем**: в EWSDR физически отсутствует соответствующий тракт (см. §4).
### 1.3 Осознанные отклонения от Thetis
Три места, где EWSDR намеренно расходится с эталоном. Больше таких нет.
| Команда | У Thetis | У нас | Почему |
|---|---|---|---|
| `ZZBS` | код диапазона, 3 символа (`160`/`040`/`WWV`) | индекс диапазона, 2 цифры | формат заложен давно, на нём уже сидят клиенты |
| `ZZMN` | пресеты DSP-фильтров, 180 символов | имя режима по номеру | имён режимов в протоколе нет вовсе, а `ZZML` опирается на эту команду |
| `ZZST` | размер шага настройки (read-only) | индекс шага FM (00..03) | глобального шага у EWSDR нет, слот иначе мёртв; у шага FM своей команды в протоколе нет |
Плюс одно ограничение диапазона, не формата: **`ZZCD`** (hang-time break-in)
эталон принимает в 150..5000 мс, у нас потолок 2000 — столько же стоит в
настройках телеграфа и уходит в поле `HangDelay` пакета DUC Specific. Значения
выше подрезаются, как и всё прочее у Thetis.
---
## 2. Что сделано
### 2.1 Реализованные команды Kenwood (одиночный префикс)
| Команда | Функция | Маппинг на контроллер |
|---|---|---|
| `FA` / `FB` | VFO A / B частота (11 цифр, Гц) | `Set/GetVfoA`, `Set/GetVfoB` |
| `FR` | приёмный VFO (0/1) | `Set/GetActiveVfo` |
| `FT` | передающий VFO / split | → `ZZSP` |
| `FW` / `SH` / `SL` | ширина фильтра (индекс пресета) | `Set/GetFilterIdx` |
| `MD` | режим (Kenwood-код) | `Set/GetMode` |
| `GT` | режим AGC (0=FAST…4=OFF) | `Set/GetAGCMode` |
| `AG` | громкость AF | `Set/GetVolume` |
| `PC` | мощность TX 0100 % | `Set/GetDriveLevel` |
| `NB` | шумоподавитель (NB) | `Set/GetNBMode` |
| `NT` | автонотч (ANF) | `Set/GetANFEnabled` |
| `SM` | S-метр (шкала Kenwood 00000030) | `GetSMeter` |
| `IF` | составной статус (37 байт) | freq/mode/tx/split |
| `ID` | идентификатор (TS-2000 = 019) | const |
| `PS` | питание/состояние | `GetRunning` |
| `TX` / `RX` | передача / приём | `SetTransmitting` |
| `BU` / `BD` | смена диапазона ±1 | `DoBandUp/Down` |
| `UP` / `DN` / `RU` / `RD` | шаг настройки ±1 | `DoTuneUp/Down` |
| `SQ` | шумоподавитель FM (шкала 0–255) | `Set/GetSquelchLevel`+`On` |
| `CN` / `CT` | CTCSS тон / вкл | `Set/GetCTCSSTone`, `…On` |
| `OF` / `OS` | репитер: offset / направление | `Set/GetFMRptOffset`, `…Dir` |
### 2.2 Реализованные расширенные `ZZ*` (выборочно, наиболее значимые)
VFO/режим/фильтр: `ZZFA ZZFB ZZFI ZZFL ZZFH ZZFM ZZMD ZZFT ZZSP ZZVS`,
диапазоны: `ZZBS ZZBU ZZBD`,
AGC/громкость/мощность/аттенюатор: `ZZGT ZZAG ZZMA ZZPC ZZAR ZZRX`,
шумодав/нотч: `ZZNA ZZNB ZZNR ZZNS ZZNT ZZNN`,
FM: `ZZSQ ZZSO ZZTA ZZTB ZZST`, CTUN: `ZZCN`,
S-метр/телеметрия: `ZZSM ZZRV`, память: `ZZMV`,
шаги VFO: `ZZAU ZZBP ZZBM ZZSU ZZSA ZZSB`,
передача: `ZZTX`, сервис: `ZZID ZZGA ZZGR`.
### 2.3 Сделано в ветке `feature/cat-commands-expansion`
Все команды ниже **были заглушками** и стали рабочими — функционал в
`TRadioController` уже существовал, но не был проброшен в CAT.
**Группа 1 — Split TX**
- `ZZSP`, `FT` — split (TX на VFO-B), `FR` — выбор приёмного VFO,
split-байт в составном `IF`.
- В `TRadioController` добавлен публичный метод `SetSplit(TxOnB)`
(флаг `FSplitTxB` + `PushNetworkState` + `Changed(rfActiveVfo)`).
TX-частоту радио берёт из `ActiveTXFreqHz` (учитывает split).
**Группа 2 — FM Squelch**
- `SQ` (шкала Kenwood 0255), `ZZSQ` (0100 %), `ZZSO` (вкл/выкл).
- Маппинг: `SetFMSquelch` / `SetFMSquelchLevel`. Уровень>0 включает
шумоподавитель.
**Группа 3 — FM CTCSS**
- `CT`/`ZZTA` (вкл/выкл), `CN` (1-based тон 0138), `ZZTB` (0-based 0037).
- Маппинг: `SetFMCTCSS` / `SetFMCTCSSTone`, таблица `CTCSS_TONES[38]`.
**Группа 4 — FM-репитер**
- `OF` (offset, 9 цифр Гц), `OS` (направление).
- Трансляция направлений Kenwood (0=simplex,1=+,2=) ↔ внутренние
`RPT_NONE/MINUS/PLUS`. Маппинг: `SetFMRpt` / `SetFMRptOffset`.
**Группа 5 — FM tuning step**
- `ZZST` (индекс 0003: 6.25/12.5/20/25 кГц). Маппинг: `SetFMStep`.
**Доп. команды (вторая итерация)**
| Команда | Что | Примечание |
|---|---|---|
| `ZZSO` | Squelch on/off | завершает группу squelch |
| `ZZNT` | ANF on/off | раньше ошибочно был алиасом NB |
| `ZZCN` | CTUN (click-tune) RX1 | новый callback `Get/SetCTun` → `SetCTun` |
| `ZZAR` | RX1 AGC-T / RF gain (`±nnn`) | новый callback `Get/SetAGCTop` → `SetAGCTop` (кламп 20120 dB) |
| `ZZAU`/`ZZBP` | сдвиг VFO A/B вверх на шаг `nn` (00–14) | **исправлен баг**: были заалиашены на `ZZFA`/`ZZAP` |
| `ZZSU` | шаг активного VFO вверх | `DoTuneUp` |
Шкала шагов `ZZAU/ZZBP` — `StepIdxToHz` (зеркало Thetis `Step2Freq`):
1 Гц, 10, 25, 50, 100, 250, 500, 1к, 5к, 9к, 10к, 100к, 250к, 500к, 1 МГц.
**Новые callback'и в `TCATContext`** (этой ветки): split, squelch on/level,
CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
### 2.4 Сделано в ветке `feature/cat-tx-cw`
К этому моменту в EWSDR появились TX-профили со всей «звуковой» цепью и
телеграф (кейер, сайдтон, break-in) — команды, которые §4 когда-то объявил
незакрываемыми, стали рабочими.
**TX-тракт** (правки уезжают в активный TX-профиль):
| Команда | Что | Маппинг |
|---|---|---|
| `MG` / `ZZMG` | усиление микрофона | `MicGainDB`; `MG` — шкала Kenwood 100:70, `ZZMG` — дБ со знаком |
| `MO` / `ZZMO` | кнопка MON | self-monitor даунлинка: `SetRxMuteOnTx` (инверсия) |
| `ZZTM` | громкость монитора | `SetTXMonVolume` (новый метод контроллера) |
| `ZZTL` / `ZZTH` | кромки TX-фильтра, Гц | `FilterLow` / `FilterHigh` |
| `PR` / `ZZPK` | речевой компрессор вкл/выкл | `CompressorOn` |
| `ZZPL` | усиление компрессора, дБ | `CompressorGain` (0..20) |
| `ZZET` | кнопка TXEQ | `EQOn` |
| `ZZEB` | значения TX-эквалайзера | `EQNumBands` + `EQGains` (формат Thetis, 36 символов). Полос бывает только 3 или 10 — движок (`PushTXEQProfile`) и редактор знают ровно эти два случая, остальное отвергается и не выдаётся |
| `ZZTO` | мощность настройки | `TUNLevel` |
| `ZZTU` | кнопка TUN | `SetTune` |
| `ZZUT` | двухтональник 2TON | `SetTwoTone` |
| `ZZLI` | кнопка PS-A | `SetPureSignal` |
| `ZZUS` | одиночная калибровка PS | `PSSingleCal` (write-only) |
| `ZZTP` | активный TX-профиль | `SelectTXProfile` / `TXProfileCount` |
| `ZZFD` | девиация передачи (2.5/5 кГц) | `TTXSettings.FMDeviation` |
**Телеграф** (раньше были проброшены только `KS`/`KY`/`ZZKM`/`ZZKS`/`ZZKY`):
| Команда | Что | Маппинг (`TCWSettings`) |
|---|---|---|
| `ZZCS` | скорость кейера, WPM (2 разряда) | `.Speed` — та же величина, что у `KS` |
| `ZZCL` | тон приёма/сайдтона, Гц | `.Pitch` |
| `ZZCI` | иамбик вкл/выкл | `.KeyerMode` (вкл выбирает iambic B, если стоял прямой ключ) |
| `ZZCB` | break-in прошивки | `.BreakIn` |
| `ZZCD` | hang-time break-in, мс | `.HangTimeMS` |
| `ZZCM` | сайдтон (у Thetis — «monitor **disable**», значение инвертировано) | `.SidetoneSW` или `.SidetoneHW` — тот, что отвечает за текущий источник манипуляции |
**Новое в контроллере.** Правка TX-настроек получила одну публичную дверь —
`SetTXSettings(const T; Notify)`, по образцу `SetCWSettings`: применение к WDSP,
перепосыл DUC Specific, живой TUN, запись в активный профиль и персист теперь
там, а не в обработчике MainForm (тот делегирует с `Notify=False`, иначе
`rfTXProfile` перезагрузил бы вкладку Transmit под руками у правящего).
Добавлен `SetTXMonVolume` — громкость self-monitor'а адресно, вне TX-контекста.
**Новое в движке.** Хелперы `OnOff` и `NumField` — разбор двухпозиционных и
числовых полей в одном месте, чтобы тела команд остались про смысл. Границы
значений (`MIC_GAIN_MIN`… в шапке юнита) повторяют контролы вкладок Transmit и
CW: по CAT прилетает что угодно, а контроллер принимает запись целиком и уже не
знает, кто её правил.
> Команды слайсов (`CATSliceEndpoint`) этих callback'ов не получают: TX-тракт
> один на радио, а профиль слайса переключается своим путём
> (`SetSliceTXProfile`). Все новые команды nil-безопасны и на слайс-порту
> отвечают дефолтом.
### 2.5 Ревизия алиасов и подписей
Подписи писались по буквам кода, а не по Thetis, и врали примерно в 150
местах. Хуже: часть **работающих** команд была привязана не к своей функции —
внешний софт получал осмысленный, но неверный ответ, что хуже честной
заглушки. Всё сверено с `CATCommands.cs` и исправлено.
**Команда делала не своё дело — теперь делает своё:**
| Команда | Было | Стало |
|---|---|---|
| `ZZMA` | режим (алиас `ZZMD`) | кнопка **MUT** → `SetMute` |
| `ZZRX` | переход на приём | **шаговый аттенюатор RX1** 0..31 дБ → `SetAtten` |
| `ZZRV` | версия ПО | **напряжение питания** PA (телеметрия) |
| `ZZMV` | индекс текущего режима | **число записей** памяти каналов |
| `ZZNN` | заглушка «ручной нотч» | **SNB** (спектральный бланкер) |
| `ZZNS` | SNB | кнопка **NR2** (режим NR 2) |
| `ZZVS` | алиас `ZZSP` (split) | **операции с парой VFO**: `0`=A→B, `1`=B→A, `2`=обмен, `3`=IF→VFO |
| `ZZBM` | режим (алиас `ZZMD`) | **VFO B вниз** на nn шагов (пара к `ZZBP`) |
| `ZZFI` | алиас `ZZBI` | сам **индекс фильтра** |
| `ZZBS` | алиас `ZZAC` | сам **выбор диапазона** |
| `ZZKM` | режим кейера | запуск **CW-макроса** (ячейка памяти сообщений) |
| `ZZFT` | всегда VFO A | **TX-частота** с учётом split |
| `ZZTX` | любое значение ≠ `0` поднимало передачу («2=TUNE» — выдумка) | строго `0`/`1`, иначе `?;` и передача НЕ включается (настройка — `ZZTU`) |
**Ошибочные алиасы сняты — теперь честные заглушки** (в скобках — где эта
функция живёт на самом деле): `ZZAA`, `ZZVG` (громкость — `ZZAG`) · `ZZOA`
(антенна RX1) · `ZZAP` (мощность — `ZZPC`) · `ZZDN` (мощность — `ZZPC`; сам
`ZZDN` = низ водопада) · `ZZAC` (диапазон — `ZZBS`) · `ZZBI` (фильтр — `ZZFI`)
· `ZZMB` (режим — `ZZMD`) · `ZZVA` (обмен VFO — `ZZVS`) · ★`ZZVB` (усиление приёма в VAC; **опрос** копировал VFO A в VFO B, то есть терял сплит оператора, — тело переписали позже комментария) · `ZZPD`, `ZZPO`
(мощность и питание — `ZZPC`/`ZZPS`) · `ZZQM` (режим — `ZZMD`) · `ZZSR`
(опорный уровень спектра) · `ZZRD`/`ZZRU` (RIT; шаг настройки — `ZZSA`/`ZZSB`).
> Что заметят внешние клиенты: `ZZRX;` больше не уводит в приём (для этого есть
> `ZZTX0;` и кенвудовская `RX;`), `ZZVA;` больше не меняет VFO местами (теперь
> `ZZVS0;`), `ZZKM` не выставляет режим кейера (иамбик вкл/выкл — `ZZCI`),
> `RD`/`RU` больше не перестраивают VFO (шаг — `UP`/`DN`).
> Во всех случаях старое поведение было отсебятиной.
**Кенвудовские команды.** Сверены отдельно: все 40 команд, реализованных в
Thetis, у нас есть, ширины полей совпадают с `CATStructs.xml`. Исправлено:
| Команда | Что было не так |
|---|---|
| `SM` | ответ был 4 цифры вместо **5** (`SM0015;` → `SM00019;`) — парсеры логгеров ждут пять; заодно принимаем селектор `2`, как шлёт Thetis |
| `RD` / `RU` | перестраивали VFO, хотя это **RIT** (его у нас нет) — стали заглушками, принимающими 5-значный аргумент |
| `KY` | поле текста у Kenwood фиксированной ширины и добито пробелами — хвост уходил в эфир словесными паузами, теперь срезается |
| `IF` | в комментарии значилось «37 байт», реальная и правильная длина ответа — **35** |
| `SH` / `SL` | обе крутят один и тот же индекс пресета; раздельных «сторон» у главного приёмника нет — оговорка добавлена в код |
| `CT` | принимала любой символ: `CT9;` молча ГАСИЛ тон вместо ответа `?;`. Теперь обёртка над `ZZTA`, как в эталоне, и сама `ZZTA` проверяет значение строго |
| `OF` / `OS` | несли реализацию сами, а `ZZOT`/`ZZOS` были заглушками — клиент Thetis обращается как раз к `ZZ*` и не получал ничего. Реализация переехала в `ZZOT`/`ZZOS`, кенвудовские стали обёртками. Заодно `OF` с нечисловым полем больше не обнуляет сдвиг молча |
**Разбор и транспорты.** Отдельный проход по валидации и вводу-выводу:
| Где | Что было не так |
|---|---|
| `TCATEngine.Parse` | команды без параметров не проверяли суффикс: `TXanything;` доходил до `CmdTX` и **поднимал передачу**; так же вели себя `RX UP DN BD BU QI RC ID IF`. Эталон отбраковывает лишний суффикс в парсере, по таблице ширин; у нас таблицы нет — список безаргументных команд теперь в `IsParamless` |
| `ZZFL` / `ZZFH` | принимали поле любой длины от 4 символов и гнали его через `StrToIntDef`: `ZZFLabcd;` молча схлопывал кромку в ноль. Поле фиксированное — ровно 5 символов со знаком, разбор строгий |
| `ZZAU` `ZZBP` `ZZBM` `ZZBS` `ZZFI` | длину поля проверяли, а содержимое — нет: `StrToIntDef(s, 0)` превращал любую нечисловую пару символов в индекс 0. То есть `ZZBSxx;` **переключал диапазон** на нулевой вместо `?;`, `ZZBMxx;` и `ZZAUxx;`/`ZZBPxx;` двигали VFO, а `ZZFIxx;` выбирал фильтр 0. Разбор приведён к идиоме `ZZFL`/`ZZFH`: `TryStrToInt`, иначе ошибка формата |
| `CN` `FW` `GT` `NB` `PC` `SH`/`SL` `AG` `SQ`, `ZZAG` `ZZAR` `ZZNA` `ZZNB` `ZZNR` `ZZPC` `ZZSQ` `ZZST` `ZZTB` | ★тот же дефект, найденный сплошным прогоном (`test/cat`): правку получили только пять команд выше, а у остальных `StrToIntDef` остался — в том числе у **кенвудовских двойников уже исправленных величин**. `FWxxxx;`/`SHxx;`/`SLxx;` ставили фильтр 0 (тот же индекс, что `ZZFI`), `AG0xxx;` и `SQ0xxx;` — громкость и порог в ноль, `GTxxx;` — АРУ в FAST, `PCxxx;`/`ZZPCxxx;` — мощность в ноль. Разбор везде строгий. Не тронуты `FR` (сам сверяет `0`/`1`), `MD`/`ZZMD` (нечисловое даёт 0, а установка идёт от 1) и `ZZOS` — там мусор трактуется как симплекс намеренно, по эталону |
| `ZZGT` | опрос отвечал тремя цифрами (как кенвудовская `GT`), а установка принимала ровно один символ: клиент, прочитавший `ZZGT000;` и написавший его назад, получал `?;`. Теперь принимаются обе ширины |
| `ZZBE` | формы были перевёрнуты: опрос `ZZBE;` отвечал `?;`, а установка `ZZBE01;` возвращала данные (`'1'`). Вся семья «сдвиг VFO на nn шагов» (`ZZAD ZZAE ZZAF ZZBF ZZSG ZZSH`) — однострочные заглушки, `ZZBE` приведён к ним |
| `KY` / `ZZKY` | текст не ограничивался; поле у Kenwood фиксированное, 25 символов. Длиннее — `?;`: очередь передачи не должна расти произвольно, иначе один пакет уводит станцию в эфир на неопределённое время |
| `CATTcp.SendStr` | один `send` на ответ. TCP не обязан отдать весь буфер за раз — длинный ответ (`IF`, `ZZEB`, список режимов) мог уехать обрезанным, и молча: усечение здесь не ошибка. Теперь дописываем остаток в цикле. ★И пишем через `WebUtils.SockSend`, а не голым `fpSend`: в нём `MSG_NOSIGNAL`, без которого запись в закрытый клиентом сокет приходит как `SIGPIPE` и убивает процесс целиком (обработчика сигнала в дереве нет, а цикл дозаписи умножает число попыток) |
| `CATSerial` | порт помечался активным ДО `SerOpen`; при отказе он навсегда оставался «работающим» в `ActiveCount` и UI, а причина нигде не оседала. Открытие переехало из потока в `TCATSerialPort.Start` (синхронно), появилось свойство `LastError`, поток теперь только читает, а закрывает владелец в `Stop`. Заодно Andromeda-порт назначается только на реально поднявшийся порт |
Формат `ZZOT` сверен отдельно: эталон читает 9 цифр как МГц с шестью знаками
после запятой (вставляет точку после третьего разряда) — численно это ровно те
же герцы, что пишем мы, поле совместимо. Мусор в `ZZOS` эталон трактует как
симплекс (`default` в `String2OffsetDirection`), и мы намеренно повторяем это,
а не отвечаем ошибкой.
Формат ответа на `ZZ*` (`ZZ` + код + **эхо суффикса запроса** + значение) сверен
с `CATParser.ParseExtended` — совпадает; это важно для `ZZSM0;`, где запрос
несёт селектор приёмника.
---
## 3. Что нужно сделать (актуально / есть чем закрыть)
Эти команды — заглушки, но соответствующая функция в EWSDR **уже есть** или
легко добавляется. Приоритетные кандидаты:
| Команда(ы) | Что | Чем закрыть | Сложность |
|---|---|---|---|
| `ZZVL`, `ZZUX`/`ZZUY` | блокировка VFO (общая и раздельная) | `SetVfoLock` | низкая |
| `ZZSW`, `ZZZW` | обмен VFO A/B | `VfoSwap` | низкая |
| `ZZAD ZZAE ZZAF ZZBE ZZBF`, `ZZSG`/`ZZSH` | сдвиг VFO на nn шагов | `StepIdxToHz` + `DoTuneUp/Down`, как у `ZZAU`/`ZZBP` | низкая |
| `ZZCU` | загрузка CPU (read) | телеметрия процесса | низкая |
| `ZZSF` | фильтр «центр + ширина» одной командой | `SetFilterEdges` | низкая |
| `ZZSG`/`ZZSH`, `ZZSD` | шаг VFO B и колеса — по одному шагу | те же `SetVfoB`/`DoTune*` | низкая |
| `ZZMY ZZMZ ZZMW ZZMX ZZQR ZZQS ZZQM` | запись/чтение памяти каналов (счётчик `ZZMV` уже есть) | `ChannelStore` / `ChannelController` | средняя |
| `ZZDN`/`ZZDO`, `ZZDP`/`ZZDQ`/`ZZDR` | низ/верх водопада, сетка спектра | `FSpecRefLevel`/`FSpecRange`, per-pan дисплей | средняя |
| `ZZPD`/`ZZPE`, `ZZPY`/`ZZPZ` | пан и зум дисплея | `SetPanZoomPan` | средняя |
| `ZZSN`, `ZZZM`, `ZZZV` | серийник, модель, версия железа | `BoardUtils` | низкая |
| `ZZXN` / `ZZXV` | составные статусы RX1/VFO | сборка из готовых геттеров | средняя |
| `ZZOC`/`ZZOD`/`ZZOE` | антенны | `SetAnt936x` (порты AD936x) | средняя |
| `ZZDX` / `ZZDY` | кнопка DX и порог спотов | флаг `ShowSpots` живёт в `MainForm` — сперва поднять в контроллер | средняя |
| `ZZAS` | RX2 AGC-T | — только при появлении RX2 | — |
> Прежде чем реализовывать новую команду, сверяйся с форматом поля в
> `Thetis/.../CAT/CATStructs.xml` (`<nsetparms>` / `<ngetparms>`) и телом
> метода в `CATCommands.cs`, чтобы ширина/знак значения совпадали. И проверяй
> **смысл**: подпись у заглушки в диспетчере может врать (см. §1.2).
### 3.1 Совсем не обрабатываются (вернут `?;`)
`ZZCU` (CPU), `ZZDY` (DX threshold), `ZZQA`/`ZZQB` (Quick Play/Rec),
`ZZVF` (VAC stereo). Из них реалистично добавить только `ZZCU` и `ZZDY`.
---
## 4. Осознанные заглушки (закрывать нечем)
Команды отвечают валидным dummy-значением для совместимости с управляющим ПО
(rigctld, N1MM, WSJT-X, Log4OM), но функции в EWSDR нет. Это ~70 % всех заглушек.
| Область | Команды (примеры) | Причина |
|---|---|---|
| **Второй приёмник RX2 / sub-RX** | `ZZAB ZZAS ZZBA ZZBB ZZBT ZZMB ZZOB ZZRS ZZSV ZZSX ZZNC ZZND ZZNF ZZNO ZZNU ZZNW ZZFJ ZZFR ZZFS ZZGU ZZLE ZZLF ZZPB ZZRY ZZXO` | один приёмник |
| **VAC / VAC2** | `ZZVA ZZVB ZZVC ZZVD ZZVF ZZVI ZZVK ZZVM ZZVO ZZVQ ZZVR ZZVT ZZVU ZZVV ZZVX ZZVY ZZVZ ZZYA ZZYB` | нет виртуального аудиокабеля |
| **RTTY / DIGI-офсеты** | `ZZRA ZZRB ZZRH ZZRL` | нет RTTY-движка |
| **RIT / XIT** | `RT XT RC`, `ZZRF ZZRT ZZXC ZZXD ZZXF ZZXS ZZXU` | нет тракта смещения приёма (CTUN — не замена) |
| **Diversity** | `ZZDB ZZDC ZZDD ZZDE ZZDF ZZDG ZZDH` (но не `ZZDA` — это усреднение спектра) | нет диверсити-приёма |
| **Компандер, noise gate, APF, VOX** | `ZZCP ZZCT`, `ZZGE ZZGL`, `ZZAA ZZAB ZZAT ZZAY`, `ZZVE ZZXH` | таких блоков в тракте нет (речевой компрессор — есть, это `ZZPK`/`ZZPL`) |
| **Микшер Flex 5000 / F1500, FlexWire** | `ZZWA…ZZWS`, `ZZWT…ZZWW`, `ZZFV ZZFW ZZFX ZZFY` | железо другого вендора |
| **ATU (Aries/Ganymede)** | `ZZOV ZZOW ZZOX ZZOZ ZZZA` | нет тюнера |
| **DSP-буферы, JSON-команды** | `ZZHA ZZHR ZZHT ZZHU ZZHV ZZHW ZZHX`, `ZZJP ZZJQ ZZJR ZZJS` | внутренние крутилки Thetis |
---
## 5. Транспорты
> CAT **дополнительных слайсов** (свой TCP-порт на слайс B..G + Auto TX) вынесен
> в отдельный документ: [`CAT_SLICES.md`](CAT_SLICES.md). Тот же движок, другой
> контекст; транспорты ниже принадлежат главному приёмнику (слайс A).
- **Serial** (`CATSerial.pas`): до 4 портов, параметры (порт/скорость/биты/
чётность/стоп) из `TGlobalSettings.CATSerial*`, применяются
`TCATAdapter.ApplySettings`. На текущей платформе сборки может быть
`{$DEFINE CAT_SERIAL_STUB}` (см. начало файла) — проверить перед релизом.
- **TCP** (`CATTcp.pas`): сервер на `CATTcpPort`, включается флагом
`CATTcpEnabled`. Клиенты идентифицируются GUID через `ZZGA`/`ZZGR`.
---
## 5.1 Стенд
`test/cat/run.sh` — сборка и прогон `test/cat/cattest.pas` (железа и движка не
требует, команды ходят по подставному радио). Проверяет не список команд, а
свойства всего набора, и по кодам ходит перебором `AA`..`ZZ`, поэтому новая
команда попадает под проверку сама:
- **круговой прогон** — ответ на опрос обязан приниматься обратно (так нашлась
разница ширин у `ZZGT`);
- **мусор в поле** — разбор не падает (сборка с `-Criot`), отвечает одним
корректным кадром, а нечисловое поле ничего не перестраивает (так нашлись
`FW`/`SH`/`SL`/`AG`/`SQ`/`GT` и десяток `ZZ`-двойников);
- **точечные регрессии** — заглушки, которые молча правили радио.
Подробности — `test/cat/README.md`. После правки любой команды прогон
обязателен: он дешёвый (секунды) и ловит ровно тот класс дефектов, которым эта
подсистема болеет.
---
## 6. Как добавить новую CAT-команду (чек-лист)
1. **Сверить формат** в `CATStructs.xml` + `CATCommands.cs` (Thetis).
2. Если нужна новая возможность контроллера — есть ли публичный метод/поле
в `TRadioController`? Если нет — добавить (по образцу `SetSplit`).
3. В `CATEngine.pas`: добавить callback(и) в `TCATContext`, `SafeGet*`-хелпер
для GET, переписать тело команды (`CmdXX` или `ZZXX`).
4. В `CATAdapter.pas`: геттер (чтение `FController.F*`), сеттер + `SyncXX`
(через `Invoke`), регистрация в `TCATAdapter.Create`.
5. Если команда `ZZ*` ещё не в диспетчере — добавить ветку `else if ext = '..'`.
6. Сборка-проверка: `lazbuild -B --ws=qt6 ewsdr.lpr` (GUI-граф включает CAT;
headless-демон `ewsdrd` CAT **не** содержит).
7. Прогнать стенд: `test/cat/run.sh`. Новая команда попадёт в круговой прогон и
в прогон мусором автоматически — если ширины GET и SET разошлись или поле
разбирается через `StrToIntDef`, стенд скажет об этом сразу.