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
+15 -18
View File
@@ -4,8 +4,8 @@
Дата последнего обновления: 2026-06-26.
Документ описывает поддержку аппаратной передней панели Apache Labs
**Andromeda / ANAN-G2** (поворотные энкодеры, кнопки, LED-индикаторы, LCD).
За эталон протокола взят Thetis (`Andromeda/Andromeda.cs`, `CAT/CATCommands.cs`).
**Andromeda / ANAN-G2** (поворотные энкодеры, кнопки, LED-индикаторы, LCD)
через команды CAT, используемые панелью.
См. также [CAT_STATUS.md](CAT_STATUS.md) — общий статус CAT-подсистемы.
@@ -84,9 +84,8 @@ Andromeda — это **не отдельный набор команд**, а д
## 3. Раскладка по умолчанию (AndromedaMap.pas)
Раскладка **зашита в коде** и зеркалит дефолтную таблицу Thetis
(`MakeNewAndromedaDataset`): наш 0-based индекс = «Pushbutton/Encoder Number»
Thetis 1. Номера **проверены на реальной панели** (см.
Раскладка **зашита в коде**: внутренний 0-based индекс на единицу меньше номера
кнопки или энкодера, передаваемого панелью. Номера **проверены на реальной панели** (см.
`andromeda_test/AndDecode.pas`). Правится в `AndromedaMap.pas`.
### Кнопки (`ZZZP`, индекс 0-based после разбора)
@@ -105,12 +104,12 @@ Thetis 1. Номера **проверены на реальной панел
| | | 47 | TUN |
Не назначены (нет тракта в ewsdr): 2 (RX2 Mute), 40 (band GEN), 41 (RIT/XIT).
NB/NR/ANF/SNB/CTCSS/Squelch у реальной панели на кнопках не выведены
(в Thetis — на тачскрине), действия в enum сохранены для кастомной раскладки.
NB/NR/ANF/SNB/CTCSS/Squelch у реальной панели на кнопках не выведены;
действия в enum сохранены для пользовательской раскладки.
### Shift / band-keypad
Кнопка #28 — модификатор-защёлка (как `eBBShift` в Thetis). Пока активен
Кнопка #28 — модификатор-защёлка. Пока он активен
(LED `aiShift` #6), кнопки band-группы #29..39 выбирают диапазон напрямую
(`SetBand` 0..10: 160m…6m); #40 = GEN — в ewsdr отсутствует. Снимается
повторным Shift либо автоматически после выбора диапазона (non-sticky).
@@ -120,7 +119,7 @@ NB/NR/ANF/SNB/CTCSS/Squelch у реальной панели на кнопках
| # | Параметр | Шаг |
|---|---|---|
| 0 | AF Gain (громкость) | ±1 |
| 1 | AGC-T (в Thetis — слайдер «RF») | ±1 dB |
| 1 | AGC-T | ±1 dB |
| 4 | Filter High → ширина фильтра | ±50 Гц (`AND_BW_STEP_HZ`) |
| 11 | Drive (мощность) | ±1 % |
| VFO | энкодер VFO (`ZZZU/ZZZD`) | ±10 Гц/шаг (`AND_VFO_STEP_HZ`) |
@@ -129,7 +128,7 @@ NB/NR/ANF/SNB/CTCSS/Squelch у реальной панели на кнопках
> симметричный (`SetFilterBW`), поэтому #4 крутит ширину, #5 пока не задействован.
> Энкодеры RX2 AF/AGC (#2/#3), diversity (#6/#7), RIT/XIT (#8/#9) — нет тракта.
### Индикаторы (`ZZZI`, номера зеркалят дефолт Thetis)
### Индикаторы (`ZZZI`, номера соответствуют протоколу панели)
| LED # | Индикатор | LED # | Индикатор |
|---|---|---|---|
| 1 | MOX | 12 | Split |
@@ -142,7 +141,7 @@ NB/NR/ANF/SNB/CTCSS/Squelch у реальной панели на кнопках
| 11 | VFO Lock | | |
Драйвятся реально: **MOX, Tune, CTUN, VFO A/B, VFO Lock, Shift, Split, NB, NR,
SNB, ANF, Squelch**. Остальные слоты дефолтной таблицы Thetis (ATU/RIT/XIT)
SNB, ANF, Squelch**. Остальные слоты таблицы панели (ATU/RIT/XIT)
в ewsdr функций не имеют и не зажигаются.
---
@@ -169,7 +168,7 @@ SNB, ANF, Squelch**. Остальные слоты дефолтной табли
- Транспорт: **serial** (USB CAT).
- Вход: `ZZZD/ZZZU` (VFO), `ZZZE` (энкодеры), `ZZZP` (кнопки), `ZZZS` (рукопожатие).
- Выход: **`ZZZI`** — LED-индикаторы (push от `OnStateChanged`, дедуп повторов).
- Раскладка — зашитый дефолт, выверена по Thetis и реальной панели.
- Раскладка по умолчанию проверена с реальной панелью.
- **Shift / band-keypad** — прямой выбор диапазона (#29..39 → `SetBand`), LED `aiShift`.
- **VFO Lock** — блокировка перестройки (`SetVfoLock`, гейт в `TuneActiveBy`), LED `aiVFOLock`.
- A>B / B>A / Toggle A/B, SDR ON (start/stop) — на штатных кнопках панели.
@@ -178,11 +177,11 @@ SNB, ANF, Squelch**. Остальные слоты дефолтной табли
### Осталось / возможные улучшения
| Задача | Заметки |
|---|---|
| **Filter High/Low (`ZZZE` #4/#5)** | независимые края фильтра; сейчас #4 = ширина, #5 не задействован. Нужны `FFilterLow/High` + переработка `ApplyModeFilter` и passband дисплея (sideband-aware, как в Thetis) |
| **Sticky shift** | сейчас shift всегда non-sticky (снимается band-нажатием); в Thetis есть `AndromedaStickyShift` |
| **Filter High/Low (`ZZZE` #4/#5)** | независимые края фильтра; сейчас #4 = ширина, #5 не задействован. Нужны `FFilterLow/High` + переработка `ApplyModeFilter` и sideband-aware passband дисплея |
| **Sticky shift** | сейчас shift всегда non-sticky и снимается нажатием кнопки диапазона; можно добавить сохраняемую настройку |
| **`ZZMF`** — текст на LCD мультифункц. энкодера | кодировка: 15 ASCII-символов парами цифр (код−32) |
| **TCP-транспорт (G2 V2 panel)** | сейчас только serial; нужен режим в `CATTcp` |
| **Настраиваемая раскладка** | сейчас зашита; вынести в конфиг/UI (как `AndromedaEditForm` в Thetis) |
| **Настраиваемая раскладка** | сейчас зашита; вынести в конфиг и пользовательский интерфейс |
| **Long-press кнопок** | принимается (`ZZZP` state=2), но действий пока нет — обрабатывается только нажатие (state=1) |
| **Индикаторы RIT/XIT/PureSignal/Diversity/ATU** | нет соответствующих функций в `TRadioController` |
| **Мультифункц. энкодер (`aeMulti`)** | назначение не задано |
@@ -197,6 +196,4 @@ SNB, ANF, Squelch**. Остальные слоты дефолтной табли
- **Добавить индикатор:** элемент `TAndIndicator` + номер в `AND_INDICATOR_NUM` +
push в `SyncAllIndicators` и в `OnControllerState` (по нужному `rf*`-полю).
- **Изменить раскладку:** только `AndromedaMap.pas` (таблицы-константы).
- Сверяться с форматами Thetis: `Andromeda/Andromeda.cs` (логика панели,
`EIndicatorActions`/`EButtonBarActions`/`EEncoderActions`) и
`CAT/CATCommands.cs` (`ZZZ*`).
- Форматы команд `ZZZ*` и нумерацию элементов проверять по протоколу панели.
+5 -9
View File
@@ -6,13 +6,9 @@
как отдельным трансивером — через свой TCP-порт. COM-порты и основной TCP-порт
(19090) остаются за главным приёмником (слайс A) и не тронуты.
Эталон поведения — **SmartSDR CAT** (FlexRadio): порт привязан к слайсу
(«VFO A Slice»), а флаг **Auto TX** — это их «Auto Switch TX Slice»:
> When enabled Auto Switch TX Slice automatically switches the Transmit Slice to
> the VFO A Slice selected above when a CAT TX command is sent on the com port…
> If this option is not enabled, the operator will have to manually designate the
> Transmit Slice.
Каждый порт привязан к определённому слайсу. Флаг **Auto TX** разрешает
команде передачи автоматически назначить этот слайс источником TX; без флага
источник выбирается оператором вручную.
---
@@ -72,8 +68,8 @@ TX-источника атомарны относительно GUI.
`RX;` / `ZZTX0;`: снимает передачу **только свою** — если TX-источник именно этот
слайс. `RX;` на порту слайса C не обрывает эфир слайса B. TUNE снимается тоже.
Выбор TX-источника после отпускания PTT **остаётся** на этом слайсе (как в
SmartSDR: Transmit Slice переключён, а не одолжен). Бейдж на флаге это показывает.
Выбор TX-источника после отпускания PTT **остаётся** на этом слайсе.
Бейдж на флаге это показывает.
`GetTransmitting` (для `TX`/`IF`/`ZZTX`) на порту слайса = «передаёт **этот**
слайс». Чужой эфир виден как приём — иначе управляющее ПО решило бы, что его PTT
+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*`-хелпер
+3 -3
View File
@@ -249,11 +249,11 @@ end;
(блокирующий push = пейсинг на device-rate); при underrun — нули (непрерывный
full-duplex). Решение rate-mismatch (риск §6.2) — апсемпл в host (RX/TX делят
один `sampling_frequency`).
- **Размер TX-буфера** масштабируется от device-rate (модель SoapyPlutoSDR):
- **Размер TX-буфера** масштабируется от device-rate:
`round(rate/60)` → следующая степень 2, кламп `[4096..65536]` (576k→16384,
1536k→32768). Фикс 4096 был мал для full-duplex выше ~1 Msps → дропы/щелчки.
FIFO держит ~4 таких буфера запаса (`TxBufPairsForRate`, пересчёт в `StartTX`).
- Подтверждено по каноничному драйверу (SoapyPlutoSDR): TX full-scale = 32768,
- Для TX используется full-scale 32768,
MSB-aligned → наш `>>8` (24→16) верен; занижения уровня по дизайну нет.
### 4.7 Усиление / телеметрия
@@ -394,7 +394,7 @@ end;
2.**TX sample-rate mismatch** (192k WDSP vs ≥521k Pluto) — решено host-апсемплом
192k→device-rate в `SendDUCIQ` (целочисленно, пресеты кратны 192k). ✅ Качество
образов — **полифазный FIR-интерполятор** (окно Блэкмана ~−58 дБ). ✅ Щелчки на
full-duplex >1 Msps — TX-буфер масштабируется от rate (модель SoapyPlutoSDR).
full-duplex >1 Msps — TX-буфер масштабируется от rate.
3. **QO-100 display-duplex** — hardware full-duplex есть (Фаза 4), остаётся довести
`SetDuplex` (водопад RX во время TX); проверить латентность/буферы libiio.
4. **LNB дрейф** — калибровка `LnbPpm` по маяку (ручная/полуавтомат).
+13 -13
View File
@@ -1,11 +1,11 @@
# Мультислайсы и панадаптеры (как во FlexRadio)
# Мультислайсы и панадаптеры
Статус: **Этапы 1 + 2 РЕАЛИЗОВАНЫ и ЗАКОММИЧЕНЫ** (ветка `feature/multislice`,
коммит `1527b0f`; проверено на эфире) + UI-полировка флагов (`30a6616`,
`3049f91`, `a3ac459`).
**2026-07-10: Этап 3 ПЕРЕПРОЕКТИРОВАН.** Вместо старой идеи «off-span слайс с
edge-маркером» принята Flex-модель: **аппаратный DDC = отдельный панадаптер**
edge-маркером» принята модель **аппаратный DDC = отдельный панадаптер**
(свой спектр+водопад), а слайсы ВСЕГДА живут внутри какого-то панадаптера.
Старый черновик (edge-маркеры + slices-рейл) перенесён в «Отвергнутые
альтернативы» в конце файла. Реализация Этапа 3 не начата.
@@ -44,9 +44,9 @@ edge-маркером» принята Flex-модель: **аппаратный
---
## Модель FlexRadio (итог разбора 2026-07-10)
## Модель панадаптеров и слайсов (итог разбора 2026-07-10)
Во Flex **две независимые сущности**, и это ключ ко всему дизайну:
В интерфейсе используются **две независимые сущности**, и это ключ ко всему дизайну:
- **Панадаптер (panafall)** — спектр+водопад. Самостоятельный объект: свой
широкополосный захват в железе, свой центр/span/зум, свои настройки водопада.
@@ -58,7 +58,7 @@ edge-маркером» принята Flex-модель: **аппаратный
ещё один захват) и создаёшь слайсы в нём. Лимиты: 6400 — 2 пана/2 слайса,
6600 — 4/4, 6700 — 8/8.
**Второй приёмник (второй АЦП)** у Flex называется SCU (Spectral Capture Unit):
**Второй приёмник (второй АЦП)** представлен отдельным трактом захвата:
полный тракт «антенный вход → АЦП → захват». 6600/6700 — два SCU, 6400 — один.
У панадаптера НЕТ переключателя «RX1/RX2» — есть селектор **антенны**
(ANT1/ANT2/RXA/RXB/XVTR), и радио само сажает панадаптер на SCU под выбранную
@@ -66,8 +66,8 @@ edge-маркером» принята Flex-модель: **аппаратный
двух SCU) — отдельный режим поверх.
**Окна:** все панафоллы тайлятся ВНУТРИ одного главного окна (сплиты с
перетаскиваемыми разделителями). Вынос панафолла в отдельное OS-окно штатный
SmartSDR НЕ умеет — наш pop-out будет улучшением поверх Flex-модели.
перетаскиваемыми разделителями). EWSDR дополнительно поддерживает вынос
панафолла в отдельное окно ОС.
---
@@ -86,12 +86,12 @@ SmartSDR НЕ умеет — наш pop-out будет улучшением по
2. Пан 0 (главный) существует всегда; его «слайс A» — текущий главный тракт
(WDSP-канал 0). Панадаптеры N>0 живут целиком на слайс-механике Этапа 1
(каналы `SLICE_CHAN_BASE+`), при создании пана автоматически создаётся
один слайс в его центре (поведение Flex).
один слайс в его центре.
3. ADC-источник — свойство **панадаптера**, не слайса (бейдж `A1`/`A2` в шапке).
В отличие от Flex показываем именно АЦП, а не антенну: на HPSDR-платах
Показываем именно АЦП, а не антенну: на HPSDR-платах
маппинг «вход→АЦП» жёсткий. Антенны ADC0 (Alex ANT1/2/3) — отдельная
ГЛОБАЛЬНАЯ ручка, общая для всех панов этого АЦП (детали в 3.4).
4. Буквы слайсов — глобальный пул A..G через все паны (как во Flex A-H):
4. Буквы слайсов — глобальный пул A..G через все паны:
буква = личность слайса, пан = место жительства.
5. Возможности бэкенда описывает запись capabilities (см. таблицу) — UI по ней
прячет/блокирует кнопки, а не хардкодит «if Pluto».
@@ -324,7 +324,7 @@ ChangeSampleRate).
### 3.3 — UI: второй панадаптер в главном окне
- **Стек панов**: область спектра = вертикальный стек `TPanafallPanel` с
`TSplitter` между ними (Flex-стиль). Пан 0 сверху. Высоты в персист.
`TSplitter` между ними. Пан 0 сверху. Высоты сохраняются в настройках.
- **Шапка пана** (тонкая полоска над спектром, темизирована как флаги):
частота центра · диапазон · rate · бейдж ADC `A1/A2` (3.4) ·
кнопки `⧉` (pop-out, 3.5) и `×` (закрыть; у пана 0 нет). Шапка пана 0
@@ -480,7 +480,7 @@ ChangeSampleRate).
### 3.5 — Pop-out: панадаптер в отдельном OS-окне
Улучшение поверх Flex (SmartSDR так не умеет). Кнопка `⧉` в шапке.
Кнопка `⧉` в шапке выносит панадаптер в отдельное окно ОС.
- `TPanFloatForm` — простая TForm-обёртка, в которую переезжает
`TPanafallPanel`; `×` окна возвращает пан в стек главного окна.
@@ -767,7 +767,7 @@ ApplyAll, тёплый рестарт восстанавливает override п
Старый черновик Этапа 3 предлагал «off-span слайсы»: слайс вне видимой
панорамы на своём hard-DDC, представленный edge-маркером у края спектра
(цветная буква + ◄/► + частота) и/или «slices-рейлом» (полоса-менеджер с
чипами слайсов). Отвергнуто 2026-07-10 в пользу Flex-модели «DDC =
чипами слайсов). Отвергнуто 2026-07-10 в пользу модели «DDC =
панадаптер»: off-span слайс без своего спектра слепой (не видно обстановку
вокруг), сущности расползаются (слайс с DDC ≠ слайс без DDC), а рейл дублирует
флаги. Идея рейла может вернуться позже как компактный обзор всех слайсов —
+22 -28
View File
@@ -3,9 +3,9 @@
Ветка разработки: `feature/tci-protocol`.
Дата последнего обновления: 2026-08-18.
Эталон протокола — «Протокол TCI, версия 2.0» Expert Electronics
Реализация соответствует спецификации «Протокол TCI, версия 2.0»
(`doc/TCI Protocol_RU.pdf`, 12 января 2024). EWSDR выступает **сервером**
(как ExpertSDR3): порт слушаем мы, клиенты — логгеры, скиммеры, программы
по стандартной серверной модели: порт слушаем мы, клиенты — логгеры, скиммеры, программы
цифровых видов, внешние усилители и коммутаторы.
---
@@ -187,13 +187,12 @@ UI — вкладка **CAT → TCI Server**, справа от «TCP CAT Server
| `arg1` у `DRIVE`, `TUNE_DRIVE` | мощность одна на радио — номер не адресует ничего |
★**Почему приёмник — слайс, а не панорама.** Сперва приёмником был панадаптер,
как в ExpertSDR3, где приёмник = DDC. У нас это разошлось с жизнью сразу на
в модели, где приёмник соответствует DDC. В EWSDR это разошлось с жизнью сразу на
двух концах. У Pluto панорама ровно одна (`BackendCaps.MaxPans = 1`), и второй
приёмник существует там **только** как слайс главного пана — при нумерации по
панам он был бы недоступен вообще, а `TRX_COUNT` навсегда равнялся единице. С
другого конца — клиенты: у MSHV в настройках всего две модели, «TCI Client rx1»
и «rx2», то есть приёмники 0 и 1, и никакого третьего номера ввести некуда
(`hvrigcontrol.cpp`: `netServPort[3]`/`[4]`, `network.cpp`: `tci_trx`). Значит
панам он был бы недоступен вообще, а `TRX_COUNT` навсегда равнялся единице.
Кроме того, некоторые клиенты позволяют выбрать только приёмники 0 и 1.
Значит
«первый добавленный слайс» обязан быть приёмником **1** на любом железе.
Слот слайса даёт ровно это. Слоты глобальные и стабильные, у каждого своя
@@ -255,9 +254,8 @@ B независимо от того, где он живёт: на openHPSDR э
слотов слайсов) один раз и навсегда, а слот из этого потолка может быть пуст.
Про пустой слот не шлётся ничего — ни `vfo`, ни `dds`, ни показания
измерителей, и команды к нему тоже игнорируются целиком (`LiveRx`). Раньше
уходили `dds`/`vfo`/`if` с нулём, и клиент принимал ноль за настоящую частоту;
для MSHV это вообще фатально — именно ответом на `vfo:<rx>,0;` он завершает
инициализацию, то есть подключился бы к пустоте. Состояние появляется в момент
уходили `dds`/`vfo`/`if` с нулём, и клиент принимал ноль за настоящую частоту,
завершая инициализацию несуществующего приёмника. Состояние появляется в момент
создания слайса: `PushChannelMap` рассылает картину нового приёмника целиком,
включая `tx_enable` с его номером.
@@ -501,10 +499,8 @@ web-клиента: явная просьба сильнее умолчания
отбрасывается молча: отвечать ошибкой на каждый чужой блок значит захлебнуться.
**★Маркер `TX_CHRONO` называет приёмник КЛИЕНТА, а не нулевой.** Клиент шлёт
TX-аудио не по своей воле, а строго в ответ на маркер, и все входящие бинарные
блоки фильтрует по номеру приёмника — у MSHV это первая строка обработчика
(`network.cpp`: `if (pStream->receiver != tci_trx) return;`, ветка `TxChrono`
там же собирает и отправляет блок). С жёстким нулём в заголовке клиент,
TX-аудио строго в ответ на маркер и может фильтровать входящие бинарные блоки
по номеру приёмника. С жёстким нулём в заголовке клиент,
сидящий на **втором слайсе** (`tci_trx = 1`), маркеров не видел вовсе: эфир по
`trx:1,true,tci` поднимался, а звука не было ни одного блока. Поэтому вместе с
клиентом-модулятором запоминается номер приёмника из его же `TRX`
@@ -536,8 +532,8 @@ TX-аудио не по своей воле, а строго в ответ на
Срок считается **по часам** от `START`, а не по накопленным сэмплам: линейный
выход может молчать (мьют, стоящий приёмник), а время записи всё равно идёт.
Кольцо «последние N секунд» вело себя иначе в обе стороны — начало записи
затирало само себя, а `SAVE` через час после `START` отдавал файл, которого у
ExpertSDR3 давно бы не было.
затирало само себя, а `SAVE` через час после `START` отдавало бы файл уже после
завершения заданного окна записи.
`SAVE` завершает запись и ставит её в очередь писателю: файл бывает в десятки
мегабайт, а команда пришла в потоке клиента, который в это время не читает свой
сокет. MP3 не поддержан — кодера в проекте нет, и на `.mp3` уходит честный
@@ -794,9 +790,8 @@ running`), а сам телеграф уходит **текущему TX-ист
настройки обязана вставать на тот же слайс, что и модуляция;
- **ответ автору называет ЕГО номер приёмника**, а состояние в нём — «в эфире
именно твой слайс» (`FTransmitting and (TxRx = Rx)`). Так надо потому, что
клиенты фильтруют входящие строки по `arg1`: MSHV, например, отбрасывает
всё, что адресовано не его приёмнику (`network.cpp`:
`if (ls2.at(0)!=tci_trx) continue;`), — и отказ, названный чужим номером, до
клиенты могут фильтровать входящие строки по `arg1`, и отказ, названный
чужим номером, до
него бы просто не дошёл. В **рассылку** уходит номер того, чей слайс сейчас
источник передачи (`TxRx` по `TxSliceId`): клиент на приёмнике 1 по ней
видит, что в эфире не он. Смена TX-источника оператором доходит до клиентов
@@ -872,8 +867,8 @@ running`), а сам телеграф уходит **текущему TX-ист
Порядок обратный: сперва UI, потом TCI.
- **`RX_ANC_ENABLE`, `RX_APF_ENABLE`, `RX_DSE_ENABLE`, `RX_NF_ENABLE`.** Этих
узлов в тракте нет физически. У нас `NR`/`NR2` (EMNR), `NB`/`NB2` (ANB/SNBA)
и `ANF` — они в TCI настоящие. `ANC` и `DSE` — блоки собственной обработки
Expert, не WDSP; `APF` и `NF` были бы новыми узлами в цепочке.
и `ANF` — они в TCI настоящие. Для `ANC`, `DSE`, `APF` и `NF` потребовались
бы новые узлы обработки в цепочке WDSP.
- **`RX_NB_PARAM`.** Пороги шумодава у нас — подобранные константы
`FILTER_NB_*`, одинаковые для всех каналов и не выведенные даже в интерфейс.
Клиент, покрутивший их по TCI, оставил бы шумодав расстроенным насовсем.
@@ -898,7 +893,7 @@ running`), а сам телеграф уходит **текущему TX-ист
| `MUTE` и линейный выход | глушит и поток: движок под мьютом не зовёт `OnAudio`. Аудиопоток приёмника (`AUDIO_START`) мьют не трогает — он снимается до громкости |
| DIGL/DIGU, 2 канала | по §3.4 в цифровых модах два канала должны нести комплексный сигнал; у нас это обычное стерео с выхода WDSP — оба тапа стоят там, где сигнал уже вещественный: `rakDemod` сразу за демодулятором, `rakLineOut` за громкостью. Комплексный выход потребовал бы отдельной ветки панели RXA и третьего маршрута тапа, а цифровые клиенты берут `IQ_START` — он честно комплексный |
| Поток канала B | не бывает: в протоколе аудиопоток один на приёмник, и он всегда про канал A |
| Формат IQ | всегда float32, два канала — `AUDIO_STREAM_SAMPLE_TYPE` относится к аудио (§4.3), а ExpertSDR3 IQ иначе и не шлёт |
| Формат IQ | всегда float32, два канала — `AUDIO_STREAM_SAMPLE_TYPE` относится к аудио (§4.3), а TCI IQ иначе и не шлёт |
| `IQ_SAMPLERATE` 384 кГц на Pluto 576/960 кГц | нацело не делится, поэтому уходит 192 кГц (см. §2.5). Клиент обязан читать частоту из заголовка блока, а не считать её равной запрошенной |
| MP3 у рекордера | не поддержан: кодера в проекте нет, а тащить внешний (lame) ради рекордера — это новая зависимость и её лицензия в сборке, которых у ewsdr сейчас нигде нет. WAV пишется без потерь и открывается всем; на `.mp3` уходит честный `tci_error`, а не молчаливый WAV с чужим расширением |
@@ -918,7 +913,7 @@ running`), а сам телеграф уходит **текущему TX-ист
одинаковыми — в телеметрии платы одно значение forward power.
У `TRX` третий аргумент разобран только для `tci` (см. §2.5). Значения
`mic1`/`mic2`/`micpc`/`ecoder2` называют физические входы ExpertSDR3, которых у
`mic1`/`mic2`/`micpc`/`ecoder2` называют физические входы TCI, которых у
нас нет: они значат «микрофон, выбранный в программе», то есть ровно то же, что
и отсутствие аргумента.
@@ -933,7 +928,7 @@ running`), а сам телеграф уходит **текущему TX-ист
(`AddSliceAtFreq` + `CreateSliceFlag` — флаг, раскладка, персист), у
контроллера есть только `RemoveSlice`. Адаптер TCI, как и CAT, до MainForm
не дотягивается и не имеет права: он обязан собираться в демоне, где LCL
нет вовсе. Вторая по существу: канал B у ExpertSDR3 — второй приёмник внутри
нет вовсе. Вторая по существу: канал B у TCI — второй приёмник внутри
того же DDC, а у нас слайс — полноценная сущность со своим флагом, звуком,
входом и CAT-портом. Клиент, «выключивший канал B», снёс бы оператору
рабочий слайс вместе с его модой, фильтром и портом. Правильный порядок:
@@ -1086,12 +1081,11 @@ running`), а сам телеграф уходит **текущему TX-ист
rate устройства сама переобъявляет и `iq_samplerate`, и `if_limits`, а на
576 кГц (Pluto) та же просьба даёт законные 192 кГц.
- **Слайс как приёмник** (часть E, живой движок): созданный на ГЛАВНОМ пане
слайс становится приёмником 1, отвечает на `vfo:1,0;` своей частотой (это и
есть вся инициализация MSHV), слушается командой `vfo:1,0,<Гц>`, отдаёт своё
слайс становится приёмником 1, отвечает на `vfo:1,0;` своей частотой,
слушается командой `vfo:1,0,<Гц>`, отдаёт своё
аудио блоками с `receiver = 1`, ★по `trx:1,true,tci` берёт модуляцию из TCI
(то есть просьбу не стирает `Changed(rfTransmitting)` из `SetTxSlice`) и шлёт
маркеры `TX_CHRONO` **под номером 1** — оба этих места и ломали передачу
MSHV, сидящего на втором слайсе; а `trx:0,true,tci` поверх этой же передачи
маркеры `TX_CHRONO` **под номером 1**; а `trx:0,true,tci` поверх этой же передачи
(команда-пустышка) номер приёмника в маркерах не меняет, а после удаления слайса приёмник 1 замолкает
целиком — вместо прежнего `vfo:1,0,0`.
- **Сквозной прогон через живой WDSP:** синтетический 24-битный IQ подаётся