Files
ewsdr/doc/TCI.md
T
ew8bakandClaude Opus 5 82f0e4f771 feat(tci): TCI 2.0 — EWSDR как сервер (команды и уведомления)
Протокол Expert Electronics поверх WebSocket, порт 40001. EWSDR слушает,
клиенты — логгеры, скиммеры, цифровые программы.

- TCIProtocol.pas — чистый слой протокола: разбор/сборка `имя:арг;`,
  экранирование `^ ~ *`, словарь видов связи, пересчёт громкости в дБ.
- TCIServer.pas — WS-сервер поверх WebUtils/WsClient: accept-поток,
  поток на клиента, HTTP-Upgrade, фреймы, рассылка, тик 20 мс.
- TCIAdapter.pas — мост к TRadioController по схеме CAT: геттеры читают
  поля напрямую, сеттеры через Invoke, уведомления через AddStateListener.
  Маппинг: приёмник TCI = панадаптер, канал A/B = VFO A/B (пан 0) либо
  первый/второй слайс (паны 1..).

Попутно: TRadioController.RemoveStateListener (адаптер умирает раньше
контроллера) и TDXSpotStore.RemoveCall (spot_delete).

Настройки — секция "tci" в settings.json (умолчание: выключено,
127.0.0.1, так как авторизации в протоколе нет) и вкладка
Advanced → TCI Server.

Бинарные потоки (IQ/аудио) — этап 2. Статус, таблица команд и список
осознанных эхо-заглушек — в doc/TCI.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 16:22:49 +03:00

14 KiB
Raw Blame History

TCI в EWSDR — статус реализации

Ветка разработки: feature/tci-protocol. Дата последнего обновления: 2026-08-17.

Эталон протокола — «Протокол TCI, версия 2.0» Expert Electronics (doc/TCI Protocol_RU.pdf, 12 января 2024). EWSDR выступает сервером (как ExpertSDR3): порт слушаем мы, клиенты — логгеры, скиммеры, программы цифровых видов, внешние усилители и коммутаторы.


1. Архитектура

TCI-клиенты ──WebSocket──► TTCIServer ──► TTCIAdapter ──► TRadioController
логгер/скиммер/цифра        транспорт      команды +          ядро
                                            уведомления
Файл Назначение
TCIProtocol.pas (~380 строк) Чистый слой протокола: разбор имя:арг1,арг2;, сборка строк, экранирование ^ ~ *, словарь видов связи, пересчёт громкости/порога в дБ. Зависит только от RTL + RadioModes.
TCIServer.pas (~660 строк) WebSocket-сервер: accept-поток, поток на клиента, HTTP-Upgrade, разбор фреймов, рассылка, тик 20 мс. Сокеты и фреймы переиспользованы из веб-подсистемы (WebUtils, WsClient).
TCIAdapter.pas (~1100 строк) Мост к TRadioController: реализация команд, пачка инициализации, уведомления об изменениях состояния, измерители.

Принципы те же, что у CAT (см. doc/CAT_STATUS.md):

  • TCI — равноправный клиент контроллера. Никаких обращений к MainForm; единственное исключение — стор спотов TDXSpotStore, который передаётся адаптеру ссылкой (команды SPOT/SPOT_DELETE/SPOT_CLEAR кладут споты в ту же базу, что и DX-кластер).
  • Потоки. Геттеры читают поля контроллера напрямую из потока клиента; сеттеры пишут параметр в scratch-поля под FLock и зовут FController.Invoke(SyncXxx) — исполнение в потоке контроллера (GUI = TThread.Synchronize).
  • Синхронизация клиентов. Изменение любого поля контроллера приходит в OnState (multicast-подписка AddStateListener) и рассылается всем подключённым — как того требует §3.5 спецификации. Отвечающий на команду клиент дополнительно получает прямой ответ.

1.1 Настройки

Секция tci в корне settings.json:

"tci": { "enabled": false, "port": 40001, "bind_addr": "127.0.0.1" }

UI — вкладка Advanced → TCI Server (галка, порт, интерфейс). В протоколе нет авторизации: открытый наружу порт означает полный доступ к трансиверу, поэтому умолчание слушает только петлю.

1.2 Маппинг модели

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) всегда один — главный тракт

2. Что реализовано

2.1 Инициализация (§4.1)

