# 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`), **на эфире и на экране не проверено**.