Files
ewsdr/doc/CAT_SLICES.md
ew8bakandClaude Opus 5 20186ce132 feat(slices): аудио-устройства слайса на вкладке Slices (in/out на слот)
У каждого слайса B..G — свои Audio out / Audio in рядом с его CAT-портом.
Списки те же, что у главных комбо; пустой выбор (Default/None) = прежнее
поведение, общий выход/вход приложения.

Это устройство СЛОТА (буквы), а не шаблон:
  • смена настройки применяется к слайсу, стоящему на слоте, немедленно
    (SetSliceSlotAudio) — как и вся остальная форма настроек EWSDR;
  • слайс, который встанет на слот позже (создание/restore из персиста),
    получает то же устройство: слот сильнее аргумента AddSlice;
  • выбор во вкладке AUD флага пишется обратно в настройку слота
    (StoreSliceSlotAudio) — вкладка всегда показывает реальность, а не
    вторую, конкурирующую истину.

Хранятся ИМЕНА PortAudio-устройств, а не индексы: порядок перечисления
между запусками плавает. Имя устройства, которого сейчас нет в системе,
не теряется — комбо добавляет его в список и держит выбранным.

ApplySliceSlotAudio зовётся на старте и на смене устройства (rfDevice):
слайсы восстанавливаются позже, слот должен знать свои звуковухи заранее.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 14:33:42 +03:00

