mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
feat(cat): CAT-порт на каждый слайс + Auto TX (эталон SmartSDR CAT)
Внешняя программа управляет отдельным слайсом как отдельным трансивером
через свой TCP-порт. COM-порты и 19090 остаются за главным приёмником
(слайс A) и не тронуты.
Протокол не дублируется: TCATEngine — чистый парсер поверх записи
callback'ов TCATContext, TCATTcpServer — транспорт поверх движка, поэтому
слайс-CAT = тот же движок с другим контекстом. Новый TCATSliceEndpoint
(движок + TCP-сервер на слот), массивом владеет TCATAdapter. Привязка к
СЛОТУ (буква B..G), не к Id: порт живёт, даже когда слайса нет (PS0).
Auto TX = «Auto Switch TX Slice» из SmartSDR CAT. Вся логика в одной точке
— TRadioController.RequestSliceTx, исполняется в потоке контроллера:
• пока кто-то уже в эфире (любой источник) — заявка игнорируется,
перехвата передачи нет никогда;
• без Auto TX слайс передаёт, только если уже выбран TX-источником;
• RX; снимает только СВОЮ передачу;
• TX-источник после отпускания остаётся на слайсе (как в SmartSDR).
На флаге слайса бейдж TX → AutoTX.
Частота слайса — TuneSliceInBand: свобода в пределах ВКЛЮЧЁННОГО диапазона
(трансвертер → его FreqBegin/FreqEnd, иначе band-план), другой диапазон —
отказ. Вне захваченной полосы окно DDC переезжает: доп. пан — центром на
цель (rfPanFreq → UI перекладывает флаги/шапку/зум-бар), пан 0 — центр
посередине между целью и главным VFO и только если оба влезают (общее
железное окно, главный приёмник не оглушаем).
Настройки: вкладка Slices (enable/порт 19091../Auto TX на слайс), персист
в секции cat + device-blob.
Попутно:
• ZZFL/ZZFH больше не заглушки — новые callback'и кромок фильтра
(у главного nil → прежний дефолт режима), хелпер SignedPad;
• FilterIdxFor, AGCModeToUI, BoardUtils.BandEdges;
• DSP-состояние слайса (NR/NB/SNB/ANF) зеркалится в TCtrlSlice — флаг
слайса рисовал нули и AGC главного;
• FUIReady в TMainForm.OnControllerState: адаптеры, создаваемые по ходу
FormCreate, больше не могут уронить старт рендером до постройки
виджетов (на этом падал Auto TX из настроек).
Проверено вживую: все порты 19091..19096 слушают, PS;/ZZFA;/MD;/ID;/SM0;/
ZZTX; отвечают; старт со всеми включёнными слайсами чист под gdb.
Документ — doc/CAT_SLICES.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
# 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`.
|
||||
|
||||
* Хранятся вместе с остальным 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`), **на эфире и на экране
|
||||
не проверено**.
|
||||
@@ -188,6 +188,10 @@ CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
|
||||
|
||||
## 5. Транспорты
|
||||
|
||||
> CAT **дополнительных слайсов** (свой TCP-порт на слайс B..G + Auto TX) вынесен
|
||||
> в отдельный документ: [`CAT_SLICES.md`](CAT_SLICES.md). Тот же движок, другой
|
||||
> контекст; транспорты ниже принадлежат главному приёмнику (слайс A).
|
||||
|
||||
- **Serial** (`CATSerial.pas`): до 4 портов, параметры (порт/скорость/биты/
|
||||
чётность/стоп) из `TGlobalSettings.CATSerial*`, применяются
|
||||
`TCATAdapter.ApplySettings`. На текущей платформе сборки может быть
|
||||
|
||||
Reference in New Issue
Block a user