mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat(tci): приёмник = слот слайса; TRX/TUNE адресуют передатчик; стенд «как MSHV»
Модель приёмников переделана: приёмник TCI — это СЛОТ СЛАЙСА, а не панадаптер.
rx0 — главный тракт (каналы A/B = VFO A/B), rx N — слайс слота N−1, то есть
буквы B..G с флага, на каком бы пане он ни стоял. Панорама стала свойством
приёмника: от неё берутся DDS (у слайса только чтение) и поток IQ.
Почему: у Pluto панорама ровно одна (MaxPans = 1), и второй приёмник там
существует ТОЛЬКО как слайс главного пана — при нумерации по панам он был
недоступен вовсе, а TRX_COUNT навсегда равнялся единице. С другого конца —
клиенты: у MSHV в настройках всего «TCI Client rx1/rx2», то есть приёмники 0 и
1, третьего номера ввести некуда. Со слотами правило «первый созданный слайс =
приёмник 1» держится на любом железе: на openHPSDR слайс второго пана и на
Pluto слайс главного одинаково занимают слот B. Номер совпадает с буквой на
экране и с портом слайс-CAT. Цена: панорама без слайсов из TCI пропала, а
второй слайс пана перестал быть «каналом B» и стал своим приёмником — у канала
B в протоколе только частота, IF и громкость, у приёмника же всё.
TRX/TUNE раньше игнорировали arg1 (номер передатчика) целиком: клиент доп.
приёмника уводил в эфир слайс ОПЕРАТОРА — чужая частота, а с кросс-бандовым
мультислайс-TX и чужой диапазон, с чужими антенной и фильтрами; trx:9,true жал
PTT. Теперь номер разбирается и проверяется, приёмник N > 0 идёт через
RequestSliceTx (та же дверь, что у CAT-порта слайса: «в эфире только один» и
Auto TX), у контроллера появился параметр Tune для TUN тем же путём. Чужую
передачу не трогаем вовсе — ни источник модуляции, ни тон: SetMOX(True) поверх
идущей передачи не выходит рано, а заново выбирает микрофон. Хозяином эфира
клиент становится, только если передача началась именно от его команды, и
решает это Sync-метод в потоке контроллера (снимок «шла ли передача», взятый в
потоке клиента, врал: между разбором и исполнением влезает PTT оператора).
Разбор исходников MSHV (он фильтрует ВСЕ строки и бинарные блоки по номеру
приёмника) дал ещё три правки:
* ответ на TRX/TUNE адресуется номером АВТОРА, состояние в нём — «в эфире
именно твой слайс»; в рассылку идёт номер реально передающего;
* tx_enable рассылается каждому живому приёмнику со своим номером и входит в
картину нового приёмника — без этого у MSHV молча мёртвая PTT
(set_ptt начинается с `if (!tci_tx_enable) return;`);
* про несуществующий приёмник молчим целиком (LiveRx), в том числе на чтение:
ответ «vfo:1,0,0» MSHV принимал бы за конец инициализации.
Попутно, вне TCI: SendDUCSpecificFromSettings трогала FNetwork без Assigned, а
зовут её по любому PTT/TUN (SetMOX → SyncCWKeyer → она) — до подключения
устройства это была Access violation, у TCI её глотал обработчик команды.
Стенды: новый test/tci/mshv_sim.py — точная копия логики клиента MSHV, отвечает
на вопрос «почему он не подключается» одной строкой (на живом приложении
воспроизвёл ошибку инициализации для rx2 до правки). tcitest — 158/158, в
сквозном прогоне добавлено создание слайса на главном пане: он становится
приёмником 1, отвечает на vfo:1,0, слушается командой, отдаёт аудио с
receiver = 1 и замолкает после удаления.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+204
-35
@@ -165,12 +165,37 @@ UI — вкладка **CAT → TCI Server**, справа от «TCP CAT Server
|
||||
|
||||
| TCI | EWSDR |
|
||||
|---|---|
|
||||
| приёмник (`TRX_COUNT`) | панадаптер: 0 = главный тракт, 1.. = доп. DDC-паны. Число = `BackendCaps.MaxPans` |
|
||||
| канал A/B (`CHANNEL_COUNT` = 2) | у приёмника 0 — VFO A / VFO B; у панов 1.. — первый и второй слайс пана |
|
||||
| `DDS` | центр панадаптера (`SetCenter` / `SetPanDDCFreq`) |
|
||||
| `IF` | смещение канала от центра панорамы |
|
||||
| `VFO` | абсолютная частота канала |
|
||||
| передатчик (`arg1` у TRX/TUNE/DRIVE) | всегда один — главный тракт |
|
||||
| приёмник (`TRX_COUNT` = 7) | 0 — главный тракт; N — **слайс слота N−1**, то есть буквы B..G с флага, на каком бы пане он ни стоял. Потолок = 1 + `MAX_SLICES` |
|
||||
| канал A/B (`CHANNEL_COUNT` = 2) | у приёмника 0 — VFO A / VFO B; у приёмника-слайса канал один (A): второго саб-приёмника внутри слайса не бывает |
|
||||
| панорама | не адресуется: стала свойством приёмника — от неё берутся `DDS` и поток IQ |
|
||||
| `DDS` | центр панорамы, на которой стоит приёмник. Пишется только у приёмника 0 (`SetCenter`); у слайса читается |
|
||||
| `IF` | смещение канала от центра его панорамы |
|
||||
| `VFO` | абсолютная частота канала (у слайса — `TuneSliceInBand`) |
|
||||
| передатчик (`arg1` у TRX/TUNE) | передатчик один, но `arg1` называет, ЧЕЙ слайс должен в него попасть: 0 — главный VFO (как обычный CAT-порт), N — слайс слота N−1 через `RequestSliceTx`. См. §2.7 |
|
||||
| `arg1` у `DRIVE`, `TUNE_DRIVE` | мощность одна на радио — номер не адресует ничего |
|
||||
|
||||
★**Почему приёмник — слайс, а не панорама.** Сперва приёмником был панадаптер,
|
||||
как в ExpertSDR3, где приёмник = DDC. У нас это разошлось с жизнью сразу на
|
||||
двух концах. У Pluto панорама ровно одна (`BackendCaps.MaxPans = 1`), и второй
|
||||
приёмник существует там **только** как слайс главного пана — при нумерации по
|
||||
панам он был бы недоступен вообще, а `TRX_COUNT` навсегда равнялся единице. С
|
||||
другого конца — клиенты: у MSHV в настройках всего две модели, «TCI Client rx1»
|
||||
и «rx2», то есть приёмники 0 и 1, и никакого третьего номера ввести некуда
|
||||
(`hvrigcontrol.cpp`: `netServPort[3]`/`[4]`, `network.cpp`: `tci_trx`). Значит
|
||||
«первый добавленный слайс» обязан быть приёмником **1** на любом железе.
|
||||
|
||||
Слот слайса даёт ровно это. Слоты глобальные и стабильные, у каждого своя
|
||||
буква на флаге (`SliceIdBySlot`, `SliceSlotLetter`), и по ним же раздаются
|
||||
порты слайс-CAT — то есть номер приёмника TCI совпадает с буквой, которую
|
||||
оператор видит на экране: B → 1, C → 2, … Первый созданный слайс занимает слот
|
||||
B независимо от того, где он живёт: на openHPSDR это может быть слайс второго
|
||||
пана, на Pluto — слайс главного, и в обоих случаях он приёмник 1.
|
||||
|
||||
Цена: панорама без единого слайса из TCI пропадает (слушать там нечего, но и
|
||||
её IQ клиенту недоступен), а второй слайс панорамы перестал быть «каналом B» и
|
||||
стал самостоятельным приёмником — что скорее выигрыш: у канала B в протоколе
|
||||
есть только частота, IF и громкость, а у приёмника — мода, фильтр, АРУ,
|
||||
шумодавы, squelch, свои потоки и `TRX`.
|
||||
|
||||
---
|
||||
|
||||
@@ -209,19 +234,20 @@ UI — вкладка **CAT → TCI Server**, справа от «TCP CAT Server
|
||||
— особенно важно при split и TX-слайсе), текущий `APP_FOCUS` и `VFO_LOCK` на
|
||||
каждый канал.
|
||||
|
||||
**У живого пана канал A есть всегда.** Выключить канал A в TCI нечем — он
|
||||
существует по определению, поэтому пан без слайсов показывает канал A на своём
|
||||
центре (`DDS`), а не хранит частоту удалённого слайса. Команды на такой канал
|
||||
игнорируются: слайса под ним нет. Как только слайс появится, канал станет
|
||||
настоящим.
|
||||
**Приёмник живёт ровно столько, сколько его слайс.** Удалили слайс — приёмник
|
||||
исчез вместе с ним: ни частоты, ни состояния, ни потоков. Создали снова — он
|
||||
займёт первый свободный слот, то есть, как правило, тот же номер (так же
|
||||
устроены и порты слайс-CAT, привязанные к слоту).
|
||||
|
||||
**Приёмники, которых ещё нет, молчат.** `TRX_COUNT` объявляет потолок железа
|
||||
(`BackendCaps.MaxPans`) один раз и навсегда, а пан из этого потолка может быть
|
||||
не создан. Для несуществующего пана не шлётся ничего (раньше уходили
|
||||
`dds`/`vfo`/`if` с нулём, и клиент принимал ноль за настоящую частоту);
|
||||
состояние приходит, когда пан появится — с `rfPanFreq`/`rfSliceState`. У
|
||||
существующего пана без слайсов есть только `DDS`. Показания измерителей для
|
||||
таких приёмников тоже не отправляются.
|
||||
**Приёмники, которых ещё нет, молчат.** `TRX_COUNT` объявляет потолок (1 + 6
|
||||
слотов слайсов) один раз и навсегда, а слот из этого потолка может быть пуст.
|
||||
Про пустой слот не шлётся ничего — ни `vfo`, ни `dds`, ни показания
|
||||
измерителей, и команды к нему тоже игнорируются целиком (`LiveRx`). Раньше
|
||||
уходили `dds`/`vfo`/`if` с нулём, и клиент принимал ноль за настоящую частоту;
|
||||
для MSHV это вообще фатально — именно ответом на `vfo:<rx>,0;` он завершает
|
||||
инициализацию, то есть подключился бы к пустоте. Состояние появляется в момент
|
||||
создания слайса: `PushChannelMap` рассылает картину нового приёмника целиком,
|
||||
включая `tx_enable` с его номером.
|
||||
|
||||
Список видов связи: `am,sam,dsb,lsb,usb,cw,nfm,wfm,digl,digu,dmr,fmraw`.
|
||||
`dmr`/`fmraw` — наше расширение (протокол расширяемый, §1.4). `CWL`/`CWU`
|
||||
@@ -264,8 +290,8 @@ UI — вкладка **CAT → TCI Server**, справа от «TCP CAT Server
|
||||
| `DDS` | `SetCenter` / `SetPanDDCFreq` |
|
||||
| `IF`, `VFO` | `SetVfoA/B`, `TuneSliceInBand` |
|
||||
| `MODULATION` | `SetMode` / `SetSliceMode` |
|
||||
| `TRX` | `SetMOX` |
|
||||
| `TUNE` | `SetTune` |
|
||||
| `TRX` | `SetMOX` (приёмник 0) / `RequestSliceTx` (приёмник N) |
|
||||
| `TUNE` | `SetTune` (приёмник 0) / `RequestSliceTx(…, Tune)` (приёмник N) |
|
||||
| `DRIVE` | `SetDrive` |
|
||||
| `TUNE_DRIVE` | `FTXSettings.TUNLevel` через `SetTXSettings` |
|
||||
| `SPLIT_ENABLE` | `SetSplit` |
|
||||
@@ -494,6 +520,71 @@ ExpertSDR3 давно бы не было.
|
||||
скорости и не знает прос-знаков. Доотправка позывного (`cw_msg:arg1;`)
|
||||
игнорируется: уже отданный в очередь текст не редактируется.
|
||||
|
||||
### 2.7 Кто именно уходит в эфир (`TRX`, `TUNE`)
|
||||
|
||||
★`arg1` у этих двух команд — **номер передатчика**, и он не декорация. Клиент
|
||||
живёт на своём приёмнике: скиммер на пане 2, цифра на пане 1, логгер на
|
||||
главном. Нажимая передачу, он просит эфир **своему** слайсу — а не тому,
|
||||
который оператор выбрал мышкой минуту назад. Пока номер игнорировался,
|
||||
`trx:2,true` уводил в эфир чужой слайс: другая частота, а с кросс-бандовым
|
||||
мультислайс-TX — и другой диапазон, то есть чужие антенна и фильтры. Клиент об
|
||||
этом не узнавал никак (ответ всегда говорил `trx:0,…`), а оператор видел
|
||||
передачу там, где её никто не просил.
|
||||
|
||||
Как устроено теперь:
|
||||
|
||||
- номер разбирается и проверяется, как у всех остальных команд:
|
||||
не число или вне `TRX_COUNT` — `tci_error … bad receiver`; номер в потолке,
|
||||
но пана под ним нет — `receiver is not running`. Свести такую просьбу к
|
||||
главному VFO нельзя: молча передать не на той частоте хуже, чем отказать;
|
||||
- **приёмник 0** — это «радио целиком», то есть главный VFO. Ведём себя как
|
||||
обычный CAT-порт: в эфир уходит выбранный оператором TX-источник;
|
||||
- **приёмник N > 0** адресует слайс слота N−1, и заявка идёт через
|
||||
`TRadioController.RequestSliceTx` — ту же дверь, что у CAT-порта слайса
|
||||
(см. `doc/CAT_SLICES.md`). Оттуда бесплатно достаются оба правила: «в эфире
|
||||
одновременно только один» (чужую передачу не перехватываем) и уважение к
|
||||
флагу **Auto TX** (без него слайс сам себя источником не назначает);
|
||||
- `TUNE` ходит той же дорогой — `RequestSliceTx(…, Tune := True)`: несущая
|
||||
настройки обязана вставать на тот же слайс, что и модуляция;
|
||||
- **ответ автору называет ЕГО номер приёмника**, а состояние в нём — «в эфире
|
||||
именно твой слайс» (`FTransmitting and (TxRx = Rx)`). Так надо потому, что
|
||||
клиенты фильтруют входящие строки по `arg1`: MSHV, например, отбрасывает
|
||||
всё, что адресовано не его приёмнику (`network.cpp`:
|
||||
`if (ls2.at(0)!=tci_trx) continue;`), — и отказ, названный чужим номером, до
|
||||
него бы просто не дошёл. В **рассылку** уходит номер того, чей слайс сейчас
|
||||
источник передачи (`TxRx` по `TxSliceId`): клиент на приёмнике 1 по ней
|
||||
видит, что в эфире не он. Смена TX-источника оператором доходит до клиентов
|
||||
сама: `SetTxSlice` заканчивается `Changed(rfTransmitting)`;
|
||||
- ключ захвата (§3.5) — **один на передатчик**, `TRX/0/0`: за него спорят и
|
||||
`TRX`, и `TUNE`, и изменения от оператора (`rfTransmitting`, `rfTuning`).
|
||||
Отдельный ключ `TUNE` делал защиту дырявой — TUN поверх чужого MOX не спорил
|
||||
ни с кем;
|
||||
- **чужую передачу не трогаем вовсе.** Пока передатчик занят (оператор, другой
|
||||
клиент, аппаратная PTT), `TRX:…,true` и `TUNE:…,true` не делают ничего — то же
|
||||
правило, что внутри `RequestSliceTx`, только теперь оно работает и для
|
||||
приёмника 0. Дырок было две: `SetMOX(True)` **не выходит рано** на уже идущей
|
||||
передаче — он заново выбирает источник модуляции, и `trx:0,true,tci` посреди
|
||||
передачи оператора уводил её в ринг TCI (а уход клиента оставлял в эфире
|
||||
несущую с тишиной: хозяином он при этом не становился, снимать было некому);
|
||||
`tune:0,true` таким же образом подмешивал настроечный тон в чужую передачу и
|
||||
оставался в эфире после ухода;
|
||||
- хозяином эфира (`FTrxOwner`, чей уход эфир снимает) клиент записывается,
|
||||
только если передача началась **именно от его команды**. Знает об этом лишь
|
||||
сам Sync-метод — он один видит состояние передатчика до и после, на потоке
|
||||
контроллера. Снимок «шла ли передача», взятый в потоке клиента до `Invoke`,
|
||||
врал бы: между разбором команды и её исполнением оператор успевает нажать
|
||||
PTT, и хозяином стал бы не тот. Поэтому решение принимается внутри, а наружу
|
||||
возвращается один флаг (`FsRes`, под тем же `FLock`, что и аргументы);
|
||||
- по той же причине отказ откатывает `TCIMicRequested`: иначе следующая PTT
|
||||
оператора ушла бы в эфир с микрофоном ушедшего клиента.
|
||||
|
||||
Попутно на этой дороге нашлась Access violation, не связанная с TCI:
|
||||
`SendDUCSpecificFromSettings` трогала `FNetwork` без `Assigned`, а зовут её по
|
||||
любому PTT/TUN (`SetMOX` → `SyncCWKeyer` → она), в том числе **до подключения
|
||||
устройства**. В GUI падение маскировалось, у TCI ошибку глотал обработчик
|
||||
команды — и «передача» жила только в ответе клиенту. Теперь там обычная
|
||||
проверка, как в остальных двенадцати местах контроллера.
|
||||
|
||||
---
|
||||
|
||||
## 3. Осознанные заглушки («эхо»)
|
||||
@@ -512,24 +603,78 @@ ExpertSDR3 давно бы не было.
|
||||
| `DIGL_OFFSET`, `DIGU_OFFSET` | смещения цифровых мод не реализованы |
|
||||
| `RX_CHANNEL_ENABLE` | канал B главного приёмника — это VFO B, он есть всегда; создание второго слайса на пане по TCI не делаем (см. §4) |
|
||||
|
||||
**Почему именно заглушки, а не «сделать по-быстрому».** Правило одно: *TCI не
|
||||
заводит в радио состояние, которого оператор не видит на экране и не может
|
||||
снять*. Клиент подключается и отключается когда ему вздумается, а оставленный
|
||||
им перекос остаётся в аппарате навсегда — искать его будет человек, у которого
|
||||
нет ни кнопки, ни индикации. Поэтому настоящими сделаны ровно те параметры, у
|
||||
которых в интерфейсе есть орган управления (`NR`/`NB`/`ANF`, АРУ, фильтр,
|
||||
громкость, squelch, скорость CW), а остальные приняты, сохранены и разосланы
|
||||
ради честной синхронизации клиентов между собой — и только.
|
||||
|
||||
Дальше — построчно, потому что причины у них разные:
|
||||
|
||||
- **`RIT`/`XIT`.** Расстройка — не кнопка, а сквозной сдвиг: `VFO` → DDC/DUC,
|
||||
панорама, флаг слайса, CAT, каналы памяти. Сделанная **только** ради TCI, она
|
||||
сразу разошлась бы с тем, что показывает экран и что отвечает CAT-логгеру, —
|
||||
и это худший вид ошибки в аппарате: слышно одно, написано другое. Заводить
|
||||
надо целиком, как фичу радио, тогда TCI получит её даром.
|
||||
- **`RX_BIN_ENABLE`, `RX_BALANCE`.** Единственные два, где тракт-то есть:
|
||||
`SetRXAPanelBinaural` и `SetRXAPanelPan` в биндингах WDSP лежат готовые. Но в
|
||||
EWSDR нет ни органа управления, ни поля в профиле, ни персиста, — включённое
|
||||
клиентом псевдостерео или уехавший баланс оператор не найдёт чем вернуть.
|
||||
Порядок обратный: сперва UI, потом TCI.
|
||||
- **`RX_ANC_ENABLE`, `RX_APF_ENABLE`, `RX_DSE_ENABLE`, `RX_NF_ENABLE`.** Этих
|
||||
узлов в тракте нет физически. У нас `NR`/`NR2` (EMNR), `NB`/`NB2` (ANB/SNBA)
|
||||
и `ANF` — они в TCI настоящие. `ANC` и `DSE` — блоки собственной обработки
|
||||
Expert, не WDSP; `APF` и `NF` были бы новыми узлами в цепочке.
|
||||
- **`RX_NB_PARAM`.** Пороги шумодава у нас — подобранные константы
|
||||
`FILTER_NB_*`, одинаковые для всех каналов и не выведенные даже в интерфейс.
|
||||
Клиент, покрутивший их по TCI, оставил бы шумодав расстроенным насовсем.
|
||||
- **`DIGL_OFFSET`, `DIGU_OFFSET`.** `DIGU`/`DIGL` у нас — обычные SSB-моды со
|
||||
своим фильтром, отдельного смещения в тракте нет. Введённое ради одной
|
||||
команды, оно опять развело бы частоту на панораме с той, что слышно.
|
||||
|
||||
## 3.1 Ограничения, о которых честнее знать заранее
|
||||
|
||||
| Что | Как ведёт себя |
|
||||
|---|---|
|
||||
| `AGC_GAIN` у приёмника > 0 | AGC-T в ewsdr один на приёмный тракт, у слайса своего нет. Команда от имени доп. приёмника **игнорируется** (раньше молча правила главный), в ответ уходит текущее значение |
|
||||
| `DDS` у приёмника-слайса | только читается (центр его панорамы): панорама под слайсом общая, и увести её по просьбе одного клиента значит утащить соседей по пану и картинку оператора. Слайсу двигаться незачем — за окном DDC следит `TuneSliceInBand` |
|
||||
| Панорама без слайсов | в TCI не видна вовсе: приёмник = слайс. Слушать там нечего, но и IQ такой панорамы клиенту недоступен |
|
||||
| Цвет спота (`SPOT`, arg4 ARGB) | не читается: `TDXSpot` цвета не хранит, подписи красятся по моде/возрасту |
|
||||
| `KEYER`, `TX_FOOTSWITCH` | не реализованы: своего ключа-уведомления и опроса педали наружу у контроллера нет |
|
||||
| Мода, фильтр, АРУ, шумодавы у канала B доп. пана | в TCI это свойства **приёмника**, а не канала: они относятся к каналу A. У канала B по протоколу есть только частота, IF и громкость |
|
||||
| `KEYER`, `TX_FOOTSWITCH` | не реализованы, причины разные — см. ниже под таблицей |
|
||||
| Канал B | есть только у приёмника 0 (VFO B). У приёмника-слайса канал один: слайс — это и есть «приёмник» целиком, со своими модой, фильтром, АРУ, шумодавами и потоками |
|
||||
| Захват параметра (§3.5) | реализован для того, что клиенты действительно перетягивают (частота, DDS, мода, фильтр, TRX/TUNE/DRIVE, split, громкости, АРУ, шумодавы, squelch, скорость CW). Эхо-параметры (RIT/XIT, BIN/ANC/…) не захватываются: на радио они не влияют |
|
||||
| Браузерные клиенты | отвергаются по `Origin` (403), см. §1.1. Web-интерфейсу ewsdr TCI не нужен — у него свой канал |
|
||||
| `TRX_COUNT` | равен `BackendCaps.MaxPans`, а не числу живых панов: протокол объявляет его один раз. Про несуществующий приёмник просто ничего не шлётся (§2.1) |
|
||||
| `TRX_COUNT` | равен потолку 1 + `MAX_SLICES` = 7, а не числу живых приёмников: протокол объявляет его один раз. Про несуществующий приёмник просто ничего не шлётся (§2.1) |
|
||||
| Потоки на передаче | RX-аудио и линейный выход на TX замолкают — движок не зовёт аудио-колбэки, пока идёт передача (кроме дуплекса с самоконтролем). То же и с IQ: RX-пакеты на TX дропаются на входе DSP. Это поведение приёмного тракта, а не потоков |
|
||||
| `MUTE` и линейный выход | глушит и поток: движок под мьютом не зовёт `OnAudio`. Аудиопоток приёмника (`AUDIO_START`) мьют не трогает — он снимается до громкости |
|
||||
| DIGL/DIGU, 2 канала | по §3.4 в цифровых модах два канала должны нести комплексный сигнал; у нас это обычное стерео с выхода WDSP. Комплексный вывод демодулятора наружу не выведен |
|
||||
| Поток канала B доп. пана | не бывает: в протоколе аудиопоток один на приёмник, и мы отдаём канал A (первый слайс) |
|
||||
| DIGL/DIGU, 2 канала | по §3.4 в цифровых модах два канала должны нести комплексный сигнал; у нас это обычное стерео с выхода WDSP — оба тапа стоят там, где сигнал уже вещественный: `rakDemod` сразу за демодулятором, `rakLineOut` за громкостью. Комплексный выход потребовал бы отдельной ветки панели RXA и третьего маршрута тапа, а цифровые клиенты берут `IQ_START` — он честно комплексный |
|
||||
| Поток канала B | не бывает: в протоколе аудиопоток один на приёмник, и он всегда про канал A |
|
||||
| Формат IQ | всегда float32, два канала — `AUDIO_STREAM_SAMPLE_TYPE` относится к аудио (§4.3), а ExpertSDR3 IQ иначе и не шлёт |
|
||||
| `IQ_SAMPLERATE` 384 кГц на Pluto 576/960 кГц | нацело не делится, поэтому уходит 192 кГц (см. §2.5). Клиент обязан читать частоту из заголовка блока, а не считать её равной запрошенной |
|
||||
| MP3 у рекордера | не поддержан (кодера в проекте нет): `LINE_OUT_RECORDER_SAVE` с `.mp3` отвечает `tci_error` |
|
||||
| MP3 у рекордера | не поддержан: кодера в проекте нет, а тащить внешний (lame) ради рекордера — это новая зависимость и её лицензия в сборке, которых у ewsdr сейчас нигде нет. WAV пишется без потерь и открывается всем; на `.mp3` уходит честный `tci_error`, а не молчаливый WAV с чужим расширением |
|
||||
|
||||
**`KEYER` и `TX_FOOTSWITCH` — почему их нет.** Команды противоположные по
|
||||
направлению, и мешать их в один пункт «не сделано» неправильно.
|
||||
|
||||
`KEYER` шлёт **клиент**: это его «клоподав», прокинутый к нам, и третий
|
||||
аргумент — длительность предыдущего знака — существует ровно затем, чтобы
|
||||
телеграфное ядро воспроизвело тайминг точно. У нас несущую в CW даёт либо
|
||||
прошивка (бит CWX / аппаратный кейер), либо программный кейер для Pluto, и оба
|
||||
берут фронты **как пришли**. Прокинуть в них фронты из WebSocket значит
|
||||
отправить в эфир сетевой джиттер, а `arg3` подставить некуда: команды «сыграй
|
||||
знак длиной 142 мс» у openHPSDR нет. Точка входа готова (`CWXKeyEvent`), но
|
||||
делать это надо вместе с очередью знаков по `arg3` — и после того, как весь
|
||||
передающий тракт TCI пройдёт живого клиента (он ещё не проверялся).
|
||||
|
||||
`TX_FOOTSWITCH` шлёт **сервер**, и это просто не выведено: флаг у контроллера
|
||||
уже есть — `HPS_PTT` из HP-статуса ловится по фронту (`FHPpHWPTT` →
|
||||
`FHWPTTActive`), — но события наружу нет. Плюс две оговорки, из-за которых
|
||||
уведомление было бы полуправдой: бит не отличает педаль от тангенты микрофона,
|
||||
а у Pluto такой линии нет вовсе. Работы тут — новый `rf`-эвент контроллера и
|
||||
строчка в адаптере.
|
||||
|
||||
Отдельно: у `TX_SENSORS` второй аргумент — уровень микрофона; измерителя
|
||||
микрофона в EWSDR нет, шлём нижнюю границу шкалы (-60 дБм), чтобы клиент не
|
||||
@@ -548,13 +693,24 @@ ExpertSDR3 давно бы не было.
|
||||
Бинарные потоки (§3.4) реализованы целиком — см. §2.5. Открыто:
|
||||
|
||||
1. **`RX_CHANNEL_ENABLE` как реальное создание/удаление второго слайса пана.**
|
||||
Сейчас это эхо: канал B главного приёмника — VFO B, он есть всегда, а
|
||||
заводить слайс по команде клиента значит отдать ему управление раскладкой
|
||||
панорамы оператора.
|
||||
2. **`KEYER`** — своего события «ключ нажат» у контроллера нет.
|
||||
Сейчас это эхо, и причин две. Первая техническая: слайс **заводит MainForm**
|
||||
(`AddSliceAtFreq` + `CreateSliceFlag` — флаг, раскладка, персист), у
|
||||
контроллера есть только `RemoveSlice`. Адаптер TCI, как и CAT, до MainForm
|
||||
не дотягивается и не имеет права: он обязан собираться в демоне, где LCL
|
||||
нет вовсе. Вторая по существу: канал B у ExpertSDR3 — второй приёмник внутри
|
||||
того же DDC, а у нас слайс — полноценная сущность со своим флагом, звуком,
|
||||
входом и CAT-портом. Клиент, «выключивший канал B», снёс бы оператору
|
||||
рабочий слайс вместе с его модой, фильтром и портом. Правильный порядок:
|
||||
сперва завести создание слайса в контроллер (от этого выиграет и демон), и
|
||||
только потом привязать к нему команду.
|
||||
2. **`KEYER`** — см. разбор под таблицей §3.1: нужна очередь знаков по `arg3`,
|
||||
а не проброс сетевых фронтов в ключ.
|
||||
3. **TCI в демоне.** Юниты LCL-free (стенд собирает и гоняет их вместе с
|
||||
`TRadioController` без единого виджета), подключается одной строкой в
|
||||
`ewsdrd.lpr`, как web.
|
||||
`ewsdrd.lpr`, как web. Не сделано намеренно и в этом порядке: в headless
|
||||
сперва попадает то, что уже прошло живого клиента в GUI, где отказ виден
|
||||
глазами. Цена ошибки в демоне выше — грабля с `SIGPIPE` (см. конец §5)
|
||||
убивала именно `ewsdrd`, целиком и молча.
|
||||
4. **Проверка на железе и с настоящим клиентом** — главное, см. конец §5.
|
||||
|
||||
## 5. Проверено
|
||||
@@ -617,7 +773,7 @@ ExpertSDR3 давно бы не было.
|
||||
движков и сети валится с AV — клиент получает `tci_error`, соединение живо) и
|
||||
неразрывность пачки инициализации под крутящейся ручкой.
|
||||
|
||||
### Стенд этапа 2 (бинарные потоки) — 112 проверок, все зелёные
|
||||
### Стенд этапа 2 (бинарные потоки) — 158 проверок, все зелёные
|
||||
|
||||
Отдельная программа (`test/tci/tcitest.pas`, прогон — `test/tci/run.sh`,
|
||||
внешних библиотек не требует) проверяет потоки на четырёх уровнях:
|
||||
@@ -646,12 +802,25 @@ ExpertSDR3 давно бы не было.
|
||||
сыром сокете): отказ на несуществующий приёмник и на нечисловой аргумент,
|
||||
отказ на старт потока с незапущенного пана, подтверждение и отбраковка
|
||||
параметров, `SAVE` без записи и `SAVE` в `.mp3` отвечают ошибкой,
|
||||
`TRX:0,true,tci` без аудиопотока модуляцию не берёт, а с потоком берёт и
|
||||
снимает её по `TRX:0,false` и по уходу клиента; чужой бинарный блок не рвёт
|
||||
соединение; без передачи маркеров `TX_CHRONO` нет. Отдельно — согласование
|
||||
`TRX:0,true,tci` без аудиопотока модуляцию не берёт, а с потоком берёт,
|
||||
реально поднимает передачу и снимает её по `TRX:0,false` и по уходу клиента;
|
||||
чужой бинарный блок не рвёт соединение; без передачи маркеров `TX_CHRONO`
|
||||
нет. Номер передатчика (§2.7): `trx:9`, `trx:abc` и `tune:9` отвечают ошибкой
|
||||
и **не поднимают эфир**, приёмник без живого пана — тоже; ответ называет
|
||||
передающий приёмник; `TUNE` через TCI включается и гасится, не оставляя за
|
||||
собой ни тона, ни поднятой PTT. Чужая передача: поверх MOX оператора `TRX`
|
||||
не уводит микрофон и не снимает передачу, `TUNE` не включает тон, а уход
|
||||
такого клиента передачу оператора не гасит. Конкурирующий TCI-клиент
|
||||
не получает `TX_CHRONO` чужой передачи, а его уход не снимает эфир
|
||||
первого клиента. Отдельно — согласование
|
||||
частот: на источнике 192 кГц просьба «384» подтверждается как 192, смена
|
||||
rate устройства сама переобъявляет и `iq_samplerate`, и `if_limits`, а на
|
||||
576 кГц (Pluto) та же просьба даёт законные 192 кГц.
|
||||
- **Слайс как приёмник** (часть E, живой движок): созданный на ГЛАВНОМ пане
|
||||
слайс становится приёмником 1, отвечает на `vfo:1,0;` своей частотой (это и
|
||||
есть вся инициализация MSHV), слушается командой `vfo:1,0,<Гц>`, отдаёт своё
|
||||
аудио блоками с `receiver = 1`, а после удаления слайса приёмник 1 замолкает
|
||||
целиком — вместо прежнего `vfo:1,0,0`.
|
||||
- **Сквозной прогон через живой WDSP:** синтетический 24-битный IQ подаётся
|
||||
в движок, а клиент по WebSocket получает блоки RX-аудио 12 кГц и IQ 48 кГц
|
||||
с верными заголовками; после `AUDIO_STOP`/`IQ_STOP` блоки прекращаются.
|
||||
|
||||
Reference in New Issue
Block a user