PROTOCOL, DEVICE, RECEIVE_ONLY, TRX_COUNT, CHANNEL_COUNT, VFO_LIMITS, IF_LIMITS, MODULATIONS_LIST, READY.

READY шлётся после полного дампа состояния: клиент, дождавшийся его, уже знает всё. Границы частот берутся из BackendCaps; пока устройство не подключено — 10 кГц…30 МГц. IF_LIMITS = ±sample rate/2, пересылается при смене частоты дискретизации.

Список видов связи: am,sam,dsb,lsb,usb,cw,nfm,wfm,digl,digu,dmr,fmraw. dmr/fmraw — наше расширение (протокол расширяемый, §1.4). CWL/CWU схлопываются в cw; при установке cw боковая сохраняется, если уже телеграф, иначе выбирается по частоте (ниже 10 МГц — CWL).

2.2 Двунаправленное управление (§4.2)

Полностью проведено в контроллер:

Команда Куда легло
START / STOP SetRun
DDS SetCenter / SetPanDDCFreq
IF, VFO SetVfoA/B, SetSliceTarget
MODULATION SetMode / SetSliceMode
TRX SetMOX
TUNE SetTune
DRIVE SetDrive
TUNE_DRIVE FTXSettings.TUNLevel через SetTXSettings
SPLIT_ENABLE SetSplit
RX_FILTER_BAND SetFilterEdges / SetSliceFilter
VOLUME, MUTE SetVolume, SetMute (дБ ↔ 0..100)
RX_MUTE, RX_VOLUME громкость/мьют слайса
MON_VOLUME, MON_ENABLE SetTXMonVolume, SetRxMuteOnTx
AGC_MODE off→Off, fast→Fast, normal→Medium
AGC_GAIN SetAGCTop (AGC-T)
RX_NR_ENABLE, RX_NB_ENABLE, RX_ANF_ENABLE SetNR/SetNB/SetANF, для панов — SetSliceDSP
LOCK SetVfoLock
SQL_ENABLE, SQL_LEVEL FM-шумоподавитель; дБ (-140..0) ↔ порог 0..100
CW_MACROS_SPEED, CW_KEYER_SPEED TCWSettings.Speed
CW_MACROS_DELAY TCWSettings.RFDelayMS

2.3 Однонаправленное управление (§4.3)

TX_ENABLE (по BackendCaps.HasTX + TXProhibited), CW_MACROS_SPEED_UP/DOWN, SPOT, SPOT_DELETE, SPOT_CLEARTDXSpotStore, спот виден на всех панадаптерах), RX_SENSORS_ENABLE, TX_SENSORS_ENABLE (период — на клиента), IQ_SAMPLERATE, AUDIO_SAMPLERATE, AUDIO_STREAM_*, TX_STREAM_AUDIO_BUFFERING (значения принимаются и подтверждаются; сами потоки — этап 2).

2.4 Уведомления (§4.4, §4.5)

RX_CHANNEL_SENSORS + устаревшая RX_SENSORS (S-метр в дБм, период 30…1000 мс на клиента), TX_SENSORS, TX_FREQUENCY, VFO_LOCK, APP_FOCUS (активация/деактивация главного окна), RX_CLICKED_ON_SPOT + устаревшая CLICKED_ON_SPOT (клик по подписи спота на любом панадаптере), CALLSIGN_SEND (после CW_MSG).

2.5 Телеграф (§3.2)

CW_MACROS, CW_MSG, CW_MACROS_STOP, CW_TERMINAL.

Текст приводится к тому, что понимает передатчик текста ewsdr (CWXSend): экранирование ^ ~ * снимается, CALL$N разворачивается в N повторов позывного, префикс/суффикс _ считаются пустыми. Не поддержано: шаг скорости внутри текста (< / >) и слитная передача аббревиатур (|SK|) — эти символы просто снимаются, потому что TCWSender работает на одной скорости и не знает прос-знаков. Доотправка позывного (cw_msg:arg1;) игнорируется: уже отданный в очередь текст не редактируется.


3. Осознанные заглушки («эхо»)

Значения принимаются, хранятся в адаптере и рассылаются клиентам — так синхронизация между несколькими клиентами остаётся честной, но на радио они не влияют, потому что соответствующего тракта в EWSDR нет:

