mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
4165cbe9a549d60f3aff3e1b6ebc3fe291ed0801
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4165cbe9a5 |
fix(tci): ревизия — потоки, валидация, арбитраж и синхронизация клиентов
Разбор семи проходов ревью ветки. Ниже — по сути, а не по списку. Потоки. Сетевые потоки больше не читают модель контроллера напрямую. Слайсы снимаются в потоке контроллера (RefreshSlices → FSliceSnap, на событиях rfSliceFreq/rfSliceState/rfDevice/…), железо — тоже (RefreshDev → TTCIDevSnap: имя платы, границы, число панов, HasTX). Копия TCtrlSlice из чужого потока портила счётчик ссылок managed-строк, а BackendCaps и BoardDisplayName смотрят в FNetwork, который UI освобождает на смене устройства. По той же причине ActiveTXFreqHz переведён на GetSliceView. Sync-методы читают живую таблицу: они уже в потоке контроллера. Жизненный цикл. Stop ждёт выхода клиентских потоков БЕЗ таймаута, прокачивая очередь Synchronize: выйти по таймауту нельзя — следом освобождаются и клиенты, и сам сервер. OnDisconnect зовётся и при остановке (иначе захваты параметров ушедших клиентов доживали до следующего запуска). Отправка переехала на поток самого клиента (recv с TCI_POLL_MS): общий поток задерживал всех на таймаут записи в один медленный сокет. WebUtils.SockSend шлёт с MSG_NOSIGNAL — SIGPIPE убивал headless-процесс. Транспорт. Слот протокола выдаётся только после Upgrade, а сокет до него живёт по таймауту handshake: восемь молчащих соединений закрывали дверь настоящим клиентам. Handshake с заголовком Origin получает 403 — авторизации в TCI нет, и без этого открытая вкладка браузера дотягивалась до TRX и VFO. Заголовки разбираются построчно, текстовые кадры проверяются на UTF-8, close длиной один байт отвергается, на close отвечаем close. Валидация. Все установки ходят через TCITryArg* — «vfo^0~0~abc» больше не превращается в честный ноль. Частота проверяется дважды: в потоке клиента по снимку и в SyncSetVfo/SyncSetCenter по живым границам (устройство успевают сменить между разбором и исполнением). Границы теперь из ОДНОГО источника (FreqLimits поверх VisibleFreqBounds) — тот же, что уходит в VFO_LIMITS; сами VFO_LIMITS переобъявляются при смене железа, и их кэш ведётся независимо от того, подключён ли кто-то. Слайс двигается только TuneSliceInBand, как у CAT: прямой SetSliceTarget уводил TX-слайс в DUC на чужой диапазон без антенн и фильтров. Параметры потоков сверяются со списками спецификации, а IQ_START и прочие запуски честно отвечают ошибкой вместо молчания. Синхронизация клиентов (§3.5). Появился захват параметра на 200 мс: два логгера больше не перетягивают частоту. Пачка инициализации уходит под FClientLock — изменение между строкой снимка и READY терялось навсегда. Глобальные величины (tune_drive, cw_macros_*, split_enable, mon_volume) рассылаются всем, а правки оператора приходят событиями: rfTXProfile, rfActiveVfo, rfMonVolume и новый rfCWSettings. Создание и удаление слайса рассылается по rfDevice (сравнение расстановки), у живого пана без слайсов канал A показывает центр — иначе клиент навсегда оставался с частотой удалённого слайса. Прочее. SliceFreqChanged переехал внутрь SetSliceTarget — один путь для мыши, CAT и TCI (перетаскивание флага мимо клиентов проходило молча). VOLUME и MON_VOLUME развели: SetVolume правит АКТИВНУЮ громкость, поэтому команда на DUP-передаче уезжала в монитор — добавлен адресный SetRxVolume. Настройки сохраняются только после успешного применения, при отказе поднимается прежний слушатель. Время спота — UTC. Подписки на измерители читаются и пишутся под локом клиента. Проверено стендом (сырой WS-клиент + живой TRadioController без железа): 73 проверки, включая изоляцию медленного клиента, остановку под Synchronize, арбитраж до и после 200 мс, отбраковку по живым границам и переобъявление VFO_LIMITS. На реальном железе и с реальным клиентом по-прежнему не гонялось. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
84c9e60b93 |
fix(tci): жизненный цикл, синхронизация слайсов и WebSocket по RFC
Разбор ревью ветки. Критичное — четыре отказа жизненного цикла и один пробел синхронизации. Use-after-free стора спотов: FTCIAdapter освобождается ДО FDXStore. Команда SPOT/SPOT_DELETE, пришедшая между их гибелью, обращалась к освобождённой памяти. Bind-адрес: TCIParseIPv4 стал строгим (out + Boolean, ровно четыре октета 0..255). Кривой адрес — отказ поднимать сокет, а не молчаливый INADDR_ANY: авторизации в TCI нет. В UI порт и адрес применяются по уходу фокуса и по Close, а не на каждую букву — набор «127.0.0.1» по дороге проходил через «127.0.0.» и открывал порт наружу. Остановка при висящем Synchronize: флаг Stopping (адаптер не начинает новых Invoke), прокачка CheckSynchronize в цикле ожидания Stop и запрет освобождать клиента, чей поток не вышел. Владение переделано: клиента освобождает только тик-поток (ReapClients), клиентский лишь помечает себя закрытым. Отправка больше не блокирует вызывающего: Send/Broadcast кладут строку в очередь клиента, в сокет пишет тик-поток вне общего лока, склеивая очередь в общие кадры. Медленный клиент морозил UI на таймаут отправки за каждое движение ручки VFO; теперь он просто вылетает. Слайсы: в контроллере появилось rfSliceState (нагрузка — FSliceFreqId), его шлют сами сеттеры слайса; SyncSetVfo зовёт SliceFreqChanged, как CAT. Адаптер разворачивает Id в пару (приёмник, канал) и рассылает состояние именно этого канала, а не канала 0 каждого пана. WebSocket по RFC 6455: маска обязательна, FIN/continuation собираются, RSV и незнакомые opcode рвут соединение, control-кадры ≤125 и только целиком, 64-битная длина не сворачивается в отрицательный Integer, пустой Sec-WebSocket-Key получает 400. Хвост пакета handshake больше не выбрасывается — первая команда не теряется. Клиент после исключения в разборе не остаётся висеть в массиве. Ещё: DSP и squelch доп. приёмников читаются и пишутся из TCtrlSlice (парные сеттеры сохраняли соседние поля значениями главного тракта); параметры потоков — в TTCIClient, они клиентские по спецификации; эхо под своим локом; ApplySettings возвращает результат, отказ старта виден оператору; инициализация объявляет только существующие каналы; SET_IN_FOCUS реализован через OnFocusRequest. Осознанно не сделано и записано в doc/TCI.md §3.1: AGC_GAIN для приёмников >0 (AGC-T один на тракт), цвет спота, KEYER, TX_FOOTSWITCH, арбитраж нескольких клиентов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |