mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(tci): KEYER — чужой ключ очередью элементов, а не сетевыми фронтами
Последняя невыполненная команда протокола. Ключ к ней в том, что arg3 — длительность интервала, который ТОЛЬКО ЧТО кончился, а не начинающегося. Документ задаёт это алгоритмом: первое нажатие keyer:0,true,0, отпускание keyer:0,false,142 («посылка длилась 142 мс»), следующее нажатие keyer:0,true,58 («пауза длилась 58 мс»). Отсюда перевод: тип элемента — это состояние ключа ДО фронта, то есть обратное пришедшему; arg3 = 0 играть нечего. Почему не дёргать ключ по приходу пакета: приход говорит, что интервал кончился, а не сколько он длился, и манипуляция «по приходу» — это сетевой джиттер прямо в эфир, тот самый «пьяный матрос», ради которого третий аргумент в протоколе и появился. Новый TCWElemPlayer (CWMorse.pas) держит очередь элементов и играет их подряд по абсолютным дедлайнам: сумма длительностей равна времени у клиента, значит отставание постоянно (сеть + один элемент) и не накапливается. Очередь опустела — ключ отпускается и следующая пачка начинается с чистого дедлайна: элементы чередуются, искажения нет, зато оборвавшийся клиент не оставляет в эфире несущую. Контроллер: CWKeyerElement(Mark, Ms) ставит элемент в очередь, CWElemKey раздаёт фронты — чужая манипуляция это прямой ключ с точными длительностями, поэтому у Pluto она идёт во вход прямого ключа локального генератора (он сам поднимает сессию, рисует огибающую и сайдтон), а у openHPSDR в бит CWX прошивки (тем же путём идёт передача текста). Гейт CWTXActive, как у CWXSend; обрыв общий с текстом — касание манипулятора, снятие MOX и уход из телеграфа гасят чужую манипуляцию тем же CWXAbort. Передачу KEYER не поднимает: при break-in PTT даёт прошивка (или сессия генератора), без него оператор держит MOX сам. Адаптер: номер передатчика разбирается как у TRX (bad receiver / receiver is not running), захват §3.5 общий с TRX — передатчик один, и ключ держит тот же, кто держит эфир. Паузы обрезаются TCI_KEYER_GAP_MAX_MS = 1 с (пауза целиком прибавляется к отставанию от клиента, а дольше секунды — это «оператор задумался», и честнее догнать реальное время), посылки — 5 с. Стенд test/tci: 238 проверок (было 219). Новая часть C2 меряет ДЛИТЕЛЬНОСТИ по фронтам ключа (посылка 150 / пауза 60 / посылка 150, допуск 30 мс), проверяет отпускание на пустой очереди, старт следующей пачки без «догона» дедлайна, обрыв и нулевую длительность; в части D — разбор аргументов команды и сквозная проверка, что keyer:0,true,<мс> ключ не замыкает (это пауза), а keyer:0,false,<мс> замыкает. Негативный контроль на инверсию перевода. ★Локальный генератор вооружается только при живом устройстве, поэтому сквозная проверка подставляет FDevConnected/FRunning на время. doc/TCI.md: §2.6 описывает команду целиком, §3.1 и §4 переписаны под то, что из пары KEYER/TX_FOOTSWITCH остался только второй; попутно убран устаревший абзац §2.3 про «потоки — этап 2». На железе с настоящим ключом по сети ещё не гонялось. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+55
-2
@@ -507,6 +507,9 @@ type
|
||||
// Программная передача текста (бит CWX). Поток создаётся лениво — у тех,
|
||||
// кто телеграфом не пользуется, лишней нити не заводится.
|
||||
FCWSender: TCWSender;
|
||||
// Чужая манипуляция по сети (TCI KEYER): очередь готовых интервалов.
|
||||
// Тоже лениво — заводится на первом элементе.
|
||||
FCWElem: TCWElemPlayer;
|
||||
// Состояние ключа для ПРОГРАММНОГО сайдтона. Пишется потоком передачи и
|
||||
// разбором HP-статуса (прямой ключ), читается DSP-потоком в MixSidetone —
|
||||
// толерантная гонка на одном булеве, как FHPp*.
|
||||
@@ -723,6 +726,7 @@ type
|
||||
function CWOptionsByte: Byte; // DUC Specific байт 5
|
||||
procedure SyncCWKeyer; // байт 5 под текущий TX-режим
|
||||
procedure CWXKeyEvent(Down: Boolean); // колбэк потока передачи
|
||||
procedure CWElemKey(Down: Boolean); // колбэк проигрывателя KEYER
|
||||
// Сдвиг несущей манипуляции от гетеродина (уход от утечки LO у zero-IF).
|
||||
// На него же уезжает DUC/LO, чтобы РЧ встала ровно на VFO — см. TXTuneFreqHz.
|
||||
function CWCarrierOffsetHz: Double;
|
||||
@@ -1128,6 +1132,10 @@ type
|
||||
// Thetis cwx.cs). Занят ли передатчик — CWXBusy; ЛЮБОЕ касание
|
||||
// манипулятора или снятие MOX обрывает передачу (CWXAbort).
|
||||
procedure CWXSend(const Text: string);
|
||||
// Чужая манипуляция: один готовый интервал (посылка или пауза) заданной
|
||||
// длины. Так приходит телеграф по TCI (команда KEYER) — знаков там нет,
|
||||
// есть измеренные клиентом длительности. См. TCWElemPlayer.
|
||||
procedure CWKeyerElement(Mark: Boolean; Ms: Integer);
|
||||
procedure CWXAbort;
|
||||
function CWXBusy: Boolean;
|
||||
// ---- Окно-терминал -----------------------------------------------------
|
||||
@@ -1534,6 +1542,11 @@ begin
|
||||
FCWSender.AbortSending;
|
||||
FreeAndNil(FCWSender);
|
||||
end;
|
||||
if Assigned(FCWElem) then
|
||||
begin
|
||||
FCWElem.AbortPlay;
|
||||
FreeAndNil(FCWElem);
|
||||
end;
|
||||
if Assigned(FCWDec) then FCWDec.Enabled := False; // кольцо больше не кормим
|
||||
FreeEngines;
|
||||
// Декодер — ПОСЛЕ движка: его кольцо наполняет DSP-поток, и освобождать
|
||||
@@ -6584,7 +6597,8 @@ begin
|
||||
// на бэнд с DoNotTx, правка Alex. Перечислять такие точки поимённо — гарантия
|
||||
// однажды пропустить очередную (ровно так уже вышло с DUC-частотой и мощностью).
|
||||
if (not (ActiveTXMode in [MODE_CWL, MODE_CWU]))
|
||||
and Assigned(FCWSender) and FCWSender.Busy then CWXAbort;
|
||||
and ((Assigned(FCWSender) and FCWSender.Busy)
|
||||
or (Assigned(FCWElem) and FCWElem.Busy)) then CWXAbort;
|
||||
// Вооружённый кейер = железо может выйти в эфир само ⇒ бэкенд обязан класть
|
||||
// уровень мощности в КАЖДЫЙ HP-кадр, а не только на передаче. Локальный
|
||||
// генератор — та же история: FTransmitting в телеграфе не поднимается, а
|
||||
@@ -6759,7 +6773,43 @@ end;
|
||||
function TRadioController.CWXBusy: Boolean;
|
||||
begin
|
||||
Result := (Assigned(FCWSender) and FCWSender.Busy)
|
||||
or (Assigned(FCWLocal) and FCWLocal.Busy);
|
||||
or (Assigned(FCWLocal) and FCWLocal.Busy)
|
||||
or (Assigned(FCWElem) and FCWElem.Busy);
|
||||
end;
|
||||
|
||||
procedure TRadioController.CWElemKey(Down: Boolean);
|
||||
// Поток проигрывателя. Чужая манипуляция — это ПРЯМОЙ КЛЮЧ, только с точными
|
||||
// длительностями, поэтому и уходит она в те же две двери, что и ключ оператора:
|
||||
// у Pluto — во вход прямого ключа локального генератора (он сам поднимает
|
||||
// сессию, рисует огибающую и сайдтон), у openHPSDR — в бит CWX прошивки (тем же
|
||||
// путём идёт и передача текста).
|
||||
begin
|
||||
if CWLocalSource then
|
||||
begin
|
||||
if Assigned(FCWLocal) then FCWLocal.StraightKey(Down);
|
||||
Exit;
|
||||
end;
|
||||
CWXKeyEvent(Down);
|
||||
end;
|
||||
|
||||
procedure TRadioController.CWKeyerElement(Mark: Boolean; Ms: Integer);
|
||||
// Поток контроллера (адаптер TCI маршалит через Invoke). Ставим элемент в
|
||||
// очередь — играет её отдельный поток по абсолютным дедлайнам.
|
||||
begin
|
||||
if Ms <= 0 then Exit;
|
||||
// Тот же гейт, что у CWXSend: CWTXActive учитывает и режим, и запрет
|
||||
// передачи (RX-only слот трансвертера, бэнд с DoNotTx) — там кейер разоружён,
|
||||
// и трогать ключ незачем.
|
||||
if not CWTXActive then Exit;
|
||||
// У Pluto манипуляцию рисуем сами: генератор обязан быть заведён и вооружён,
|
||||
// иначе первый же элемент уйдёт в никуда (то же делает CWXSend).
|
||||
if CWLocalSource then
|
||||
begin
|
||||
EnsureCWLocal;
|
||||
SyncCWKeyer;
|
||||
end;
|
||||
if not Assigned(FCWElem) then FCWElem := TCWElemPlayer.Create(Self.CWElemKey);
|
||||
FCWElem.Enqueue(Mark, Ms);
|
||||
end;
|
||||
|
||||
procedure TRadioController.CWXSend(const Text: string);
|
||||
@@ -6790,6 +6840,9 @@ procedure TRadioController.CWXAbort;
|
||||
begin
|
||||
if Assigned(FCWSender) then FCWSender.AbortSending;
|
||||
if Assigned(FCWLocal) then FCWLocal.AbortText;
|
||||
// Чужая манипуляция обрывается тем же движением: касание манипулятора,
|
||||
// снятие MOX и уход из телеграфа гасят её так же, как передачу текста.
|
||||
if Assigned(FCWElem) then FCWElem.AbortPlay;
|
||||
end;
|
||||
|
||||
function TRadioController.CWXBackspace: Boolean;
|
||||
|
||||
Reference in New Issue
Block a user