mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Merge feature/cat-tx-cw: CAT-команды TX-тракта и телеграфа, ревизия алиасов и подписей, починка транспортов
This commit is contained in:
+400
@@ -39,6 +39,10 @@ type
|
|||||||
FSyncInt: Integer;
|
FSyncInt: Integer;
|
||||||
FSyncBool: Boolean;
|
FSyncBool: Boolean;
|
||||||
FSyncStr: string;
|
FSyncStr: string;
|
||||||
|
// Какое поле TX-настроек правит SyncTXField (значение — в FSyncInt/FSyncBool).
|
||||||
|
FSyncTXFld: (txfMicGain, txfFiltLow, txfFiltHigh, txfCompOn, txfCompGain,
|
||||||
|
txfEQOn, txfTUNLevel, txfFMDev);
|
||||||
|
FSyncEQ: TCATEQGains;
|
||||||
|
|
||||||
// ── Геттеры (CAT-поток, read-only) ──
|
// ── Геттеры (CAT-поток, read-only) ──
|
||||||
function GetVfoA: Double;
|
function GetVfoA: Double;
|
||||||
@@ -96,6 +100,76 @@ type
|
|||||||
procedure SyncCWSpeed;
|
procedure SyncCWSpeed;
|
||||||
procedure SyncCWKeyerMode;
|
procedure SyncCWKeyerMode;
|
||||||
procedure SyncCWText;
|
procedure SyncCWText;
|
||||||
|
// ── Телеграф: тон, break-in, сайдтон ──
|
||||||
|
function GetCWPitch: Integer;
|
||||||
|
procedure SetCWPitch(V: Integer);
|
||||||
|
function GetCWBreakIn: Boolean;
|
||||||
|
procedure SetCWBreakIn(V: Boolean);
|
||||||
|
function GetCWHangTime: Integer;
|
||||||
|
procedure SetCWHangTime(V: Integer);
|
||||||
|
function GetCWMonitor: Boolean;
|
||||||
|
procedure SetCWMonitor(V: Boolean);
|
||||||
|
procedure SyncCWPitch;
|
||||||
|
procedure SyncCWBreakIn;
|
||||||
|
procedure SyncCWHangTime;
|
||||||
|
procedure SyncCWMonitor;
|
||||||
|
// ── TX-тракт (активный TX-профиль + вкладка Transmit) ──
|
||||||
|
function GetMicGain: Integer;
|
||||||
|
procedure SetMicGain(V: Integer);
|
||||||
|
function GetTXFilterLow: Integer;
|
||||||
|
procedure SetTXFilterLow(V: Integer);
|
||||||
|
function GetTXFilterHigh: Integer;
|
||||||
|
procedure SetTXFilterHigh(V: Integer);
|
||||||
|
function GetCompOn: Boolean;
|
||||||
|
procedure SetCompOn(V: Boolean);
|
||||||
|
function GetCompGain: Integer;
|
||||||
|
procedure SetCompGain(V: Integer);
|
||||||
|
function GetTXEQOn: Boolean;
|
||||||
|
procedure SetTXEQOn(V: Boolean);
|
||||||
|
function GetTXEQ(out Gains: TCATEQGains): Integer;
|
||||||
|
procedure SetTXEQ(NumBands: Integer; const Gains: TCATEQGains);
|
||||||
|
function GetTUNLevel: Integer;
|
||||||
|
procedure SetTUNLevel(V: Integer);
|
||||||
|
function GetFMDeviation: Integer;
|
||||||
|
procedure SetFMDeviation(V: Integer);
|
||||||
|
function GetTXProfile: Integer;
|
||||||
|
procedure SetTXProfile(V: Integer);
|
||||||
|
function GetTXProfileCount: Integer;
|
||||||
|
function GetMonitorOn: Boolean;
|
||||||
|
procedure SetMonitorOn(V: Boolean);
|
||||||
|
function GetMonitorLevel: Integer;
|
||||||
|
procedure SetMonitorLevel(V: Integer);
|
||||||
|
function GetTuning: Boolean;
|
||||||
|
procedure SetTuning(V: Boolean);
|
||||||
|
function GetTwoTone: Boolean;
|
||||||
|
procedure SetTwoTone(V: Boolean);
|
||||||
|
function GetPureSignal: Boolean;
|
||||||
|
procedure SetPureSignal(V: Boolean);
|
||||||
|
procedure DoPSSingleCal;
|
||||||
|
// ── Приёмник и сервис ──
|
||||||
|
function GetMuted: Boolean;
|
||||||
|
procedure SetMuted(V: Boolean);
|
||||||
|
function GetAtten: Integer;
|
||||||
|
procedure SetAtten(V: Integer);
|
||||||
|
function GetSupplyVolts: Double;
|
||||||
|
procedure DoVfoSwap;
|
||||||
|
function GetMemoryCount: Integer;
|
||||||
|
procedure SendCWMacro(V: Integer);
|
||||||
|
procedure SyncMuted;
|
||||||
|
procedure SyncAtten;
|
||||||
|
procedure SyncVfoSwap;
|
||||||
|
procedure SyncCWMacro;
|
||||||
|
// Правка одного поля TX-настроек: копия записи, поле, SetTXSettings. Так же
|
||||||
|
// это делает вкладка Transmit — она тоже отдаёт запись целиком.
|
||||||
|
procedure SyncTXField;
|
||||||
|
procedure SyncTXEQ;
|
||||||
|
procedure SyncTXProfile;
|
||||||
|
procedure SyncMonitorOn;
|
||||||
|
procedure SyncMonitorLevel;
|
||||||
|
procedure SyncTuning;
|
||||||
|
procedure SyncTwoTone;
|
||||||
|
procedure SyncPureSignal;
|
||||||
|
procedure SyncPSSingleCal;
|
||||||
procedure DoBandUp;
|
procedure DoBandUp;
|
||||||
procedure DoBandDown;
|
procedure DoBandDown;
|
||||||
procedure DoTuneUp;
|
procedure DoTuneUp;
|
||||||
@@ -234,6 +308,59 @@ begin
|
|||||||
Ctx.SetCWKeyerMode := @SetCWKeyerMode;
|
Ctx.SetCWKeyerMode := @SetCWKeyerMode;
|
||||||
Ctx.SendCWText := @SendCWText;
|
Ctx.SendCWText := @SendCWText;
|
||||||
Ctx.CWTextBusy := @GetCWTextBusy;
|
Ctx.CWTextBusy := @GetCWTextBusy;
|
||||||
|
// Телеграф: тон (ZZCL), break-in и его hang-time (ZZCB/ZZCD), сайдтон (ZZCM).
|
||||||
|
Ctx.GetCWPitch := @GetCWPitch;
|
||||||
|
Ctx.SetCWPitch := @SetCWPitch;
|
||||||
|
Ctx.GetCWBreakIn := @GetCWBreakIn;
|
||||||
|
Ctx.SetCWBreakIn := @SetCWBreakIn;
|
||||||
|
Ctx.GetCWHangTime := @GetCWHangTime;
|
||||||
|
Ctx.SetCWHangTime := @SetCWHangTime;
|
||||||
|
Ctx.GetCWMonitor := @GetCWMonitor;
|
||||||
|
Ctx.SetCWMonitor := @SetCWMonitor;
|
||||||
|
// TX-тракт: усиление микрофона, кромки TX-фильтра, компрессор, эквалайзер,
|
||||||
|
// TUN/2TON/PS и выбор TX-профиля.
|
||||||
|
Ctx.GetMicGain := @GetMicGain;
|
||||||
|
Ctx.SetMicGain := @SetMicGain;
|
||||||
|
Ctx.GetTXFilterLow := @GetTXFilterLow;
|
||||||
|
Ctx.SetTXFilterLow := @SetTXFilterLow;
|
||||||
|
Ctx.GetTXFilterHigh := @GetTXFilterHigh;
|
||||||
|
Ctx.SetTXFilterHigh := @SetTXFilterHigh;
|
||||||
|
Ctx.GetCompOn := @GetCompOn;
|
||||||
|
Ctx.SetCompOn := @SetCompOn;
|
||||||
|
Ctx.GetCompGain := @GetCompGain;
|
||||||
|
Ctx.SetCompGain := @SetCompGain;
|
||||||
|
Ctx.GetTXEQOn := @GetTXEQOn;
|
||||||
|
Ctx.SetTXEQOn := @SetTXEQOn;
|
||||||
|
Ctx.GetTXEQ := @GetTXEQ;
|
||||||
|
Ctx.SetTXEQ := @SetTXEQ;
|
||||||
|
Ctx.GetTUNLevel := @GetTUNLevel;
|
||||||
|
Ctx.SetTUNLevel := @SetTUNLevel;
|
||||||
|
Ctx.GetFMDeviation := @GetFMDeviation;
|
||||||
|
Ctx.SetFMDeviation := @SetFMDeviation;
|
||||||
|
Ctx.GetTXProfile := @GetTXProfile;
|
||||||
|
Ctx.SetTXProfile := @SetTXProfile;
|
||||||
|
Ctx.GetTXProfileCount := @GetTXProfileCount;
|
||||||
|
Ctx.GetMonitorOn := @GetMonitorOn;
|
||||||
|
Ctx.SetMonitorOn := @SetMonitorOn;
|
||||||
|
Ctx.GetMonitorLevel := @GetMonitorLevel;
|
||||||
|
Ctx.SetMonitorLevel := @SetMonitorLevel;
|
||||||
|
Ctx.GetTuning := @GetTuning;
|
||||||
|
Ctx.SetTuning := @SetTuning;
|
||||||
|
Ctx.GetTwoTone := @GetTwoTone;
|
||||||
|
Ctx.SetTwoTone := @SetTwoTone;
|
||||||
|
Ctx.GetPureSignal := @GetPureSignal;
|
||||||
|
Ctx.SetPureSignal := @SetPureSignal;
|
||||||
|
Ctx.DoPSSingleCal := @DoPSSingleCal;
|
||||||
|
// Приёмник и сервис: MUT (ZZMA), шаговый аттенюатор (ZZRX), напряжение
|
||||||
|
// питания (ZZRV), обмен VFO (ZZVS), счётчик памяти (ZZMV), CW-макрос (ZZKM).
|
||||||
|
Ctx.GetMuted := @GetMuted;
|
||||||
|
Ctx.SetMuted := @SetMuted;
|
||||||
|
Ctx.GetAtten := @GetAtten;
|
||||||
|
Ctx.SetAtten := @SetAtten;
|
||||||
|
Ctx.GetSupplyVolts := @GetSupplyVolts;
|
||||||
|
Ctx.DoVfoSwap := @DoVfoSwap;
|
||||||
|
Ctx.GetMemoryCount := @GetMemoryCount;
|
||||||
|
Ctx.SendCWMacro := @SendCWMacro;
|
||||||
Ctx.OnPanelVfoStep := @PanelVfoStep;
|
Ctx.OnPanelVfoStep := @PanelVfoStep;
|
||||||
Ctx.OnPanelEncoder := @PanelEncoder;
|
Ctx.OnPanelEncoder := @PanelEncoder;
|
||||||
Ctx.OnPanelButton := @PanelButton;
|
Ctx.OnPanelButton := @PanelButton;
|
||||||
@@ -476,6 +603,279 @@ begin FController.CWXSend(FSyncStr); end;
|
|||||||
function TCATAdapter.GetCWTextBusy: Boolean;
|
function TCATAdapter.GetCWTextBusy: Boolean;
|
||||||
begin Result := FController.CWXBusy; end;
|
begin Result := FController.CWXBusy; end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetCWPitch: Integer;
|
||||||
|
begin Result := FController.CWSettings.Pitch; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetCWPitch(V: Integer);
|
||||||
|
begin FSyncInt := V; FController.Invoke(@SyncCWPitch); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncCWPitch;
|
||||||
|
var C: TCWSettings;
|
||||||
|
begin
|
||||||
|
C := FController.CWSettings;
|
||||||
|
C.Pitch := FSyncInt;
|
||||||
|
FController.SetCWSettings(C);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetCWBreakIn: Boolean;
|
||||||
|
begin Result := FController.CWSettings.BreakIn; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetCWBreakIn(V: Boolean);
|
||||||
|
begin FSyncBool := V; FController.Invoke(@SyncCWBreakIn); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncCWBreakIn;
|
||||||
|
var C: TCWSettings;
|
||||||
|
begin
|
||||||
|
C := FController.CWSettings;
|
||||||
|
C.BreakIn := FSyncBool;
|
||||||
|
FController.SetCWSettings(C);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetCWHangTime: Integer;
|
||||||
|
begin Result := FController.CWSettings.HangTimeMS; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetCWHangTime(V: Integer);
|
||||||
|
begin FSyncInt := V; FController.Invoke(@SyncCWHangTime); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncCWHangTime;
|
||||||
|
var C: TCWSettings;
|
||||||
|
begin
|
||||||
|
C := FController.CWSettings;
|
||||||
|
C.HangTimeMS := FSyncInt;
|
||||||
|
FController.SetCWSettings(C);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetCWMonitor: Boolean;
|
||||||
|
// Сайдтонов два, и они не взаимозаменяемы: аппаратный звучит в наушниках
|
||||||
|
// трансивера, программный — в звуковой карте PC. ZZCM правит тот, что отвечает
|
||||||
|
// за текущий источник манипуляции, иначе команда молча промахивалась бы мимо.
|
||||||
|
begin
|
||||||
|
if FController.CWLocalSource then Result := FController.CWSettings.SidetoneSW
|
||||||
|
else Result := FController.CWSettings.SidetoneHW;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetCWMonitor(V: Boolean);
|
||||||
|
begin FSyncBool := V; FController.Invoke(@SyncCWMonitor); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncCWMonitor;
|
||||||
|
var C: TCWSettings;
|
||||||
|
begin
|
||||||
|
C := FController.CWSettings;
|
||||||
|
if FController.CWLocalSource then C.SidetoneSW := FSyncBool
|
||||||
|
else C.SidetoneHW := FSyncBool;
|
||||||
|
FController.SetCWSettings(C);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// ── TX-тракт ─────────────────────────────────────────────────────────────────
|
||||||
|
// Геттеры читают живые TX-настройки контроллера (они же — активный профиль),
|
||||||
|
// сеттеры кладут значение в scratch и правят одно поле в потоке контроллера.
|
||||||
|
|
||||||
|
function TCATAdapter.GetMicGain: Integer;
|
||||||
|
begin Result := Round(FController.FTXSettings.MicGainDB); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetMicGain(V: Integer);
|
||||||
|
begin FSyncTXFld := txfMicGain; FSyncInt := V; FController.Invoke(@SyncTXField); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTXFilterLow: Integer;
|
||||||
|
begin Result := FController.FTXSettings.FilterLow; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetTXFilterLow(V: Integer);
|
||||||
|
begin FSyncTXFld := txfFiltLow; FSyncInt := V; FController.Invoke(@SyncTXField); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTXFilterHigh: Integer;
|
||||||
|
begin Result := FController.FTXSettings.FilterHigh; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetTXFilterHigh(V: Integer);
|
||||||
|
begin FSyncTXFld := txfFiltHigh; FSyncInt := V; FController.Invoke(@SyncTXField); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetCompOn: Boolean;
|
||||||
|
begin Result := FController.FTXSettings.CompressorOn; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetCompOn(V: Boolean);
|
||||||
|
begin FSyncTXFld := txfCompOn; FSyncBool := V; FController.Invoke(@SyncTXField); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetCompGain: Integer;
|
||||||
|
begin Result := Round(FController.FTXSettings.CompressorGain); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetCompGain(V: Integer);
|
||||||
|
begin FSyncTXFld := txfCompGain; FSyncInt := V; FController.Invoke(@SyncTXField); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTXEQOn: Boolean;
|
||||||
|
begin Result := FController.FTXSettings.EQOn; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetTXEQOn(V: Boolean);
|
||||||
|
begin FSyncTXFld := txfEQOn; FSyncBool := V; FController.Invoke(@SyncTXField); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTXEQ(out Gains: TCATEQGains): Integer;
|
||||||
|
var i: Integer;
|
||||||
|
begin
|
||||||
|
for i := 0 to High(Gains) do Gains[i] := Round(FController.FTXSettings.EQGains[i]);
|
||||||
|
Result := FController.FTXSettings.EQNumBands;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetTXEQ(NumBands: Integer; const Gains: TCATEQGains);
|
||||||
|
begin
|
||||||
|
FSyncInt := NumBands;
|
||||||
|
FSyncEQ := Gains;
|
||||||
|
FController.Invoke(@SyncTXEQ);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncTXEQ;
|
||||||
|
// Частоты полос команда не несёт (их в формате Thetis попросту нет) — трогаем
|
||||||
|
// только усиления и число полос, сетка остаётся той, что настроена в UI.
|
||||||
|
var
|
||||||
|
T: TTXSettings;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
T := FController.FTXSettings;
|
||||||
|
T.EQNumBands := FSyncInt;
|
||||||
|
for i := 0 to High(FSyncEQ) do
|
||||||
|
if i <= FSyncInt then T.EQGains[i] := FSyncEQ[i];
|
||||||
|
FController.SetTXSettings(T);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTUNLevel: Integer;
|
||||||
|
begin Result := FController.FTXSettings.TUNLevel; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetTUNLevel(V: Integer);
|
||||||
|
begin FSyncTXFld := txfTUNLevel; FSyncInt := V; FController.Invoke(@SyncTXField); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetFMDeviation: Integer;
|
||||||
|
begin Result := Round(FController.FTXSettings.FMDeviation); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetFMDeviation(V: Integer);
|
||||||
|
begin FSyncTXFld := txfFMDev; FSyncInt := V; FController.Invoke(@SyncTXField); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncTXField;
|
||||||
|
var T: TTXSettings;
|
||||||
|
begin
|
||||||
|
T := FController.FTXSettings;
|
||||||
|
case FSyncTXFld of
|
||||||
|
txfMicGain: T.MicGainDB := FSyncInt;
|
||||||
|
txfFiltLow: T.FilterLow := FSyncInt;
|
||||||
|
txfFiltHigh: T.FilterHigh := FSyncInt;
|
||||||
|
txfCompOn: T.CompressorOn := FSyncBool;
|
||||||
|
txfCompGain: T.CompressorGain := FSyncInt;
|
||||||
|
txfEQOn: T.EQOn := FSyncBool;
|
||||||
|
txfTUNLevel: T.TUNLevel := FSyncInt;
|
||||||
|
txfFMDev: T.FMDeviation := FSyncInt;
|
||||||
|
end;
|
||||||
|
FController.SetTXSettings(T);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTXProfile: Integer;
|
||||||
|
begin Result := FController.ActiveTXProfile; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetTXProfile(V: Integer);
|
||||||
|
begin FSyncInt := V; FController.Invoke(@SyncTXProfile); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncTXProfile;
|
||||||
|
begin FController.SelectTXProfile(FSyncInt); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTXProfileCount: Integer;
|
||||||
|
begin Result := FController.TXProfileCount; end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetMonitorOn: Boolean;
|
||||||
|
begin Result := not FController.FRxMuteOnTx; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetMonitorOn(V: Boolean);
|
||||||
|
begin FSyncBool := V; FController.Invoke(@SyncMonitorOn); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncMonitorOn;
|
||||||
|
begin FController.SetRxMuteOnTx(not FSyncBool); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetMonitorLevel: Integer;
|
||||||
|
begin Result := FController.FTxMonVolume; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetMonitorLevel(V: Integer);
|
||||||
|
begin FSyncInt := V; FController.Invoke(@SyncMonitorLevel); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncMonitorLevel;
|
||||||
|
begin FController.SetTXMonVolume(FSyncInt); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTuning: Boolean;
|
||||||
|
begin Result := FController.FTuning; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetTuning(V: Boolean);
|
||||||
|
begin FSyncBool := V; FController.Invoke(@SyncTuning); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncTuning;
|
||||||
|
begin FController.SetTune(FSyncBool); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetTwoTone: Boolean;
|
||||||
|
begin Result := FController.FTwoTone; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetTwoTone(V: Boolean);
|
||||||
|
begin FSyncBool := V; FController.Invoke(@SyncTwoTone); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncTwoTone;
|
||||||
|
begin FController.SetTwoTone(FSyncBool); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetPureSignal: Boolean;
|
||||||
|
begin Result := FController.FPSEnabled; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetPureSignal(V: Boolean);
|
||||||
|
begin FSyncBool := V; FController.Invoke(@SyncPureSignal); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncPureSignal;
|
||||||
|
begin FController.SetPureSignal(FSyncBool); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.DoPSSingleCal;
|
||||||
|
begin FController.Invoke(@SyncPSSingleCal); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncPSSingleCal;
|
||||||
|
begin FController.PSSingleCal; end;
|
||||||
|
|
||||||
|
// ── Приёмник и сервис ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function TCATAdapter.GetMuted: Boolean;
|
||||||
|
begin Result := FController.FMuted; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetMuted(V: Boolean);
|
||||||
|
begin FSyncBool := V; FController.Invoke(@SyncMuted); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncMuted;
|
||||||
|
begin FController.SetMute(FSyncBool); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetAtten: Integer;
|
||||||
|
begin Result := FController.FAtten; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SetAtten(V: Integer);
|
||||||
|
begin FSyncInt := V; FController.Invoke(@SyncAtten); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncAtten;
|
||||||
|
begin FController.SetAtten(FSyncInt); end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetSupplyVolts: Double;
|
||||||
|
// Телеметрия платы; -1 = данных ещё нет (движок отдаст ноль, как Thetis).
|
||||||
|
begin Result := FController.FLastSupplyV; end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.DoVfoSwap;
|
||||||
|
begin FController.Invoke(@SyncVfoSwap); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncVfoSwap;
|
||||||
|
begin FController.VfoSwap; end;
|
||||||
|
|
||||||
|
function TCATAdapter.GetMemoryCount: Integer;
|
||||||
|
begin
|
||||||
|
if Assigned(FController.FChannelStore) then Result := FController.FChannelStore.Count
|
||||||
|
else Result := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SendCWMacro(V: Integer);
|
||||||
|
begin FSyncInt := V; FController.Invoke(@SyncCWMacro); end;
|
||||||
|
|
||||||
|
procedure TCATAdapter.SyncCWMacro;
|
||||||
|
// Макрос n (1..9) = ячейка памяти сообщений n. Ячеек восемь (F1..F8): девятая
|
||||||
|
// и пустые просто ничего не передают — очередь дёргать незачем.
|
||||||
|
var Idx: Integer;
|
||||||
|
begin
|
||||||
|
Idx := FSyncInt - 1;
|
||||||
|
if (Idx < 0) or (Idx >= CW_MSG_COUNT) then Exit;
|
||||||
|
if FController.CWSettings.Messages[Idx] = '' then Exit;
|
||||||
|
FController.CWXSend(FController.CWSettings.Messages[Idx]);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TCATAdapter.SyncMOX;
|
procedure TCATAdapter.SyncMOX;
|
||||||
begin FController.SetMOX(FSyncBool); end;
|
begin FController.SetMOX(FSyncBool); end;
|
||||||
|
|
||||||
|
|||||||
+870
-398
File diff suppressed because it is too large
Load Diff
+65
-46
@@ -63,7 +63,6 @@ type
|
|||||||
FHandle: TSerialHandle;
|
FHandle: TSerialHandle;
|
||||||
FBuf: string;
|
FBuf: string;
|
||||||
procedure ProcessBuffer;
|
procedure ProcessBuffer;
|
||||||
function TryOpenPort: Boolean;
|
|
||||||
protected
|
protected
|
||||||
procedure Execute; override;
|
procedure Execute; override;
|
||||||
public
|
public
|
||||||
@@ -79,6 +78,9 @@ type
|
|||||||
FHandle: TSerialHandle;
|
FHandle: TSerialHandle;
|
||||||
FLock: TCriticalSection;
|
FLock: TCriticalSection;
|
||||||
FActive: Boolean;
|
FActive: Boolean;
|
||||||
|
FLastError: string;
|
||||||
|
function OpenPort: Boolean; // синхронно, из Start — чтобы отказ был виден сразу
|
||||||
|
procedure ClosePort;
|
||||||
public
|
public
|
||||||
constructor Create(AEngine: TCATEngine; const ACfg: TCATSerialConfig);
|
constructor Create(AEngine: TCATEngine; const ACfg: TCATSerialConfig);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
@@ -89,6 +91,9 @@ type
|
|||||||
property Config: TCATSerialConfig read FConfig;
|
property Config: TCATSerialConfig read FConfig;
|
||||||
property Engine: TCATEngine read FEngine;
|
property Engine: TCATEngine read FEngine;
|
||||||
property Active: Boolean read FActive;
|
property Active: Boolean read FActive;
|
||||||
|
// Причина, по которой порт не поднялся (пусто, если поднялся). Раньше отказ
|
||||||
|
// SerOpen нигде не оседал, а порт продолжал числиться активным.
|
||||||
|
property LastError: string read FLastError;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TCATSerialManager — manages up to 4 serial ports }
|
{ TCATSerialManager — manages up to 4 serial ports }
|
||||||
@@ -120,40 +125,6 @@ begin
|
|||||||
inherited Create(True);
|
inherited Create(True);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TCATSerialThread.TryOpenPort: Boolean;
|
|
||||||
{$IFNDEF CAT_SERIAL_STUB}
|
|
||||||
var
|
|
||||||
cfg: TCATSerialConfig;
|
|
||||||
h: TSerialHandle;
|
|
||||||
par: TParityType;
|
|
||||||
{$ENDIF}
|
|
||||||
begin
|
|
||||||
Result := False;
|
|
||||||
{$IFDEF CAT_SERIAL_STUB}
|
|
||||||
Exit;
|
|
||||||
{$ELSE}
|
|
||||||
cfg := FOwner.Config;
|
|
||||||
h := SerOpen(cfg.PortName);
|
|
||||||
{$IFDEF MSWINDOWS}
|
|
||||||
if h = TSerialHandle(INVALID_HANDLE_VALUE) then Exit;
|
|
||||||
{$ELSE}
|
|
||||||
if h < 0 then Exit;
|
|
||||||
{$ENDIF}
|
|
||||||
|
|
||||||
case cfg.Parity of
|
|
||||||
cspNone: par := Serial.NoneParity;
|
|
||||||
cspOdd: par := Serial.OddParity;
|
|
||||||
cspEven: par := Serial.EvenParity;
|
|
||||||
else par := Serial.NoneParity;
|
|
||||||
end;
|
|
||||||
|
|
||||||
SerSetParams(h, cfg.BaudRate, cfg.DataBits, par, cfg.StopBits, []);
|
|
||||||
FHandle := h;
|
|
||||||
FOwner.Handle := h;
|
|
||||||
Result := True;
|
|
||||||
{$ENDIF}
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TCATSerialThread.ProcessBuffer;
|
procedure TCATSerialThread.ProcessBuffer;
|
||||||
var
|
var
|
||||||
tpos: Integer;
|
tpos: Integer;
|
||||||
@@ -173,6 +144,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCATSerialThread.Execute;
|
procedure TCATSerialThread.Execute;
|
||||||
|
// Порт уже открыт владельцем (TCATSerialPort.Start) — поток только читает.
|
||||||
|
// Закрывает тоже владелец, в Stop, после WaitFor.
|
||||||
var
|
var
|
||||||
ch: Byte;
|
ch: Byte;
|
||||||
n: LongInt;
|
n: LongInt;
|
||||||
@@ -180,8 +153,7 @@ begin
|
|||||||
{$IFDEF CAT_SERIAL_STUB}
|
{$IFDEF CAT_SERIAL_STUB}
|
||||||
Exit;
|
Exit;
|
||||||
{$ELSE}
|
{$ELSE}
|
||||||
if not TryOpenPort then Exit;
|
FHandle := FOwner.Handle;
|
||||||
try
|
|
||||||
while not Terminated do begin
|
while not Terminated do begin
|
||||||
n := SerReadTimeout(FHandle, ch, 50);
|
n := SerReadTimeout(FHandle, ch, 50);
|
||||||
if n = 1 then begin
|
if n = 1 then begin
|
||||||
@@ -189,14 +161,6 @@ begin
|
|||||||
if ch = Ord(';') then ProcessBuffer;
|
if ch = Ord(';') then ProcessBuffer;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
finally
|
|
||||||
SerClose(FHandle);
|
|
||||||
{$IFDEF MSWINDOWS}
|
|
||||||
FOwner.Handle := INVALID_HANDLE_VALUE;
|
|
||||||
{$ELSE}
|
|
||||||
FOwner.Handle := -1;
|
|
||||||
{$ENDIF}
|
|
||||||
end;
|
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -223,9 +187,61 @@ begin
|
|||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TCATSerialPort.OpenPort: Boolean;
|
||||||
|
{$IFNDEF CAT_SERIAL_STUB}
|
||||||
|
var
|
||||||
|
h: TSerialHandle;
|
||||||
|
par: TParityType;
|
||||||
|
{$ENDIF}
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
{$IFDEF CAT_SERIAL_STUB}
|
||||||
|
FLastError := 'serial support not built on this platform';
|
||||||
|
{$ELSE}
|
||||||
|
h := SerOpen(FConfig.PortName);
|
||||||
|
{$IFDEF MSWINDOWS}
|
||||||
|
if h = TSerialHandle(INVALID_HANDLE_VALUE) then
|
||||||
|
{$ELSE}
|
||||||
|
if h < 0 then
|
||||||
|
{$ENDIF}
|
||||||
|
begin
|
||||||
|
FLastError := 'не удалось открыть ' + FConfig.PortName;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
case FConfig.Parity of
|
||||||
|
cspNone: par := Serial.NoneParity;
|
||||||
|
cspOdd: par := Serial.OddParity;
|
||||||
|
cspEven: par := Serial.EvenParity;
|
||||||
|
else par := Serial.NoneParity;
|
||||||
|
end;
|
||||||
|
|
||||||
|
SerSetParams(h, FConfig.BaudRate, FConfig.DataBits, par, FConfig.StopBits, []);
|
||||||
|
FHandle := h;
|
||||||
|
FLastError := '';
|
||||||
|
Result := True;
|
||||||
|
{$ENDIF}
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCATSerialPort.ClosePort;
|
||||||
|
begin
|
||||||
|
{$IFNDEF CAT_SERIAL_STUB}
|
||||||
|
{$IFDEF MSWINDOWS}
|
||||||
|
if FHandle <> INVALID_HANDLE_VALUE then SerClose(FHandle);
|
||||||
|
FHandle := INVALID_HANDLE_VALUE;
|
||||||
|
{$ELSE}
|
||||||
|
if FHandle >= 0 then SerClose(FHandle);
|
||||||
|
FHandle := -1;
|
||||||
|
{$ENDIF}
|
||||||
|
{$ENDIF}
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TCATSerialPort.Start;
|
procedure TCATSerialPort.Start;
|
||||||
|
// Порт открываем ЗДЕСЬ, а не в потоке: иначе отказ SerOpen оставался внутри
|
||||||
|
// потока, а порт всё это время числился активным — и в ActiveCount, и в UI.
|
||||||
begin
|
begin
|
||||||
if FActive or not FConfig.Enabled then Exit;
|
if FActive or not FConfig.Enabled then Exit;
|
||||||
|
if not OpenPort then Exit; // FActive остаётся False, причина — в LastError
|
||||||
FActive := True;
|
FActive := True;
|
||||||
FThread := TCATSerialThread.Create(Self);
|
FThread := TCATSerialThread.Create(Self);
|
||||||
FThread.Start;
|
FThread.Start;
|
||||||
@@ -240,6 +256,7 @@ begin
|
|||||||
FThread.WaitFor;
|
FThread.WaitFor;
|
||||||
FreeAndNil(FThread);
|
FreeAndNil(FThread);
|
||||||
end;
|
end;
|
||||||
|
ClosePort;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCATSerialPort.SendStr(const S: string);
|
procedure TCATSerialPort.SendStr(const S: string);
|
||||||
@@ -294,7 +311,9 @@ begin
|
|||||||
for i := 0 to cnt-1 do begin
|
for i := 0 to cnt-1 do begin
|
||||||
FPorts[i] := TCATSerialPort.Create(FEngine, Configs[i]);
|
FPorts[i] := TCATSerialPort.Create(FEngine, Configs[i]);
|
||||||
if Configs[i].Enabled then FPorts[i].Start;
|
if Configs[i].Enabled then FPorts[i].Start;
|
||||||
if Configs[i].Enabled and Configs[i].Andromeda and (FAndromedaPort = nil) then
|
// Панель назначаем только на РЕАЛЬНО поднявшийся порт: раньше хватало галки
|
||||||
|
// в настройках, и HasAndromeda рапортовал о панели, которой нет.
|
||||||
|
if FPorts[i].Active and Configs[i].Andromeda and (FAndromedaPort = nil) then
|
||||||
FAndromedaPort := FPorts[i];
|
FAndromedaPort := FPorts[i];
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|||||||
+15
-5
@@ -161,22 +161,32 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCATTcpClientThread.SendStr(const S: string);
|
procedure TCATTcpClientThread.SendStr(const S: string);
|
||||||
|
// TCP не обязан отдавать весь буфер за один send: длинный ответ (IF, ZZEB,
|
||||||
|
// список режимов) мог уехать обрезанным — и молча, потому что усечение здесь
|
||||||
|
// не ошибка. Дописываем остаток, пока он есть.
|
||||||
var
|
var
|
||||||
buf: AnsiString;
|
buf: AnsiString;
|
||||||
n: Integer;
|
sent, n: Integer;
|
||||||
begin
|
begin
|
||||||
if FSocket = SOCK_INVALID then Exit;
|
if FSocket = SOCK_INVALID then Exit;
|
||||||
buf := AnsiString(S);
|
buf := AnsiString(S);
|
||||||
if Length(buf) = 0 then Exit;
|
if Length(buf) = 0 then Exit;
|
||||||
|
sent := 0;
|
||||||
|
while sent < Length(buf) do
|
||||||
|
begin
|
||||||
{$IFDEF MSWINDOWS}
|
{$IFDEF MSWINDOWS}
|
||||||
n := send(FSocket, buf[1], Length(buf), 0);
|
n := send(FSocket, buf[sent + 1], Length(buf) - sent, 0);
|
||||||
if n = SOCKET_ERROR then begin
|
if n = SOCKET_ERROR then n := -1;
|
||||||
{$ELSE}
|
{$ELSE}
|
||||||
n := fpSend(FSocket, @buf[1], Length(buf), 0);
|
n := fpSend(FSocket, @buf[sent + 1], Length(buf) - sent, 0);
|
||||||
if n <= 0 then begin
|
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
|
if n <= 0 then
|
||||||
|
begin
|
||||||
FMarkDelete := True;
|
FMarkDelete := True;
|
||||||
Terminate;
|
Terminate;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
Inc(sent, n);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
+7
-48
@@ -8520,64 +8520,23 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
|
procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
|
||||||
// Любая правка в Settings→Transmit летит сюда: обновляем FController.FTXSettings,
|
// Любая правка в Settings→Transmit летит сюда. Применение записи (WDSP, DUC
|
||||||
// перешиваем WDSP TX-цепь и (если поменялись mic-биты) перепосылаем DUC Specific.
|
// Specific, живой TUN, профиль, персист) — в контроллере: SetTXSettings, та же
|
||||||
var
|
// дверь, что у CAT и web. Форме остаётся рендер.
|
||||||
OldMicByte, NewMicByte: Byte;
|
// Notify=False — иначе rfTXProfile перезагрузил бы вкладку Transmit прямо под
|
||||||
OldAtt, OldTUNLevel, OldTUNFreq: Integer;
|
// руками у того, кто её сейчас правит.
|
||||||
GridChanged: Boolean;
|
var GridChanged: Boolean;
|
||||||
begin
|
begin
|
||||||
OldMicByte := FController.BuildMicLineSelectByte;
|
|
||||||
OldAtt := FController.FTXSettings.AttOnTX;
|
|
||||||
OldTUNLevel := FController.FTXSettings.TUNLevel;
|
|
||||||
OldTUNFreq := FController.FTXSettings.TUNFreq;
|
|
||||||
GridChanged := (FController.FTXSpecRefLevel <> T.TXSpecRefLevel)
|
GridChanged := (FController.FTXSpecRefLevel <> T.TXSpecRefLevel)
|
||||||
or (FController.FTXSpecRange <> T.TXSpecRange)
|
or (FController.FTXSpecRange <> T.TXSpecRange)
|
||||||
or (FController.FTXSpecGridStep <> T.TXSpecGridStep);
|
or (FController.FTXSpecGridStep <> T.TXSpecGridStep);
|
||||||
FController.FTXSettings := T;
|
FController.SetTXSettings(T, False);
|
||||||
NewMicByte := FController.BuildMicLineSelectByte;
|
|
||||||
// TX-grid в момент правки — обновляем рабочие поля и (если на передаче) пушим в FSpecView
|
|
||||||
FController.FTXSpecRefLevel := FController.FTXSettings.TXSpecRefLevel;
|
|
||||||
FController.FTXSpecRange := FController.FTXSettings.TXSpecRange;
|
|
||||||
if FController.FTXSettings.TXSpecGridStep > 0 then
|
|
||||||
FController.FTXSpecGridStep := FController.FTXSettings.TXSpecGridStep;
|
|
||||||
if GridChanged and FController.FTransmitting then
|
if GridChanged and FController.FTransmitting then
|
||||||
ApplySpecViewGridFromState;
|
ApplySpecViewGridFromState;
|
||||||
FController.ApplyTXSettingsToDSP;
|
|
||||||
// Low/High из вкладки Transmit — та же полоска фильтра на спектре: кромки
|
// Low/High из вкладки Transmit — та же полоска фильтра на спектре: кромки
|
||||||
// берём уже пересчитанными движком (знак боковой) и на передаче перерисовываем.
|
// берём уже пересчитанными движком (знак боковой) и на передаче перерисовываем.
|
||||||
PushFilterEdgesToView;
|
PushFilterEdgesToView;
|
||||||
if FController.FTransmitting then FSpectrumDirty := True;
|
if FController.FTransmitting then FSpectrumDirty := True;
|
||||||
if (OldMicByte <> NewMicByte) or (OldAtt <> FController.FTXSettings.AttOnTX) then
|
|
||||||
FController.SendDUCSpecificFromSettings;
|
|
||||||
// TUN-параметры live: если идёт tune — обновить тон/drive, иначе просто запомнить.
|
|
||||||
if FController.FTuning then
|
|
||||||
begin
|
|
||||||
if (OldTUNFreq <> FController.FTXSettings.TUNFreq) and FController.FWDSPReady then
|
|
||||||
FController.FDSPEngine.SetTXTone(True, FController.FTXSettings.TUNFreq, 1.0);
|
|
||||||
if OldTUNLevel <> FController.FTXSettings.TUNLevel then
|
|
||||||
begin
|
|
||||||
FController.FDriveLevel := CalcDriveByte;
|
|
||||||
if FController.FWDSPReady then
|
|
||||||
FController.FDSPEngine.SetDriveLevel(FController.FTXSettings.TUNLevel / 100.0);
|
|
||||||
if FController.FNetwork.Connected and FController.FNetwork.Running then
|
|
||||||
begin
|
|
||||||
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), FController.TXTuneFreqHz, FController.FDriveLevel,
|
|
||||||
FController.FTransmitting, True, True, FController.FTuning, FController.FTwoTone);
|
|
||||||
FController.FNetwork.SendFullHP;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
// Правка «звуковых» полей — это правка активного профиля: отдельной кнопки
|
|
||||||
// «сохранить» нет, профиль ведётся за живым состоянием (см. TTXProfile).
|
|
||||||
FController.StoreActiveTXProfile;
|
|
||||||
// Сохраняем сразу — настройки per-device, попадают в hpsdr_settings.json
|
|
||||||
if FController.FDevConnected then
|
|
||||||
begin
|
|
||||||
FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings);
|
|
||||||
FController.FSettings.SaveTXProfiles(FController.FDevMAC, FController.FTXProfiles);
|
|
||||||
FController.FSettings.Save;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnCWSettingsChange(const C: TCWSettings);
|
procedure TMainForm.OnCWSettingsChange(const C: TCWSettings);
|
||||||
|
|||||||
@@ -691,6 +691,11 @@ type
|
|||||||
function TXProfileCount: Integer;
|
function TXProfileCount: Integer;
|
||||||
function TXProfileName(Idx: Integer): string;
|
function TXProfileName(Idx: Integer): string;
|
||||||
function ActiveTXProfile: Integer;
|
function ActiveTXProfile: Integer;
|
||||||
|
// Единая дверь для правки TX-настроек снаружи (вкладка Transmit, CAT, web):
|
||||||
|
// применяет запись к движку/железу, дописывает активный профиль и персистит.
|
||||||
|
// Notify=False — вызывающий сам отрисует изменения (так делает MainForm,
|
||||||
|
// иначе rfTXProfile перезагрузил бы форму настроек прямо под руками).
|
||||||
|
procedure SetTXSettings(const T: TTXSettings; Notify: Boolean = True);
|
||||||
// Auto=True — переключение сделала автоматика привязок; ручной выбор
|
// Auto=True — переключение сделала автоматика привязок; ручной выбор
|
||||||
// (дропдаун, список настроек) дополнительно ЗАПОМИНАЕТСЯ за текущей парой
|
// (дропдаун, список настроек) дополнительно ЗАПОМИНАЕТСЯ за текущей парой
|
||||||
// «источник + модуляция» (BindTXProfileToCurrentMode).
|
// «источник + модуляция» (BindTXProfileToCurrentMode).
|
||||||
@@ -787,6 +792,9 @@ type
|
|||||||
// Громкость / mute / drive / аттенюатор
|
// Громкость / mute / drive / аттенюатор
|
||||||
procedure SetVolume(V: Integer);
|
procedure SetVolume(V: Integer);
|
||||||
procedure VolumeBy(Delta: Integer);
|
procedure VolumeBy(Delta: Integer);
|
||||||
|
// Громкость self-monitor'а на передаче отдельно от RX-громкости: слайдер
|
||||||
|
// правит её только когда MonitoringTX, а CAT (ZZTM) — в любой момент.
|
||||||
|
procedure SetTXMonVolume(V: Integer);
|
||||||
// True — сейчас звучит self-monitor даунлинка на передаче (TX + DUP + RX MUTE
|
// True — сейчас звучит self-monitor даунлинка на передаче (TX + DUP + RX MUTE
|
||||||
// off). В этом контексте SetVolume/слайдер правят FTxMonVolume, иначе FVolume.
|
// off). В этом контексте SetVolume/слайдер правят FTxMonVolume, иначе FVolume.
|
||||||
function MonitoringTX: Boolean;
|
function MonitoringTX: Boolean;
|
||||||
@@ -4683,6 +4691,14 @@ end;
|
|||||||
procedure TRadioController.VolumeBy(Delta: Integer);
|
procedure TRadioController.VolumeBy(Delta: Integer);
|
||||||
begin SetVolume(ActiveVolume + Delta); end;
|
begin SetVolume(ActiveVolume + Delta); end;
|
||||||
|
|
||||||
|
procedure TRadioController.SetTXMonVolume(V: Integer);
|
||||||
|
// Адресно правит громкость self-monitor'а. Слайдер добирается до неё только на
|
||||||
|
// передаче (SetVolume через ActiveVolume), CAT-клиенту такой контекст не нужен.
|
||||||
|
begin
|
||||||
|
FTxMonVolume := EnsureRange(V, 0, 100);
|
||||||
|
if MonitoringTX then ApplyActiveVolume;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetMute(On_: Boolean);
|
procedure TRadioController.SetMute(On_: Boolean);
|
||||||
begin
|
begin
|
||||||
FMuted := On_;
|
FMuted := On_;
|
||||||
@@ -5531,6 +5547,59 @@ begin
|
|||||||
FSettings.Save;
|
FSettings.Save;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.SetTXSettings(const T: TTXSettings; Notify: Boolean);
|
||||||
|
// Одна дверь для правки TX-настроек — по образцу SetCWSettings. Клиенту
|
||||||
|
// (вкладка Transmit, CAT, web) достаточно отдать запись целиком: применение к
|
||||||
|
// WDSP, перепосыл DUC Specific при смене mic-битов/ATT, живое обновление TUN,
|
||||||
|
// запись в активный профиль и персист — здесь.
|
||||||
|
var
|
||||||
|
OldMicByte: Byte;
|
||||||
|
OldAtt, OldTUNLevel, OldTUNFreq: Integer;
|
||||||
|
begin
|
||||||
|
OldMicByte := BuildMicLineSelectByte;
|
||||||
|
OldAtt := FTXSettings.AttOnTX;
|
||||||
|
OldTUNLevel := FTXSettings.TUNLevel;
|
||||||
|
OldTUNFreq := FTXSettings.TUNFreq;
|
||||||
|
FTXSettings := T;
|
||||||
|
// TX-сетка спектра — рабочие поля рендера; их перерисовку заказывает вызывающий.
|
||||||
|
FTXSpecRefLevel := FTXSettings.TXSpecRefLevel;
|
||||||
|
FTXSpecRange := FTXSettings.TXSpecRange;
|
||||||
|
if FTXSettings.TXSpecGridStep > 0 then FTXSpecGridStep := FTXSettings.TXSpecGridStep;
|
||||||
|
ApplyTXSettingsToDSP;
|
||||||
|
// Маршрутизация микрофона и ATT on TX живут в железе (DUC Specific), WDSP тут
|
||||||
|
// ни при чём — пакет перепосылаем только когда байты реально поменялись.
|
||||||
|
if (BuildMicLineSelectByte <> OldMicByte) or (OldAtt <> FTXSettings.AttOnTX) then
|
||||||
|
SendDUCSpecificFromSettings;
|
||||||
|
// TUN-параметры live: идёт настройка — обновить тон/уровень, иначе просто запомнить.
|
||||||
|
if FTuning then
|
||||||
|
begin
|
||||||
|
if (OldTUNFreq <> FTXSettings.TUNFreq) and FWDSPReady then
|
||||||
|
FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0);
|
||||||
|
if OldTUNLevel <> FTXSettings.TUNLevel then
|
||||||
|
begin
|
||||||
|
FDriveLevel := CalcDriveByte;
|
||||||
|
if FWDSPReady then
|
||||||
|
FDSPEngine.SetDriveLevel(EnsureRange(FTXSettings.TUNLevel, 0, 100) / 100.0);
|
||||||
|
if FNetwork.Connected and FNetwork.Running then
|
||||||
|
begin
|
||||||
|
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), TXTuneFreqHz, FDriveLevel,
|
||||||
|
FTransmitting, True, True, FTuning, FTwoTone);
|
||||||
|
FNetwork.SendFullHP;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
// Правка «звуковых» полей — это правка активного профиля: отдельной кнопки
|
||||||
|
// «сохранить» нет, профиль ведётся за живым состоянием (см. TTXProfile).
|
||||||
|
StoreActiveTXProfile;
|
||||||
|
if FDevConnected and Assigned(FSettings) then
|
||||||
|
begin
|
||||||
|
FSettings.SaveTX(FDevMAC, FTXSettings);
|
||||||
|
FSettings.SaveTXProfiles(FDevMAC, FTXProfiles);
|
||||||
|
FSettings.Save;
|
||||||
|
end;
|
||||||
|
if Notify then Changed(rfTXProfile);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.StoreActiveTXProfile(Persist: Boolean = False);
|
procedure TRadioController.StoreActiveTXProfile(Persist: Boolean = False);
|
||||||
// Живое состояние → активный профиль. Зовётся на каждой правке TX-настроек и
|
// Живое состояние → активный профиль. Зовётся на каждой правке TX-настроек и
|
||||||
// на смене drive; Persist=False не трогает диск (drive ездит слайдером).
|
// на смене drive; Persist=False не трогает диск (drive ездит слайдером).
|
||||||
|
|||||||
+185
-30
@@ -1,7 +1,7 @@
|
|||||||
# CAT-система EWSDR — статус реализации
|
# CAT-система EWSDR — статус реализации
|
||||||
|
|
||||||
Ветка разработки: `feature/cat-commands-expansion`.
|
Ветка разработки: `feature/cat-tx-cw`.
|
||||||
Дата последнего обновления: 2026-06-26.
|
Дата последнего обновления: 2026-08-17.
|
||||||
|
|
||||||
Документ описывает текущее состояние CAT-подсистемы EWSDR: архитектуру,
|
Документ описывает текущее состояние CAT-подсистемы EWSDR: архитектуру,
|
||||||
полный перечень реализованных команд, осознанные заглушки и план дальнейших
|
полный перечень реализованных команд, осознанные заглушки и план дальнейших
|
||||||
@@ -20,9 +20,9 @@ CAT-транспорты ──► TCATEngine (парсер протокола)
|
|||||||
|
|
||||||
| Файл | Назначение |
|
| Файл | Назначение |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `CATEngine.pas` (~2270 строк) | Движок протокола: разбор `<PREFIX><SUFFIX>;`, формирование ответов, реализации всех команд Kenwood + `ZZ*`. Зависит только от RTL. |
|
| `CATEngine.pas` (~2740 строк) | Движок протокола: разбор `<PREFIX><SUFFIX>;`, формирование ответов, реализации всех команд Kenwood + `ZZ*`. Зависит только от RTL. |
|
||||||
| `CATAdapter.pas` (~445 строк) | Мост к `TRadioController`. Строит `TCATContext`: геттеры читают поля контроллера напрямую (CAT-поток, read-only), сеттеры/команды маршалятся в поток контроллера через `FController.Invoke`. Владеет движком + транспортами. |
|
| `CATAdapter.pas` (~910 строк) | Мост к `TRadioController`. Строит `TCATContext`: геттеры читают поля контроллера напрямую (CAT-поток, read-only), сеттеры/команды маршалятся в поток контроллера через `FController.Invoke`. Владеет движком + транспортами. |
|
||||||
| `CATSerial.pas` (~313 строк) | Менеджер до 4 последовательных портов (виртуальные COM-пары для rigctld/N1MM/WSJT-X). |
|
| `CATSerial.pas` (~330 строк) | Менеджер до 4 последовательных портов (виртуальные COM-пары для rigctld/N1MM/WSJT-X). |
|
||||||
| `CATTcp.pas` (~507 строк) | TCP-сервер (Thetis-совместимый), управление клиентами по GUID (`ZZGA`/`ZZGR`). |
|
| `CATTcp.pas` (~507 строк) | TCP-сервер (Thetis-совместимый), управление клиентами по GUID (`ZZGA`/`ZZGR`). |
|
||||||
|
|
||||||
### 1.1 Принципы
|
### 1.1 Принципы
|
||||||
@@ -51,12 +51,28 @@ CAT-транспорты ──► TCATEngine (парсер протокола)
|
|||||||
| | Кол-во |
|
| | Кол-во |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Всего `ZZ*` в Thetis | 315 |
|
| Всего `ZZ*` в Thetis | 315 |
|
||||||
| Реально реализовано в EWSDR | **98** |
|
| Реально реализовано в EWSDR | **~120** |
|
||||||
| Заглушки (отвечают валидным dummy, функции нет) | ~211 |
|
| Заглушки (отвечают валидным dummy, функции нет) | ~190 |
|
||||||
| Совсем не обрабатываются (вернут `?;`) | 7 |
|
| Совсем не обрабатываются (вернут `?;`) | 5 |
|
||||||
|
|
||||||
Большинство заглушек закрыть **нечем** — в EWSDR физически отсутствует
|
Подписи всех 315 команд сверены с `CATCommands.cs` (см. §2.5) — комментарию у
|
||||||
соответствующий тракт (см. §4).
|
заглушки теперь можно верить. Оставшиеся заглушки в большинстве закрыть
|
||||||
|
**нечем**: в EWSDR физически отсутствует соответствующий тракт (см. §4).
|
||||||
|
|
||||||
|
### 1.3 Осознанные отклонения от Thetis
|
||||||
|
|
||||||
|
Три места, где EWSDR намеренно расходится с эталоном. Больше таких нет.
|
||||||
|
|
||||||
|
| Команда | У Thetis | У нас | Почему |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `ZZBS` | код диапазона, 3 символа (`160`/`040`/`WWV`) | индекс диапазона, 2 цифры | формат заложен давно, на нём уже сидят клиенты |
|
||||||
|
| `ZZMN` | пресеты DSP-фильтров, 180 символов | имя режима по номеру | имён режимов в протоколе нет вовсе, а `ZZML` опирается на эту команду |
|
||||||
|
| `ZZST` | размер шага настройки (read-only) | индекс шага FM (00..03) | глобального шага у EWSDR нет, слот иначе мёртв; у шага FM своей команды в протоколе нет |
|
||||||
|
|
||||||
|
Плюс одно ограничение диапазона, не формата: **`ZZCD`** (hang-time break-in)
|
||||||
|
эталон принимает в 150..5000 мс, у нас потолок 2000 — столько же стоит в
|
||||||
|
настройках телеграфа и уходит в поле `HangDelay` пакета DUC Specific. Значения
|
||||||
|
выше подрезаются, как и всё прочее у Thetis.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -89,13 +105,14 @@ CAT-транспорты ──► TCATEngine (парсер протокола)
|
|||||||
|
|
||||||
### 2.2 Реализованные расширенные `ZZ*` (выборочно, наиболее значимые)
|
### 2.2 Реализованные расширенные `ZZ*` (выборочно, наиболее значимые)
|
||||||
|
|
||||||
VFO/режим/фильтр: `ZZFA ZZFB ZZFI ZZFL ZZFH ZZFM ZZMD ZZFT ZZSP`,
|
VFO/режим/фильтр: `ZZFA ZZFB ZZFI ZZFL ZZFH ZZFM ZZMD ZZFT ZZSP ZZVS`,
|
||||||
диапазоны: `ZZBS ZZBU ZZBD ZZBA ZZBI ZZBM`,
|
диапазоны: `ZZBS ZZBU ZZBD`,
|
||||||
AGC/громкость/мощность: `ZZGT ZZAG ZZPC ZZPD ZZAR`,
|
AGC/громкость/мощность/аттенюатор: `ZZGT ZZAG ZZMA ZZPC ZZAR ZZRX`,
|
||||||
шумодав/нотч: `ZZNA ZZNB ZZNR ZZNS ZZNT ZZNL ZZNN`,
|
шумодав/нотч: `ZZNA ZZNB ZZNR ZZNS ZZNT ZZNN`,
|
||||||
FM: `ZZSQ ZZSO ZZTA ZZTB ZZST`, CTUN: `ZZCN`,
|
FM: `ZZSQ ZZSO ZZTA ZZTB ZZST`, CTUN: `ZZCN`,
|
||||||
S-метр/телеметрия: `ZZSM`, шаги VFO: `ZZAU ZZBP ZZSU ZZSA ZZSB`,
|
S-метр/телеметрия: `ZZSM ZZRV`, память: `ZZMV`,
|
||||||
передача: `ZZTX ZZRX`, сервис: `ZZID ZZRV ZZGA ZZGR`.
|
шаги VFO: `ZZAU ZZBP ZZBM ZZSU ZZSA ZZSB`,
|
||||||
|
передача: `ZZTX`, сервис: `ZZID ZZGA ZZGR`.
|
||||||
|
|
||||||
### 2.3 Сделано в ветке `feature/cat-commands-expansion`
|
### 2.3 Сделано в ветке `feature/cat-commands-expansion`
|
||||||
|
|
||||||
@@ -142,6 +159,133 @@ S-метр/телеметрия: `ZZSM`, шаги VFO: `ZZAU ZZBP ZZSU ZZSA ZZSB
|
|||||||
**Новые callback'и в `TCATContext`** (этой ветки): split, squelch on/level,
|
**Новые callback'и в `TCATContext`** (этой ветки): split, squelch on/level,
|
||||||
CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
|
CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
|
||||||
|
|
||||||
|
### 2.4 Сделано в ветке `feature/cat-tx-cw`
|
||||||
|
|
||||||
|
К этому моменту в EWSDR появились TX-профили со всей «звуковой» цепью и
|
||||||
|
телеграф (кейер, сайдтон, break-in) — команды, которые §4 когда-то объявил
|
||||||
|
незакрываемыми, стали рабочими.
|
||||||
|
|
||||||
|
**TX-тракт** (правки уезжают в активный TX-профиль):
|
||||||
|
|
||||||
|
| Команда | Что | Маппинг |
|
||||||
|
|---|---|---|
|
||||||
|
| `MG` / `ZZMG` | усиление микрофона | `MicGainDB`; `MG` — шкала Kenwood 100:70, `ZZMG` — дБ со знаком |
|
||||||
|
| `MO` / `ZZMO` | кнопка MON | self-monitor даунлинка: `SetRxMuteOnTx` (инверсия) |
|
||||||
|
| `ZZTM` | громкость монитора | `SetTXMonVolume` (новый метод контроллера) |
|
||||||
|
| `ZZTL` / `ZZTH` | кромки TX-фильтра, Гц | `FilterLow` / `FilterHigh` |
|
||||||
|
| `PR` / `ZZPK` | речевой компрессор вкл/выкл | `CompressorOn` |
|
||||||
|
| `ZZPL` | усиление компрессора, дБ | `CompressorGain` (0..20) |
|
||||||
|
| `ZZET` | кнопка TXEQ | `EQOn` |
|
||||||
|
| `ZZEB` | значения TX-эквалайзера | `EQNumBands` + `EQGains` (формат Thetis, 36 символов) |
|
||||||
|
| `ZZTO` | мощность настройки | `TUNLevel` |
|
||||||
|
| `ZZTU` | кнопка TUN | `SetTune` |
|
||||||
|
| `ZZUT` | двухтональник 2TON | `SetTwoTone` |
|
||||||
|
| `ZZLI` | кнопка PS-A | `SetPureSignal` |
|
||||||
|
| `ZZUS` | одиночная калибровка PS | `PSSingleCal` (write-only) |
|
||||||
|
| `ZZTP` | активный TX-профиль | `SelectTXProfile` / `TXProfileCount` |
|
||||||
|
| `ZZFD` | девиация передачи (2.5/5 кГц) | `TTXSettings.FMDeviation` |
|
||||||
|
|
||||||
|
**Телеграф** (раньше были проброшены только `KS`/`KY`/`ZZKM`/`ZZKS`/`ZZKY`):
|
||||||
|
|
||||||
|
| Команда | Что | Маппинг (`TCWSettings`) |
|
||||||
|
|---|---|---|
|
||||||
|
| `ZZCS` | скорость кейера, WPM (2 разряда) | `.Speed` — та же величина, что у `KS` |
|
||||||
|
| `ZZCL` | тон приёма/сайдтона, Гц | `.Pitch` |
|
||||||
|
| `ZZCI` | иамбик вкл/выкл | `.KeyerMode` (вкл выбирает iambic B, если стоял прямой ключ) |
|
||||||
|
| `ZZCB` | break-in прошивки | `.BreakIn` |
|
||||||
|
| `ZZCD` | hang-time break-in, мс | `.HangTimeMS` |
|
||||||
|
| `ZZCM` | сайдтон (у Thetis — «monitor **disable**», значение инвертировано) | `.SidetoneSW` или `.SidetoneHW` — тот, что отвечает за текущий источник манипуляции |
|
||||||
|
|
||||||
|
**Новое в контроллере.** Правка TX-настроек получила одну публичную дверь —
|
||||||
|
`SetTXSettings(const T; Notify)`, по образцу `SetCWSettings`: применение к WDSP,
|
||||||
|
перепосыл DUC Specific, живой TUN, запись в активный профиль и персист теперь
|
||||||
|
там, а не в обработчике MainForm (тот делегирует с `Notify=False`, иначе
|
||||||
|
`rfTXProfile` перезагрузил бы вкладку Transmit под руками у правящего).
|
||||||
|
Добавлен `SetTXMonVolume` — громкость self-monitor'а адресно, вне TX-контекста.
|
||||||
|
|
||||||
|
**Новое в движке.** Хелперы `OnOff` и `NumField` — разбор двухпозиционных и
|
||||||
|
числовых полей в одном месте, чтобы тела команд остались про смысл. Границы
|
||||||
|
значений (`MIC_GAIN_MIN`… в шапке юнита) повторяют контролы вкладок Transmit и
|
||||||
|
CW: по CAT прилетает что угодно, а контроллер принимает запись целиком и уже не
|
||||||
|
знает, кто её правил.
|
||||||
|
|
||||||
|
> Команды слайсов (`CATSliceEndpoint`) этих callback'ов не получают: TX-тракт
|
||||||
|
> один на радио, а профиль слайса переключается своим путём
|
||||||
|
> (`SetSliceTXProfile`). Все новые команды nil-безопасны и на слайс-порту
|
||||||
|
> отвечают дефолтом.
|
||||||
|
|
||||||
|
### 2.5 Ревизия алиасов и подписей
|
||||||
|
|
||||||
|
Подписи писались по буквам кода, а не по Thetis, и врали примерно в 150
|
||||||
|
местах. Хуже: часть **работающих** команд была привязана не к своей функции —
|
||||||
|
внешний софт получал осмысленный, но неверный ответ, что хуже честной
|
||||||
|
заглушки. Всё сверено с `CATCommands.cs` и исправлено.
|
||||||
|
|
||||||
|
**Команда делала не своё дело — теперь делает своё:**
|
||||||
|
|
||||||
|
| Команда | Было | Стало |
|
||||||
|
|---|---|---|
|
||||||
|
| `ZZMA` | режим (алиас `ZZMD`) | кнопка **MUT** → `SetMute` |
|
||||||
|
| `ZZRX` | переход на приём | **шаговый аттенюатор RX1** 0..31 дБ → `SetAtten` |
|
||||||
|
| `ZZRV` | версия ПО | **напряжение питания** PA (телеметрия) |
|
||||||
|
| `ZZMV` | индекс текущего режима | **число записей** памяти каналов |
|
||||||
|
| `ZZNN` | заглушка «ручной нотч» | **SNB** (спектральный бланкер) |
|
||||||
|
| `ZZNS` | SNB | кнопка **NR2** (режим NR 2) |
|
||||||
|
| `ZZVS` | алиас `ZZSP` (split) | **операции с парой VFO**: `0`=A→B, `1`=B→A, `2`=обмен, `3`=IF→VFO |
|
||||||
|
| `ZZBM` | режим (алиас `ZZMD`) | **VFO B вниз** на nn шагов (пара к `ZZBP`) |
|
||||||
|
| `ZZFI` | алиас `ZZBI` | сам **индекс фильтра** |
|
||||||
|
| `ZZBS` | алиас `ZZAC` | сам **выбор диапазона** |
|
||||||
|
| `ZZKM` | режим кейера | запуск **CW-макроса** (ячейка памяти сообщений) |
|
||||||
|
| `ZZFT` | всегда VFO A | **TX-частота** с учётом split |
|
||||||
|
| `ZZTX` | любое значение ≠ `0` поднимало передачу («2=TUNE» — выдумка) | строго `0`/`1`, иначе `?;` и передача НЕ включается (настройка — `ZZTU`) |
|
||||||
|
|
||||||
|
**Ошибочные алиасы сняты — теперь честные заглушки** (в скобках — где эта
|
||||||
|
функция живёт на самом деле): `ZZAA`, `ZZVG` (громкость — `ZZAG`) · `ZZOA`
|
||||||
|
(антенна RX1) · `ZZAP` (мощность — `ZZPC`) · `ZZDN` (мощность — `ZZPC`; сам
|
||||||
|
`ZZDN` = низ водопада) · `ZZAC` (диапазон — `ZZBS`) · `ZZBI` (фильтр — `ZZFI`)
|
||||||
|
· `ZZMB` (режим — `ZZMD`) · `ZZVA` (обмен VFO — `ZZVS`) · `ZZPD`, `ZZPO`
|
||||||
|
(мощность и питание — `ZZPC`/`ZZPS`) · `ZZQM` (режим — `ZZMD`) · `ZZSR`
|
||||||
|
(опорный уровень спектра) · `ZZRD`/`ZZRU` (RIT; шаг настройки — `ZZSA`/`ZZSB`).
|
||||||
|
|
||||||
|
> Что заметят внешние клиенты: `ZZRX;` больше не уводит в приём (для этого есть
|
||||||
|
> `ZZTX0;` и кенвудовская `RX;`), `ZZVA;` больше не меняет VFO местами (теперь
|
||||||
|
> `ZZVS0;`), `ZZKM` не выставляет режим кейера (иамбик вкл/выкл — `ZZCI`),
|
||||||
|
> `RD`/`RU` больше не перестраивают VFO (шаг — `UP`/`DN`).
|
||||||
|
> Во всех случаях старое поведение было отсебятиной.
|
||||||
|
|
||||||
|
**Кенвудовские команды.** Сверены отдельно: все 40 команд, реализованных в
|
||||||
|
Thetis, у нас есть, ширины полей совпадают с `CATStructs.xml`. Исправлено:
|
||||||
|
|
||||||
|
| Команда | Что было не так |
|
||||||
|
|---|---|
|
||||||
|
| `SM` | ответ был 4 цифры вместо **5** (`SM0015;` → `SM00019;`) — парсеры логгеров ждут пять; заодно принимаем селектор `2`, как шлёт Thetis |
|
||||||
|
| `RD` / `RU` | перестраивали VFO, хотя это **RIT** (его у нас нет) — стали заглушками, принимающими 5-значный аргумент |
|
||||||
|
| `KY` | поле текста у Kenwood фиксированной ширины и добито пробелами — хвост уходил в эфир словесными паузами, теперь срезается |
|
||||||
|
| `IF` | в комментарии значилось «37 байт», реальная и правильная длина ответа — **35** |
|
||||||
|
| `SH` / `SL` | обе крутят один и тот же индекс пресета; раздельных «сторон» у главного приёмника нет — оговорка добавлена в код |
|
||||||
|
| `CT` | принимала любой символ: `CT9;` молча ГАСИЛ тон вместо ответа `?;`. Теперь обёртка над `ZZTA`, как в эталоне, и сама `ZZTA` проверяет значение строго |
|
||||||
|
| `OF` / `OS` | несли реализацию сами, а `ZZOT`/`ZZOS` были заглушками — клиент Thetis обращается как раз к `ZZ*` и не получал ничего. Реализация переехала в `ZZOT`/`ZZOS`, кенвудовские стали обёртками. Заодно `OF` с нечисловым полем больше не обнуляет сдвиг молча |
|
||||||
|
|
||||||
|
**Разбор и транспорты.** Отдельный проход по валидации и вводу-выводу:
|
||||||
|
|
||||||
|
| Где | Что было не так |
|
||||||
|
|---|---|
|
||||||
|
| `TCATEngine.Parse` | команды без параметров не проверяли суффикс: `TXanything;` доходил до `CmdTX` и **поднимал передачу**; так же вели себя `RX UP DN BD BU QI RC ID IF`. Эталон отбраковывает лишний суффикс в парсере, по таблице ширин; у нас таблицы нет — список безаргументных команд теперь в `IsParamless` |
|
||||||
|
| `ZZFL` / `ZZFH` | принимали поле любой длины от 4 символов и гнали его через `StrToIntDef`: `ZZFLabcd;` молча схлопывал кромку в ноль. Поле фиксированное — ровно 5 символов со знаком, разбор строгий |
|
||||||
|
| `KY` / `ZZKY` | текст не ограничивался; поле у Kenwood фиксированное, 25 символов. Длиннее — `?;`: очередь передачи не должна расти произвольно, иначе один пакет уводит станцию в эфир на неопределённое время |
|
||||||
|
| `CATTcp.SendStr` | один `send` на ответ. TCP не обязан отдать весь буфер за раз — длинный ответ (`IF`, `ZZEB`, список режимов) мог уехать обрезанным, и молча: усечение здесь не ошибка. Теперь дописываем остаток в цикле |
|
||||||
|
| `CATSerial` | порт помечался активным ДО `SerOpen`; при отказе он навсегда оставался «работающим» в `ActiveCount` и UI, а причина нигде не оседала. Открытие переехало из потока в `TCATSerialPort.Start` (синхронно), появилось свойство `LastError`, поток теперь только читает, а закрывает владелец в `Stop`. Заодно Andromeda-порт назначается только на реально поднявшийся порт |
|
||||||
|
|
||||||
|
Формат `ZZOT` сверен отдельно: эталон читает 9 цифр как МГц с шестью знаками
|
||||||
|
после запятой (вставляет точку после третьего разряда) — численно это ровно те
|
||||||
|
же герцы, что пишем мы, поле совместимо. Мусор в `ZZOS` эталон трактует как
|
||||||
|
симплекс (`default` в `String2OffsetDirection`), и мы намеренно повторяем это,
|
||||||
|
а не отвечаем ошибкой.
|
||||||
|
|
||||||
|
Формат ответа на `ZZ*` (`ZZ` + код + **эхо суффикса запроса** + значение) сверен
|
||||||
|
с `CATParser.ParseExtended` — совпадает; это важно для `ZZSM0;`, где запрос
|
||||||
|
несёт селектор приёмника.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Что нужно сделать (актуально / есть чем закрыть)
|
## 3. Что нужно сделать (актуально / есть чем закрыть)
|
||||||
@@ -151,21 +295,30 @@ CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
|
|||||||
|
|
||||||
| Команда(ы) | Что | Чем закрыть | Сложность |
|
| Команда(ы) | Что | Чем закрыть | Сложность |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|
| `ZZVL`, `ZZUX`/`ZZUY` | блокировка VFO (общая и раздельная) | `SetVfoLock` | низкая |
|
||||||
|
| `ZZSW`, `ZZZW` | обмен VFO A/B | `VfoSwap` | низкая |
|
||||||
|
| `ZZAD ZZAE ZZAF ZZBE ZZBF`, `ZZSG`/`ZZSH` | сдвиг VFO на nn шагов | `StepIdxToHz` + `DoTuneUp/Down`, как у `ZZAU`/`ZZBP` | низкая |
|
||||||
| `ZZCU` | загрузка CPU (read) | телеметрия процесса | низкая |
|
| `ZZCU` | загрузка CPU (read) | телеметрия процесса | низкая |
|
||||||
| `ZZMY` / `ZZQR` / `ZZQS` / `ZZQM` | память каналов (запись/чтение/quick) | `ChannelStore` уже есть | средняя |
|
| `ZZSF` | фильтр «центр + ширина» одной командой | `SetFilterEdges` | низкая |
|
||||||
| `ZZFD` | FM-девиация (radio button) | нужен публичный `SetFMDeviation` (сейчас только поле `FFMDeviation`) | низкая |
|
| `ZZSG`/`ZZSH`, `ZZSD` | шаг VFO B и колеса — по одному шагу | те же `SetVfoB`/`DoTune*` | низкая |
|
||||||
| `ZZDM` / `ZZDA` / `ZZPO` / `ZZDY` | режим/усреднение/пик/порог спектра | UI-параметры спектра | средняя |
|
| `ZZMY ZZMZ ZZMW ZZMX ZZQR ZZQS ZZQM` | запись/чтение памяти каналов (счётчик `ZZMV` уже есть) | `ChannelStore` / `ChannelController` | средняя |
|
||||||
|
| `ZZDN`/`ZZDO`, `ZZDP`/`ZZDQ`/`ZZDR` | низ/верх водопада, сетка спектра | `FSpecRefLevel`/`FSpecRange`, per-pan дисплей | средняя |
|
||||||
|
| `ZZPD`/`ZZPE`, `ZZPY`/`ZZPZ` | пан и зум дисплея | `SetPanZoomPan` | средняя |
|
||||||
|
| `ZZSN`, `ZZZM`, `ZZZV` | серийник, модель, версия железа | `BoardUtils` | низкая |
|
||||||
|
| `ZZXN` / `ZZXV` | составные статусы RX1/VFO | сборка из готовых геттеров | средняя |
|
||||||
|
| `ZZOC`/`ZZOD`/`ZZOE` | антенны | `SetAnt936x` (порты AD936x) | средняя |
|
||||||
|
| `ZZDX` / `ZZDY` | кнопка DX и порог спотов | флаг `ShowSpots` живёт в `MainForm` — сперва поднять в контроллер | средняя |
|
||||||
| `ZZAS` | RX2 AGC-T | — только при появлении RX2 | — |
|
| `ZZAS` | RX2 AGC-T | — только при появлении RX2 | — |
|
||||||
|
|
||||||
> Прежде чем реализовывать новую команду, сверяйся с форматом поля в
|
> Прежде чем реализовывать новую команду, сверяйся с форматом поля в
|
||||||
> `Thetis/.../CAT/CATStructs.xml` (`<nsetparms>` / `<ngetparms>`) и телом
|
> `Thetis/.../CAT/CATStructs.xml` (`<nsetparms>` / `<ngetparms>`) и телом
|
||||||
> метода в `CATCommands.cs`, чтобы ширина/знак значения совпадали.
|
> метода в `CATCommands.cs`, чтобы ширина/знак значения совпадали. И проверяй
|
||||||
|
> **смысл**: подпись у заглушки в диспетчере может врать (см. §1.2).
|
||||||
|
|
||||||
### 3.1 Совсем не обрабатываются (вернут `?;`)
|
### 3.1 Совсем не обрабатываются (вернут `?;`)
|
||||||
|
|
||||||
`ZZCU` (CPU), `ZZDY` (DX threshold), `ZZPK`/`ZZPL` (речевой компрессор),
|
`ZZCU` (CPU), `ZZDY` (DX threshold), `ZZQA`/`ZZQB` (Quick Play/Rec),
|
||||||
`ZZQA`/`ZZQB` (Quick Play/Rec), `ZZVF` (VAC stereo).
|
`ZZVF` (VAC stereo). Из них реалистично добавить только `ZZCU` и `ZZDY`.
|
||||||
Из них реалистично добавить только `ZZCU` и (частично) `ZZDY`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -176,13 +329,15 @@ CTCSS on/tone, FM repeater dir/offset, FM step, AGC-T, CTUN.
|
|||||||
|
|
||||||
| Область | Команды (примеры) | Причина |
|
| Область | Команды (примеры) | Причина |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Второй приёмник RX2 / sub-RX** | `ZZAB ZZAS ZZBA ZZMB ZZOB`, ext `BF BG BR DO DQ DR OF OG OH OL SG SH SS` | один приёмник |
|
| **Второй приёмник RX2 / sub-RX** | `ZZAB ZZAS ZZBA ZZBB ZZBT ZZMB ZZOB ZZRS ZZSV ZZSX ZZNC ZZND ZZNF ZZNO ZZNU ZZNW ZZFJ ZZFR ZZFS ZZGU ZZLE ZZLF ZZPB ZZRY ZZXO` | один приёмник |
|
||||||
| **TX-DSP тракт** | mic gain `ZZMG`/`MG`, монитор `ZZMO`/`MO`, TX-EQ `ZZEB/ER/ET`, компрессор `ZZPK/ZZPL/CF CI CL CM`, two-tone `ZZUS`, VAC `ZZVB/ZZVF` | нет обработки передачи |
|
| **VAC / VAC2** | `ZZVC ZZVD ZZVF ZZVI ZZVK ZZVM ZZVO ZZVQ ZZVR ZZVT ZZVU ZZVV ZZVX ZZVY ZZVZ ZZYA ZZYB` | нет виртуального аудиокабеля |
|
||||||
| **CW-кейер / CWX** | `ZZKM ZZKS ZZKY ZZCB ZZCD ZZCS`, `KY KS` | нет кейера |
|
| **RTTY / DIGI-офсеты** | `ZZRA ZZRB ZZRH ZZRL` | нет RTTY-движка |
|
||||||
| **RTTY / DIGI** | `ZZRA ZZRL`, ext `RF RH RM RS RY` | нет RTTY-движка |
|
| **RIT / XIT** | `RT XT RC`, `ZZRF ZZRT ZZXC ZZXD ZZXF ZZXS ZZXU` | нет тракта смещения приёма (CTUN — не замена) |
|
||||||
| **RIT / XIT** | `RT XT RC RU RD` (как RIT), ext `RB RF RH` | нет смещения приёма |
|
| **Diversity** | `ZZDB ZZDC ZZDD ZZDE ZZDF ZZDG ZZDH` (но не `ZZDA` — это усреднение спектра) | нет диверсити-приёма |
|
||||||
| **Diversity** | `ZZDA(div) ZZDB`, ext `DA` | нет диверсити-приёма |
|
| **Компандер, noise gate, APF, VOX** | `ZZCP ZZCT`, `ZZGE ZZGL`, `ZZAA ZZAB ZZAT ZZAY`, `ZZVE ZZXH` | таких блоков в тракте нет (речевой компрессор — есть, это `ZZPK`/`ZZPL`) |
|
||||||
| **Спектр/водопад (внутр.)** | ext `SV SW SX SY SZ WT WU WV WW` | не управляется по CAT |
|
| **Микшер Flex 5000 / F1500, FlexWire** | `ZZWA…ZZWS`, `ZZWT…ZZWW`, `ZZFV ZZFW ZZFX ZZFY` | железо другого вендора |
|
||||||
|
| **ATU (Aries/Ganymede)** | `ZZOV ZZOW ZZOX ZZOZ ZZZA` | нет тюнера |
|
||||||
|
| **DSP-буферы, JSON-команды** | `ZZHA ZZHR ZZHT ZZHU ZZHV ZZHW ZZHX`, `ZZJP ZZJQ ZZJR ZZJS` | внутренние крутилки Thetis |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user