mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
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>
This commit is contained in:
+221
@@ -0,0 +1,221 @@
|
||||
# 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`:
|
||||
|
||||
```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_CLEAR` (в `TDXSpotStore`, спот виден на всех
|
||||
панадаптерах), `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`. Это осознанно — дубли идемпотентны, а рассылка нужна для тех
|
||||
случаев, когда значение поменял не клиент, а оператор.
|
||||
Reference in New Issue
Block a user