Команда Причина
RIT_ENABLE, RIT_OFFSET, XIT_ENABLE, XIT_OFFSET расстройки RX/TX в контроллере нет вообще
RX_BIN_ENABLE псевдостерео не реализовано
RX_ANC_ENABLE, RX_APF_ENABLE, RX_DSE_ENABLE, RX_NF_ENABLE таких блоков в тракте нет
RX_NB_PARAM параметры NB в WDSP наружу не выведены
RX_BALANCE баланса каналов у слайса нет
DIGL_OFFSET, DIGU_OFFSET смещения цифровых мод не реализованы
RX_CHANNEL_ENABLE канал B главного приёмника — это VFO B, он есть всегда; создание второго слайса на пане по TCI — этап 2
SET_IN_FOCUS окно программы не поднимаем

Отдельно: у TX_SENSORS второй аргумент — уровень микрофона; измерителя микрофона в EWSDR нет, шлём нижнюю границу шкалы (-60 дБм), чтобы клиент не рисовал случайные значения. Третий аргумент (RMS) и четвёртый (пик) отдаём одинаковыми — в телеметрии платы одно значение forward power.

У TRX третий аргумент (источник сигнала tci/mic1/…) игнорируется: аудио по TCI ещё нет, модуляция берётся из выбранного в программе входа.


4. Этап 2 — бинарные потоки (§3.4)

Не реализовано ничего из потоков; команды управления ими принимаются, но данные не идут. Что нужно сделать:

  1. Заголовок блока уже описан — TTCIStreamHeader в TCIProtocol.pas (16 × uint32 + сэмплы).
  2. RX_AUDIO_STREAM — нужен multicast-тап RX-аудио в контроллере. Сейчас есть только OnAudioConsume — одиночный перехват, которым владеет веб-адаптер (он же глушит локальный звук). Для TCI нужен именно тап «послушать, не забирая», по образцу AddStateListener.
  3. TX_AUDIO_STREAM + TX_CHRONO — приём бинарных фреймов от клиента (сейчас TCIServer их отбрасывает; приёмный буфер TWsClient — 4 КБ, под 16 КБ блоков его придётся растить) и подача в TX-тракт наравне с веб-микрофоном (PushMicSamples).
  4. IQ_STREAM — тап сырого IQ в TWDSPEngine.PushIQItemToDSP (как у декодера маяка), с децимацией до IQ_SAMPLERATE (48/96/192/384 кГц).
  5. LINEOUT_STREAM + LINE_OUT_RECORDER_* — запись в WAV/MP3.

Также в очереди: RX_CHANNEL_ENABLE как реальное создание/удаление второго слайса пана и SET_IN_FOCUS.


5. Проверено

Стендом (WS-клиент на сыром сокете, без внешних библиотек):

  • Транспорт: handshake, маска входящих фреймов, несколько команд в одном фрейме, регистронезависимость, экранированный текст, ping/pong, игнорирование бинарных фреймов, рассылка всем клиентам, чистая остановка сервера с живым клиентом (потоки дренируются, зависаний нет).
  • Сквозной прогон с настоящим TRadioController (движки созданы, железо не подключено): пачка инициализации из 57 строк со всеми обязательными командами и READY в конце; VFO, MODULATION (cw на 7 МГц дал CWL), RX_FILTER_BAND, DRIVE, VOLUME, MUTE, AGC_GAIN, LOCK, CW_MACROS_SPEED, SPOT, RIT_* — состояние контроллера после прогона совпало с посланным; чтение (vfo:0,1;) отвечает; RX_SENSORS_ENABLE:true,100 даёт поток rx_channel_sensors с заданным периодом.
  • Устойчивость: исключение в обработке команды не рвёт соединение — клиенту уходит tci_error:<команда>,<сообщение>;, остальные команды продолжают работать (проверено на контроллере без движков, где SetMode/SetCWSettings падают с AV на неинициализированном FNetwork).

Сборка: lazbuild -B --ws=qt6 ewsdr.lpr и ./build-ewsdrd.sh (демон собирается, TCI в его граф пока не заведён — юниты LCL-free, подключается одной строкой в ewsdrd.lpr, как web).

На реальном железе и с реальным клиентом (Log4OM/N1MM/WSJT-X/CW Skimmer) не проверялось.

Ответ на команду-установку клиент получает дважды: прямым ответом и рассылкой из OnState. Это осознанно — дубли идемпотентны, а рассылка нужна для тех случаев, когда значение поменял не клиент, а оператор.