12 KiB
CAT дополнительных слайсов (Slice CAT)
Ветка: feature/slice-cat. Дата: 2026-07-25.
Внешняя программа (WSJT-X, FLDIGI, лог) управляет отдельным слайсом EWSDR как отдельным трансивером — через свой TCP-порт. COM-порты и основной TCP-порт (19090) остаются за главным приёмником (слайс A) и не тронуты.
Каждый порт привязан к определённому слайсу. Флаг Auto TX разрешает команде передачи автоматически назначить этот слайс источником TX; без флага источник выбирается оператором вручную.
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; на порту слайса:
- Слайса на слоте нет → игнор.
- Кто-то уже передаёт (другой слайс, главный VFO, кнопка MOX/TUNE, аппаратная PTT) → игнор. Перехвата эфира нет никогда — передаёт всегда только один слайс.
- Режим слайса
DMR→ игнор (AMBE-передатчика нет). - Слайс не выбран TX-источником:
- Auto TX включён →
SetTxSlice(слайс), затемSetMOX(True); - Auto TX выключен → игнор (оператор назначает источник руками —
бейдж
TXна флаге слайса).
- Auto TX включён →
- Слайс уже TX-источник → просто
SetMOX(True).
RX; / ZZTX0;: снимает передачу только свою — если TX-источник именно этот
слайс. RX; на порту слайса C не обрывает эфир слайса B. TUNE снимается тоже.
Выбор TX-источника после отпускания PTT остаётся на этом слайсе. Бейдж на флаге это показывает.
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:
- Другой диапазон → отказ. Границы берутся у включённого сейчас диапазона:
активный трансвертер → его
FreqBegin/FreqEnd(2 м = 144…146 МГц), иначе band-план бэкенда (BoardUtils.BandEdges). Стоим на 2 м, программа просит 145.500 → 144.174 — можно; 7.074 МГц — команда отбрасывается. Смена диапазона остаётся решением оператора. - Цель внутри захваченной полосы (
SliceFitsCapture) → простоSetSliceTarget. - Цель внутри диапазона, но вне окна DDC → окно переезжает центром на цель:
- слайс на дополнительном пане (свой DDC) — свободно (
SetPanDDCFreq+Changed(rfPanFreq), UI перекладывает флаги/шапку/зум-бар); - слайс на пане 0 — окно общее с главным приёмником, поэтому центр ставится
ПОСЕРЕДИНЕ между целью и активным VFO: так в окно попадают оба. Если они
дальше, чем окно (
|цель − VFO| > 0.95·полоса), команда отбрасывается — перестраивать главный приёмник ради слайса нельзя.
- слайс на дополнительном пане (свой DDC) — свободно (
Практический вывод: чтобы внешняя программа гуляла по всему диапазону, слайс лучше держать на своём пане (дополнительный 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), на эфире и на экране
не проверено.