mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
У каждого слайса 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>
181 lines
13 KiB
Markdown
181 lines
13 KiB
Markdown
# 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`), **на эфире и на экране
|
||
не проверено**.
|