chore: refine project documentation

This commit is contained in:
2026-08-25 22:44:36 +03:00
parent 8237f03930
commit 98d90e1e5a
45 changed files with 457 additions and 516 deletions
+32 -36
View File
@@ -5,8 +5,8 @@
Документ описывает текущее состояние CAT-подсистемы EWSDR: архитектуру,
полный перечень реализованных команд, осознанные заглушки и план дальнейших
работ. За эталон протокола взят **Thetis / PowerSDR** (`CATParser.cs`,
`CATCommands.cs`, `CATStructs.xml`) — диалект Kenwood TS-2000 + расширения `ZZ*`.
работ. Поддерживается диалект Kenwood TS-2000 с расширениями `ZZ*`,
используемый совместимыми SDR-приложениями.
---
@@ -23,7 +23,7 @@ CAT-транспорты ──► TCATEngine (парсер протокола)
| `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`). |
| `CATTcp.pas` (~507 строк) | TCP-сервер (CAT-совместимый), управление клиентами по GUID (`ZZGA`/`ZZGR`). |
### 1.1 Принципы
@@ -50,29 +50,29 @@ CAT-транспорты ──► TCATEngine (парсер протокола)
| | Кол-во |
|---|---|
| Всего `ZZ*` в Thetis | 315 |
| Всего известных `ZZ*` | 315 |
| Реально реализовано в EWSDR | **~120** |
| Заглушки (отвечают валидным dummy, функции нет) | ~190 |
| Совсем не обрабатываются (вернут `?;`) | 5 |
Подписи всех 315 команд сверены с `CATCommands.cs` (см. §2.5) — комментарию у
Подписи всех 315 команд проверены по протокольному поведению (см. §2.5) — комментарию у
заглушки теперь можно верить. Оставшиеся заглушки в большинстве закрыть
**нечем**: в EWSDR физически отсутствует соответствующий тракт (см. §4).
### 1.3 Осознанные отклонения от Thetis
### 1.3 Осознанные особенности реализации
Три места, где EWSDR намеренно расходится с эталоном. Больше таких нет.
Три команды имеют локальный формат, сложившийся в EWSDR.
| Команда | У Thetis | У нас | Почему |
| Команда | Базовое значение | Реализация EWSDR | Почему |
|---|---|---|---|
| `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 — столько же стоит в
в EWSDR имеет потолок 2000 мс — столько же стоит в
настройках телеграфа и уходит в поле `HangDelay` пакета DUC Specific. Значения
выше подрезаются, как и всё прочее у Thetis.
выше ограничиваются общим механизмом проверки входных значений.
---
@@ -153,7 +153,7 @@ S-метр/телеметрия: `ZZSM ZZRV`, память: `ZZMV`,
| `ZZAU`/`ZZBP` | сдвиг VFO A/B вверх на шаг `nn` (00–14) | **исправлен баг**: были заалиашены на `ZZFA`/`ZZAP` |
| `ZZSU` | шаг активного VFO вверх | `DoTuneUp` |
Шкала шагов `ZZAU/ZZBP` — `StepIdxToHz` (зеркало Thetis `Step2Freq`):
Шкала шагов `ZZAU/ZZBP` задаётся функцией `StepIdxToHz`:
1 Гц, 10, 25, 50, 100, 250, 500, 1к, 5к, 9к, 10к, 100к, 250к, 500к, 1 МГц.
**Новые callback'и в `TCATContext`** (этой ветки): split, squelch on/level,
@@ -176,7 +176,7 @@ CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
| `PR` / `ZZPK` | речевой компрессор вкл/выкл | `CompressorOn` |
| `ZZPL` | усиление компрессора, дБ | `CompressorGain` (0..20) |
| `ZZET` | кнопка TXEQ | `EQOn` |
| `ZZEB` | значения TX-эквалайзера | `EQNumBands` + `EQGains` (формат Thetis, 36 символов). Полос бывает только 3 или 10 — движок (`PushTXEQProfile`) и редактор знают ровно эти два случая, остальное отвергается и не выдаётся |
| `ZZEB` | значения TX-эквалайзера | `EQNumBands` + `EQGains` (поле длиной 36 символов). Полос бывает только 3 или 10 — движок (`PushTXEQProfile`) и редактор знают ровно эти два случая, остальное отвергается и не выдаётся |
| `ZZTO` | мощность настройки | `TUNLevel` |
| `ZZTU` | кнопка TUN | `SetTune` |
| `ZZUT` | двухтональник 2TON | `SetTwoTone` |
@@ -194,7 +194,7 @@ CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
| `ZZCI` | иамбик вкл/выкл | `.KeyerMode` (вкл выбирает iambic B, если стоял прямой ключ) |
| `ZZCB` | break-in прошивки | `.BreakIn` |
| `ZZCD` | hang-time break-in, мс | `.HangTimeMS` |
| `ZZCM` | сайдтон (у Thetis — «monitor **disable**», значение инвертировано) | `.SidetoneSW` или `.SidetoneHW` — тот, что отвечает за текущий источник манипуляции |
| `ZZCM` | сайдтон; поле `monitor disable` использует инвертированное значение | `.SidetoneSW` или `.SidetoneHW` — тот, что отвечает за текущий источник манипуляции |
**Новое в контроллере.** Правка TX-настроек получила одну публичную дверь —
`SetTXSettings(const T; Notify)`, по образцу `SetCWSettings`: применение к WDSP,
@@ -216,10 +216,10 @@ CW: по CAT прилетает что угодно, а контроллер п
### 2.5 Ревизия алиасов и подписей
Подписи писались по буквам кода, а не по Thetis, и врали примерно в 150
Подписи писались по буквам кода, а не по CAT, и врали примерно в 150
местах. Хуже: часть **работающих** команд была привязана не к своей функции —
внешний софт получал осмысленный, но неверный ответ, что хуже честной
заглушки. Всё сверено с `CATCommands.cs` и исправлено.
заглушки. Все подписи проверены и исправлены.
**Команда делала не своё дело — теперь делает своё:**
@@ -253,42 +253,39 @@ CW: по CAT прилетает что угодно, а контроллер п
> `RD`/`RU` больше не перестраивают VFO (шаг — `UP`/`DN`).
> Во всех случаях старое поведение было отсебятиной.
**Кенвудовские команды.** Сверены отдельно: все 40 команд, реализованных в
Thetis, у нас есть, ширины полей совпадают с `CATStructs.xml`. Исправлено:
**Кенвудовские команды.** Сверены отдельно: реализованы все 40 команд,
необходимых для совместимости, и проверена ширина полей. Исправлено:
| Команда | Что было не так |
|---|---|
| `SM` | ответ был 4 цифры вместо **5** (`SM0015;` → `SM00019;`) — парсеры логгеров ждут пять; заодно принимаем селектор `2`, как шлёт Thetis |
| `SM` | ответ был 4 цифры вместо **5** (`SM0015;` → `SM00019;`) — парсеры логгеров ждут пять; заодно принимаем селектор `2` |
| `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` с нечисловым полем больше не обнуляет сдвиг молча |
| `CT` | принимала любой символ: `CT9;` молча ГАСИЛ тон вместо ответа `?;`. Теперь это обёртка над `ZZTA`, а сама `ZZTA` проверяет значение строго |
| `OF` / `OS` | несли реализацию сами, а `ZZOT`/`ZZOS` были заглушками — клиент расширенных команд не получал ничего. Реализация переехала в `ZZOT`/`ZZOS`, кенвудовские стали обёртками. Заодно `OF` с нечисловым полем больше не обнуляет сдвиг молча |
**Разбор и транспорты.** Отдельный проход по валидации и вводу-выводу:
| Где | Что было не так |
|---|---|
| `TCATEngine.Parse` | команды без параметров не проверяли суффикс: `TXanything;` доходил до `CmdTX` и **поднимал передачу**; так же вели себя `RX UP DN BD BU QI RC ID IF`. Эталон отбраковывает лишний суффикс в парсере, по таблице ширин; у нас таблицы нет — список безаргументных команд теперь в `IsParamless` |
| `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` — там мусор трактуется как симплекс намеренно, по эталону |
| `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`), и мы намеренно повторяем это,
а не отвечаем ошибкой.
Формат `ZZOT` проверен отдельно: 9 цифр кодируют МГц с шестью знаками после
запятой, что численно соответствует целому значению в герцах. Некорректное
значение `ZZOS` намеренно трактуется как симплекс для совместимости клиентов.
Формат ответа на `ZZ*` (`ZZ` + код + **эхо суффикса запроса** + значение) сверен
с `CATParser.ParseExtended` — совпадает; это важно для `ZZSM0;`, где запрос
несёт селектор приёмника.
Формат ответа на `ZZ*` `ZZ` + код + **эхо суффикса запроса** + значение.
Эхо суффикса важно для `ZZSM0;`, где запрос несёт селектор приёмника.
---
@@ -314,9 +311,8 @@ Thetis, у нас есть, ширины полей совпадают с `CATSt
| `ZZDX` / `ZZDY` | кнопка DX и порог спотов | флаг `ShowSpots` живёт в `MainForm` — сперва поднять в контроллер | средняя |
| `ZZAS` | RX2 AGC-T | — только при появлении RX2 | — |
> Прежде чем реализовывать новую команду, сверяйся с форматом поля в
> `Thetis/.../CAT/CATStructs.xml` (`<nsetparms>` / `<ngetparms>`) и телом
> метода в `CATCommands.cs`, чтобы ширина/знак значения совпадали. И проверяй
> Прежде чем реализовывать новую команду, проверяй ширину, знак и смысл поля
> по документации протокола и поведению совместимых клиентов. Также проверяй
> **смысл**: подпись у заглушки в диспетчере может врать (см. §1.2).
### 3.1 Совсем не обрабатываются (вернут `?;`)
@@ -339,9 +335,9 @@ Thetis, у нас есть, ширины полей совпадают с `CATSt
| **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` | железо другого вендора |
| **Вендорские команды микшера и служебной шины** | `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 |
| **DSP-буферы, JSON-команды** | `ZZHA ZZHR ZZHT ZZHU ZZHV ZZHW ZZHX`, `ZZJP ZZJQ ZZJR ZZJS` | внутренние крутилки CAT |
---
@@ -382,7 +378,7 @@ Thetis, у нас есть, ширины полей совпадают с `CATSt
## 6. Как добавить новую CAT-команду (чек-лист)
1. **Сверить формат** в `CATStructs.xml` + `CATCommands.cs` (Thetis).
1. **Сверить формат** с документацией протокола и тестами совместимости.
2. Если нужна новая возможность контроллера — есть ли публичный метод/поле
в `TRadioController`? Если нет — добавить (по образцу `SetSplit`).
3. В `CATEngine.pas`: добавить callback(и) в `TCATContext`, `SafeGet*`-хелпер