Files
ewsdr/test/tci
ew8bakandClaude Opus 5 bb48f5d3fa 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>
2026-08-18 22:10:02 +03:00
..

Стенд TCI

Прогон:

test/tci/run.sh

Собирает tcitest.pas и запускает его; код возврата ненулевой, если хоть одна проверка провалена. Внешних библиотек стенд не требует — WebSocket-клиент в нём написан на сыром сокете. libwdsp нужна только последней части (сквозной прогон через движок); без неё эта часть сообщает о пропуске, а остальные идут как обычно.

Что проверяется — в шапке tcitest.pas; чего стенд не покрывает — в doc/TCI.md, §5.

Две грабли, из-за которых стенд собирается именно так (обе стоили по часу):

  • -Mobjfpc обязателен. С -Mdelphi в командной строке выключаются вложенные комментарии, и {$MODE Delphi} внутри шапки WebUtils.pas закрывает комментарий раньше времени — компиляция падает на «illegal character».
  • Каталог .ppu — свой, с именем не lib, и путь абсолютный. Относительное lib/<cpu>-<os> компилятор ищет и относительно -Fu, то есть находит units GUI-сборки в корне проекта, а там PlatformUtils собран с LCL: стенд падает на линковке с undefined reference to TC_$FORMS_$$_SCREEN. По той же причине сборка идёт с -dHEADLESS, как у демона.

Каталоги units/ и bin/ — выход сборки, в git не попадают.

Проверка на живом эфире без передачи

python3 test/tci/live_rx_test.py --host 127.0.0.1 --port 40001

Наглядный UI-прогон с автоматическим возвратом к исходным значениям:

python3 -u test/tci/live_rx_test.py --visible --commands-only --dwell 1.5

Стенд проверяет receive-only команды, IQ, аудио демодулятора и line-out для всех обнаруженных приёмников. Он никогда не посылает START, STOP, установку TRX/TUNE, TX-аудио и CW-текст. Если радио уже на передаче, прогон аварийно прекращается.

Стенд «как MSHV»

test/tci/mshv_sim.py --rx 0            # в MSHV это «TCI Client rx1»
test/tci/mshv_sim.py --rx 1 --audio    # «TCI Client rx2»

mshv_sim.py повторяет логику TCI-клиента MSHV по его исходникам — включая обе особенности, из-за которых он ведёт себя не так, как наш стенд:

  • фильтр по номеру приёмникаif (ls2.at(0)!=tci_trx) continue;: строка, адресованная не его приёмнику, для MSHV не существует (и то же самое для бинарных блоков, по полю receiver заголовка);
  • инициализация — один запрос и один ответ: он шлёт vfo:<rx>,0; раз в 500 мс, максимум пять раз, и ждёт vfo:<rx>,0,<частота>. Не дождался — «Error To Initialize TCI Server». Других причин у этой ошибки нет.

Поэтому стенд отвечает на вопрос «почему MSHV не подключается» одной строкой, а заодно ловит то, что иначе проявляется молча: без tx_enable со своим номером приёмника у MSHV не работает PTT (set_ptt начинается с if (!tci_tx_enable) return;).

По умолчанию стенд безопасен на приём: ни TRX, ни TUNE, ни START/STOP, ни TX-аудио он не шлёт. Передачу включает явный --tx.