181 lines
13 KiB
Markdown
Raw Permalink 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 дополнительных слайсов (Slice CAT)
Ветка: `feature/slice-cat`. Дата: 2026-07-25.
Внешняя программа (WSJT-X, FLDIGI, лог) управляет **отдельным слайсом** EWSDR
как отдельным трансивером — через свой 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.
---
## 1. Архитектура
Протокол **не дублируется**. `TCATEngine` — чистый парсер/формировщик поверх
плоской записи callback'ов `TCATContext`, `TCATTcpServer` — транспорт поверх
движка. Значит «CAT слайса» = **тот же движок с другим контекстом**:
```
TCATAdapter ──┬── главный контекст ──── TCATEngine ── serial ×4 + TCP 19090 (слайс A)
├── TCATSliceEndpoint ─── TCATEngine ── TCP 19091 (слайс B)
├── TCATSliceEndpoint ─── TCATEngine ── TCP 19092 (слайс C)
└── … по слоту на букву (B..G, потолок = WDSPEngine.MAX_SLICES)
```
| Файл | Что добавлено |
|---|---|
| `CATSliceEndpoint.pas` (новый) | `TCATSliceEndpoint` — контекст поверх `FSlices[слот]`, свой движок + свой TCP-сервер. Константа `CAT_SLICE_SLOTS`. |
| `CATAdapter.pas` | Владеет массивом эндпоинтов; `ApplySliceSettings` поднимает/опускает порты и кладёт Auto TX в контроллер. |
| `CATEngine.pas` | `ZZFL`/`ZZFH` перестали быть заглушками: новые callback'и `Get/SetFilterLow/High` (у главного nil → прежний дефолт режима). Хелпер `SignedPad`. |
| `RadioController.pas` | `RequestSliceTx`, `FSliceAutoTx[слот]`, слот-хелперы, `FilterIdxFor`, `AGCModeToUI`, зеркало DSP-состояния слайса в `TCtrlSlice`. |
| `Settings.pas` | `CATSliceEnabled/CATSlicePort/CATSliceAutoTX[0..5]`, персист в корневой секции `cat` и в device-blob. |
| `SettingsForm.pas` | Вкладка **Slices**. |
| `VfoOverlay.pas`, `PanafallPanel.pas` | Бейдж `AutoTX` вместо `TX` на флаге слайса. |
**Привязка к слоту, а не к Id слайса.** Слот 0..5 = буква B..G. Слайс на слоте
создаётся/удаляется в любой момент, порт при этом остаётся поднятым: пока слайса
нет, эндпоинт отвечает «радио выключено» (`PS0;`) и игнорирует команды. Настройка
(порт, Auto TX) переживает удаление слайса.
**Потокобезопасность** — как у главного адаптера: геттеры читают состояние в
CAT-потоке, сеттеры кладут параметр под локом эндпоинта и маршалятся в поток
контроллера через `Invoke` (GUI = `TThread.Synchronize`).
---
## 2. Auto TX и защита передачи
Вся логика — в **одной** точке: `TRadioController.RequestSliceTx(SliceId, On_)`,
исполняется в потоке контроллера, поэтому проверка «уже в эфире» и смена
TX-источника атомарны относительно GUI.
`TX;` / `ZZTX1;` на порту слайса:
1. Слайса на слоте нет → игнор.
2. **Кто-то уже передаёт** (другой слайс, главный VFO, кнопка MOX/TUNE,
аппаратная PTT) → игнор. Перехвата эфира нет никогда — передаёт всегда
только один слайс.
3. Режим слайса `DMR` → игнор (AMBE-передатчика нет).
4. Слайс не выбран TX-источником:
* **Auto TX включён** → `SetTxSlice(слайс)`, затем `SetMOX(True)`;
* **Auto TX выключен** → игнор (оператор назначает источник руками —
бейдж `TX` на флаге слайса).
5. Слайс уже TX-источник → просто `SetMOX(True)`.
`RX;` / `ZZTX0;`: снимает передачу **только свою** — если TX-источник именно этот
слайс. `RX;` на порту слайса C не обрывает эфир слайса B. TUNE снимается тоже.
Выбор TX-источника после отпускания PTT **остаётся** на этом слайсе (как в
SmartSDR: Transmit Slice переключён, а не одолжен). Бейдж на флаге это показывает.
`GetTransmitting` (для `TX`/`IF`/`ZZTX`) на порту слайса = «передаёт **этот**
слайс». Чужой эфир виден как приём — иначе управляющее ПО решило бы, что его PTT
прошла.
---
## 3. Что поддерживает CAT-порт слайса
| Команды | Маппинг на слайс |
|---|---|
| `FA` `ZZFA` (VFO A) | `TuneSliceInBand` — свободно в пределах включённого диапазона, см. §3.1 |
| `FB` `ZZFB` (VFO B) | теневое значение: читается/пишется, эфир не двигает (второго VFO у слайса нет) |
| `MD` `ZZMD` `ZZMA` | `SetSliceMode` |
| `FW` `SH` `SL` `ZZFI` (индекс фильтра) | `FilterIdxFor`/`FilterBWFor``SetSliceModeBW` |
| `ZZFL` `ZZFH` (кромки, Гц) | `FilterLo/FilterHi``SetSliceFilter` |
| `GT` `ZZGT` (AGC) | `AGC``SetSliceAGCMode` (`AGCModeToUI`/`FromUI`) |
| `AG` `ZZAG` (громкость) | `Volume``SetSliceVolume` |
| `ZZNR` `ZZNB` `ZZNS` `ZZNT` (DSP) | `SetSliceDSP` (состояние зеркалится в `TCtrlSlice`) |
| `SQ` `ZZSQ` `ZZSO` (squelch) | `FMSQOn/FMSQLevel``SetSliceFMSquelch` |
| `SM` `ZZSM` | `SliceSMeter` |
| `TX` `RX` `ZZTX` | `RequestSliceTx` (см. §2) |
| `PS` | слайс существует **и** радио запущено |
| `IF` `ID` | составной статус/идентификатор по данным слайса |
| `UP` `DN` `RU` `RD` | шаг ±10 Гц по частоте слайса |
| `PC` `ZZPC` (мощность), `ZZBS` (диапазон, read) | глобальные — передатчик один на всё радио |
### 3.1 Частота слайса: свобода в пределах диапазона
`TRadioController.TuneSliceInBand(Id, TargetHz)` — единственная точка перестройки
слайса из CAT:
1. **Другой диапазон → отказ.** Границы берутся у включённого сейчас диапазона:
активный трансвертер → его `FreqBegin/FreqEnd` (2 м = 144…146 МГц), иначе
band-план бэкенда (`BoardUtils.BandEdges`). Стоим на 2 м, программа просит
145.500 → 144.174 — можно; 7.074 МГц — команда отбрасывается. Смена диапазона
остаётся решением оператора.
2. Цель внутри захваченной полосы (`SliceFitsCapture`) → просто `SetSliceTarget`.
3. Цель внутри диапазона, но вне окна DDC → **окно переезжает центром на цель**:
* слайс на дополнительном пане (свой DDC) — свободно (`SetPanDDCFreq` +
`Changed(rfPanFreq)`, UI перекладывает флаги/шапку/зум-бар);
* слайс на пане 0 — окно общее с главным приёмником, поэтому центр ставится
ПОСЕРЕДИНЕ между целью и активным VFO: так в окно попадают оба. Если они
дальше, чем окно (`|цель VFO| > 0.95·полоса`), команда отбрасывается —
перестраивать главный приёмник ради слайса нельзя.
Практический вывод: чтобы внешняя программа гуляла по всему диапазону, слайс
лучше держать на своём пане (дополнительный DDC). На пане 0 её свобода —
в пределах общего окна вокруг главного VFO.
**Осознанно не проброшено** (движок ответит валидной заглушкой): split/`FR`/`FT`
(второго VFO нет), смена диапазона `BU`/`BD`/`ZZBS`-set (диапазон — свойство пана,
на котором живёт слайс), CTUN, AGC-T, CTCSS/репитер/FM-шаг (настройки главного
тракта), Andromeda (панель одна и висит на главном).
---
## 4. Настройки
Вкладка **Slices**: на каждый слайс B..G — `Enable CAT TCP server`, `Port`
(по умолчанию 19091, 19092, …), `Auto TX`, `Audio out`, `Audio in`.
### 4.1 Аудио слота
`Audio out`/`Audio in` — это **устройство слота (буквы)**, а не шаблон:
* смена настройки применяется немедленно к слайсу, который стоит на слоте
сейчас (`TRadioController.SetSliceSlotAudio`);
* слайс, который встанет на слот позже (создание/восстановление из персиста),
получает то же устройство — оно сильнее того, что попросил вызывающий
`AddSlice`;
* выбор во вкладке `AUD` флага слайса пишется обратно в настройку слота
(`SetSliceDevice`/`SetSliceInDevice``MainForm.StoreSliceSlotAudio`), так
что вкладка всегда показывает реальное устройство — одна истина, два места
правки;
* пусто (`Default (app output)` / `None (app input)`) — как раньше: общий выход
приложения и общий вход.
Хранятся **имена** PortAudio-устройств, а не индексы: порядок перечисления между
запусками плавает. Имя отсутствующего сейчас устройства (кабель не поднят) не
теряется — комбо добавляет его в список и держит выбранным.
* Хранятся вместе с остальным CAT: корневая секция `cat` (`slice_en_N`,
`slice_port_N`, `slice_autotx_N`) + device-blob (`cat_slice_*`), не привязаны
к MAC — доступны до подключения железа.
* Порт, совпадающий с основным TCP-портом или с портом слайса меньшей буквы,
**не поднимается** (иначе второй `bind` просто упал бы молча).
* Правка одной строки не рвёт живые сессии остальных: эндпоинт перезапускается
только при реальном изменении своего enable/порта.
---
## 5. Проверка
```
# слайс B на 19091
printf 'ZZFA;' | nc -q1 127.0.0.1 19091 # частота слайса
printf 'ZZMD;' | nc -q1 127.0.0.1 19091 # режим
printf 'TX;' | nc -q1 127.0.0.1 19091 # PTT (нужен Auto TX либо выбранный TX-слайс)
printf 'RX;' | nc -q1 127.0.0.1 19091
```
Статус: собрано (`lazbuild --ws=qt6` + `build-ewsdrd.sh`), **на эфире и на экране
не проверено**.