mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(cat): CAT-порт на каждый слайс + Auto TX (эталон SmartSDR CAT)
Внешняя программа управляет отдельным слайсом как отдельным трансивером
через свой TCP-порт. COM-порты и 19090 остаются за главным приёмником
(слайс A) и не тронуты.
Протокол не дублируется: TCATEngine — чистый парсер поверх записи
callback'ов TCATContext, TCATTcpServer — транспорт поверх движка, поэтому
слайс-CAT = тот же движок с другим контекстом. Новый TCATSliceEndpoint
(движок + TCP-сервер на слот), массивом владеет TCATAdapter. Привязка к
СЛОТУ (буква B..G), не к Id: порт живёт, даже когда слайса нет (PS0).
Auto TX = «Auto Switch TX Slice» из SmartSDR CAT. Вся логика в одной точке
— TRadioController.RequestSliceTx, исполняется в потоке контроллера:
• пока кто-то уже в эфире (любой источник) — заявка игнорируется,
перехвата передачи нет никогда;
• без Auto TX слайс передаёт, только если уже выбран TX-источником;
• RX; снимает только СВОЮ передачу;
• TX-источник после отпускания остаётся на слайсе (как в SmartSDR).
На флаге слайса бейдж TX → AutoTX.
Частота слайса — TuneSliceInBand: свобода в пределах ВКЛЮЧЁННОГО диапазона
(трансвертер → его FreqBegin/FreqEnd, иначе band-план), другой диапазон —
отказ. Вне захваченной полосы окно DDC переезжает: доп. пан — центром на
цель (rfPanFreq → UI перекладывает флаги/шапку/зум-бар), пан 0 — центр
посередине между целью и главным VFO и только если оба влезают (общее
железное окно, главный приёмник не оглушаем).
Настройки: вкладка Slices (enable/порт 19091../Auto TX на слайс), персист
в секции cat + device-blob.
Попутно:
• ZZFL/ZZFH больше не заглушки — новые callback'и кромок фильтра
(у главного nil → прежний дефолт режима), хелпер SignedPad;
• FilterIdxFor, AGCModeToUI, BoardUtils.BandEdges;
• DSP-состояние слайса (NR/NB/SNB/ANF) зеркалится в TCtrlSlice — флаг
слайса рисовал нули и AGC главного;
• FUIReady в TMainForm.OnControllerState: адаптеры, создаваемые по ходу
FormCreate, больше не могут уронить старт рендером до постройки
виджетов (на этом падал Auto TX из настроек).
Проверено вживую: все порты 19091..19096 слушают, PS;/ZZFA;/MD;/ID;/SM0;/
ZZTX; отвечают; старт со всеми включёнными слайсами чист под gdb.
Документ — doc/CAT_SLICES.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,571 @@
|
||||
unit CATSliceEndpoint;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// TCATSliceEndpoint — CAT-порт ОДНОГО дополнительного слайса (B, C, …).
|
||||
//
|
||||
// Протокол не дублируется: движок TCATEngine работает поверх плоской записи
|
||||
// callback'ов TCATContext, поэтому «CAT слайса» — это тот же движок с другим
|
||||
// контекстом, где геттеры/сеттеры смотрят в FSlices[слот], а не в главный RX.
|
||||
// Транспорт — тот же TCATTcpServer, просто на своём порту.
|
||||
//
|
||||
// TCATAdapter ──┬── главный контекст ── TCATEngine ── serial + TCP 19090
|
||||
// └── TCATSliceEndpoint ── TCATEngine ── TCP 19091 (слайс B)
|
||||
// TCATSliceEndpoint ── TCATEngine ── TCP 19092 (слайс C) …
|
||||
//
|
||||
// Эталон поведения — SmartSDR CAT: порт привязан к слайсу, а флаг Auto TX
|
||||
// («Auto Switch TX Slice») разрешает команде TX самой сделать этот слайс
|
||||
// источником передачи. Вся защита «в эфире одновременно только один слайс»
|
||||
// живёт в TRadioController.RequestSliceTx (выполняется в потоке контроллера).
|
||||
//
|
||||
// Привязка — к СЛОТУ (букве), а не к Id слайса: слайс на слоте может быть
|
||||
// создан/удалён в любой момент, порт при этом остаётся поднятым. Когда слайса
|
||||
// нет, эндпоинт отвечает «выключенным радио» (PS0) и игнорирует команды.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Math, SyncObjs,
|
||||
RadioController, CATEngine, CATTcp, Settings, WDSPEngine, RadioModes;
|
||||
|
||||
const
|
||||
// Слотов слайс-CAT = потолок слайсов движка (буквы B..). Настройки хранят
|
||||
// ровно столько же полей — иначе персист разъедется с рантаймом.
|
||||
CAT_SLICE_SLOTS = MAX_SLICES;
|
||||
{$IF CAT_SLICE_SLOTS <> CFG_CAT_SLICE_COUNT}
|
||||
{$ERROR CFG_CAT_SLICE_COUNT must match WDSPEngine.MAX_SLICES}
|
||||
{$IFEND}
|
||||
|
||||
type
|
||||
TCATSliceEndpoint = class
|
||||
private
|
||||
FController: TRadioController;
|
||||
FSlot: Integer; // 0 = слайс B
|
||||
FEngine: TCATEngine;
|
||||
FTcp: TCATTcpServer;
|
||||
FPort: Integer;
|
||||
FEnabled: Boolean;
|
||||
// Теневой VFO B: своего второго VFO у слайса нет, но управляющее ПО любит
|
||||
// его читать/писать (split-режимы). Храним значение, эфир им не двигаем.
|
||||
FShadowVfoB: Double;
|
||||
// Параметры команды для маршалинга в поток контроллера (Invoke). Один
|
||||
// эндпоинт может обслуживать несколько TCP-клиентов, поэтому «положить
|
||||
// параметр + вызвать Invoke» защищено локом.
|
||||
FCmdLock: TCriticalSection;
|
||||
FSyncFreq: Double;
|
||||
FSyncInt: Integer;
|
||||
FSyncBool: Boolean;
|
||||
|
||||
function GetLetter: Char;
|
||||
function SliceId: Integer; // 0 = слайса на слоте нет
|
||||
function Slice(out S: TCtrlSlice): Boolean;
|
||||
function SliceBW(const S: TCtrlSlice): Integer; // ширина фильтра под режим
|
||||
|
||||
// ── Геттеры (CAT-поток, read-only) ──
|
||||
function GetVfoA: Double;
|
||||
function GetVfoB: Double;
|
||||
function GetMode: Integer;
|
||||
function GetFilterIdx: Integer;
|
||||
function GetFilterBW: Integer;
|
||||
function GetFilterLow: Integer;
|
||||
function GetFilterHigh: Integer;
|
||||
function GetAGCMode: Integer;
|
||||
function GetVolume: Integer;
|
||||
function GetDriveLevel: Integer;
|
||||
function GetNRMode: Integer;
|
||||
function GetNBMode: Integer;
|
||||
function GetSNB: Boolean;
|
||||
function GetANF: Boolean;
|
||||
function GetTX: Boolean;
|
||||
function GetRunning: Boolean;
|
||||
function GetSMeter: Double;
|
||||
function GetBand: Integer;
|
||||
function GetSquelchOn: Boolean;
|
||||
function GetSquelchLevel: Integer;
|
||||
|
||||
// ── Сеттеры (CAT-поток → Invoke в поток контроллера) ──
|
||||
procedure SetVfoA(V: Double);
|
||||
procedure SetVfoB(V: Double);
|
||||
procedure SetMode(V: Integer);
|
||||
procedure SetFilterIdx(V: Integer);
|
||||
procedure SetFilterLow(V: Integer);
|
||||
procedure SetFilterHigh(V: Integer);
|
||||
procedure SetAGCMode(V: Integer);
|
||||
procedure SetVolume(V: Integer);
|
||||
procedure SetDriveLevel(V: Integer);
|
||||
procedure SetNRMode(V: Integer);
|
||||
procedure SetNBMode(V: Integer);
|
||||
procedure SetSNB(V: Boolean);
|
||||
procedure SetANF(V: Boolean);
|
||||
procedure SetTransmitting(V: Boolean);
|
||||
procedure SetSquelchOn(V: Boolean);
|
||||
procedure SetSquelchLevel(V: Integer);
|
||||
procedure DoTuneUp;
|
||||
procedure DoTuneDown;
|
||||
|
||||
// ── Sync-методы (поток контроллера) ──
|
||||
procedure SyncVfoA;
|
||||
procedure SyncMode;
|
||||
procedure SyncFilterIdx;
|
||||
procedure SyncFilterLow;
|
||||
procedure SyncFilterHigh;
|
||||
procedure SyncAGC;
|
||||
procedure SyncVolume;
|
||||
procedure SyncDrive;
|
||||
procedure SyncNR;
|
||||
procedure SyncNB;
|
||||
procedure SyncSNB;
|
||||
procedure SyncANF;
|
||||
procedure SyncTx;
|
||||
procedure SyncSquelchOn;
|
||||
procedure SyncSquelchLevel;
|
||||
procedure SyncTuneBy;
|
||||
|
||||
// Параметр команды + Invoke под локом: между «положить» и «исполнить»
|
||||
// не должен вклиниться второй клиент этого же порта.
|
||||
procedure CmdFreq(V: Double; P: TThreadMethod);
|
||||
procedure CmdInt (V: Integer; P: TThreadMethod);
|
||||
procedure CmdBool(V: Boolean; P: TThreadMethod);
|
||||
procedure ApplyDSP(NR, NB: Integer; SNBOn, ANFOn: Boolean);
|
||||
public
|
||||
constructor Create(AController: TRadioController; ASlot: Integer);
|
||||
destructor Destroy; override;
|
||||
// Поднять/опустить TCP-порт эндпоинта. Перезапуск только при реальном
|
||||
// изменении — иначе правка чужой строки настроек рвала бы живые сессии.
|
||||
procedure Apply(AEnabled: Boolean; APort: Integer);
|
||||
property Slot: Integer read FSlot;
|
||||
property Port: Integer read FPort;
|
||||
property Enabled: Boolean read FEnabled;
|
||||
property Letter: Char read GetLetter;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
constructor TCATSliceEndpoint.Create(AController: TRadioController; ASlot: Integer);
|
||||
var
|
||||
Ctx: TCATContext;
|
||||
begin
|
||||
inherited Create;
|
||||
FController := AController;
|
||||
FSlot := ASlot;
|
||||
FPort := 0;
|
||||
FEnabled := False;
|
||||
FShadowVfoB := 0;
|
||||
FCmdLock := TCriticalSection.Create;
|
||||
|
||||
FillChar(Ctx, SizeOf(Ctx), 0);
|
||||
// Слайс-скоуп.
|
||||
Ctx.GetVfoA := @GetVfoA;
|
||||
Ctx.GetVfoB := @GetVfoB;
|
||||
Ctx.GetMode := @GetMode;
|
||||
Ctx.GetFilterIdx := @GetFilterIdx;
|
||||
Ctx.GetFilterBW := @GetFilterBW;
|
||||
Ctx.GetFilterLow := @GetFilterLow;
|
||||
Ctx.GetFilterHigh := @GetFilterHigh;
|
||||
Ctx.GetAGCMode := @GetAGCMode;
|
||||
Ctx.GetVolume := @GetVolume;
|
||||
Ctx.GetNRMode := @GetNRMode;
|
||||
Ctx.GetNBMode := @GetNBMode;
|
||||
Ctx.GetSNBEnabled := @GetSNB;
|
||||
Ctx.GetANFEnabled := @GetANF;
|
||||
Ctx.GetTransmitting := @GetTX;
|
||||
Ctx.GetRunning := @GetRunning;
|
||||
Ctx.GetSMeter := @GetSMeter;
|
||||
Ctx.GetSquelchOn := @GetSquelchOn;
|
||||
Ctx.GetSquelchLevel := @GetSquelchLevel;
|
||||
Ctx.SetVfoA := @SetVfoA;
|
||||
Ctx.SetVfoB := @SetVfoB;
|
||||
Ctx.SetMode := @SetMode;
|
||||
Ctx.SetFilterIdx := @SetFilterIdx;
|
||||
Ctx.SetFilterLow := @SetFilterLow;
|
||||
Ctx.SetFilterHigh := @SetFilterHigh;
|
||||
Ctx.SetAGCMode := @SetAGCMode;
|
||||
Ctx.SetVolume := @SetVolume;
|
||||
Ctx.SetNRMode := @SetNRMode;
|
||||
Ctx.SetNBMode := @SetNBMode;
|
||||
Ctx.SetSNBEnabled := @SetSNB;
|
||||
Ctx.SetANFEnabled := @SetANF;
|
||||
Ctx.SetTransmitting := @SetTransmitting;
|
||||
Ctx.SetSquelchOn := @SetSquelchOn;
|
||||
Ctx.SetSquelchLevel := @SetSquelchLevel;
|
||||
Ctx.DoTuneUp := @DoTuneUp;
|
||||
Ctx.DoTuneDown := @DoTuneDown;
|
||||
// Глобальные (общие с главным) параметры: передатчик один на всё радио.
|
||||
Ctx.GetDriveLevel := @GetDriveLevel;
|
||||
Ctx.SetDriveLevel := @SetDriveLevel;
|
||||
Ctx.GetCurrentBand := @GetBand;
|
||||
// Осознанно НЕ проброшены: split/VFO-переключение (у слайса нет второго VFO),
|
||||
// смена диапазона (диапазон — свойство пана слайса), CTUN/AGC-T/CTCSS/репитер
|
||||
// (настройки главного тракта). Движок ответит на них валидной заглушкой.
|
||||
|
||||
FEngine := TCATEngine.Create(Ctx);
|
||||
FTcp := TCATTcpServer.Create(FEngine);
|
||||
end;
|
||||
|
||||
destructor TCATSliceEndpoint.Destroy;
|
||||
begin
|
||||
if Assigned(FTcp) then begin FTcp.Stop; FreeAndNil(FTcp); end;
|
||||
FreeAndNil(FEngine);
|
||||
FreeAndNil(FCmdLock);
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetLetter: Char;
|
||||
begin
|
||||
Result := TRadioController.SliceSlotLetter(FSlot);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.Apply(AEnabled: Boolean; APort: Integer);
|
||||
begin
|
||||
APort := EnsureRange(APort, 1, 65535);
|
||||
if (AEnabled = FEnabled) and (APort = FPort) and (FTcp.Running = AEnabled) then Exit;
|
||||
FTcp.Stop;
|
||||
FEnabled := AEnabled;
|
||||
FPort := APort;
|
||||
if FEnabled then
|
||||
begin
|
||||
FTcp.Port := FPort;
|
||||
FTcp.Start;
|
||||
end;
|
||||
end;
|
||||
|
||||
// ── Доступ к слайсу слота ────────────────────────────────────────────────────
|
||||
|
||||
function TCATSliceEndpoint.SliceId: Integer;
|
||||
begin
|
||||
Result := FController.SliceIdBySlot(FSlot);
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.Slice(out S: TCtrlSlice): Boolean;
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
Result := (Id > 0) and FController.GetSlice(Id, S);
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.SliceBW(const S: TCtrlSlice): Integer;
|
||||
// Как в UI-флаге: у односторонних режимов полоса = «своя» кромка.
|
||||
begin
|
||||
case S.Mode of
|
||||
MODE_LSB, MODE_DIGL: Result := -S.FilterLo;
|
||||
MODE_USB, MODE_DIGU: Result := S.FilterHi;
|
||||
else Result := S.FilterHi - S.FilterLo;
|
||||
end;
|
||||
end;
|
||||
|
||||
// ── Геттеры ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function TCATSliceEndpoint.GetVfoA: Double;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := S.TargetHz else Result := 0;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetVfoB: Double;
|
||||
begin
|
||||
Result := FShadowVfoB;
|
||||
if Result <= 0 then Result := GetVfoA;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetMode: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := S.Mode else Result := MODE_USB;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetFilterIdx: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := FController.FilterIdxFor(S.Mode, SliceBW(S))
|
||||
else Result := 0;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetFilterBW: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := SliceBW(S) else Result := 0;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetFilterLow: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := S.FilterLo else Result := 0;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetFilterHigh: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := S.FilterHi else Result := 0;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetAGCMode: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := TRadioController.AGCModeToUI(S.AGC)
|
||||
else Result := 1;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetVolume: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := EnsureRange(Round(S.Volume * 100), 0, 100)
|
||||
else Result := 0;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetDriveLevel: Integer;
|
||||
begin Result := FController.FDrivePercent; end;
|
||||
|
||||
function TCATSliceEndpoint.GetNRMode: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := S.NRMode else Result := 0;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetNBMode: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := S.NBMode else Result := 0;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetSNB: Boolean;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
Result := Slice(S) and S.SNB;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetANF: Boolean;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
Result := Slice(S) and S.ANF;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetTX: Boolean;
|
||||
// «Передаю» для этого порта = передаёт именно ЭТОТ слайс. Чужой эфир на
|
||||
// CAT-порту слайса виден как приём (иначе управляющее ПО решит, что его PTT
|
||||
// прошла, и будет ждать несуществующую передачу).
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
Result := (Id > 0) and FController.FTransmitting and (FController.FTxSliceId = Id);
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetRunning: Boolean;
|
||||
begin
|
||||
Result := (SliceId > 0) and FController.FRunning;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetSMeter: Double;
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if Id > 0 then Result := FController.SliceSMeter(Id) else Result := -140;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetBand: Integer;
|
||||
begin Result := FController.FCurrentBand; end;
|
||||
|
||||
function TCATSliceEndpoint.GetSquelchOn: Boolean;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
Result := Slice(S) and S.FMSQOn;
|
||||
end;
|
||||
|
||||
function TCATSliceEndpoint.GetSquelchLevel: Integer;
|
||||
var S: TCtrlSlice;
|
||||
begin
|
||||
if Slice(S) then Result := S.FMSQLevel else Result := 0;
|
||||
end;
|
||||
|
||||
// ── Сеттеры (маршалинг в поток контроллера) ──────────────────────────────────
|
||||
|
||||
procedure TCATSliceEndpoint.CmdFreq(V: Double; P: TThreadMethod);
|
||||
begin
|
||||
FCmdLock.Acquire;
|
||||
try FSyncFreq := V; FController.Invoke(P); finally FCmdLock.Release; end;
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.CmdInt(V: Integer; P: TThreadMethod);
|
||||
begin
|
||||
FCmdLock.Acquire;
|
||||
try FSyncInt := V; FController.Invoke(P); finally FCmdLock.Release; end;
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.CmdBool(V: Boolean; P: TThreadMethod);
|
||||
begin
|
||||
FCmdLock.Acquire;
|
||||
try FSyncBool := V; FController.Invoke(P); finally FCmdLock.Release; end;
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetVfoA(V: Double);
|
||||
begin CmdFreq(V, @SyncVfoA); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncVfoA;
|
||||
// Свобода в пределах ВКЛЮЧЁННОГО диапазона: за захваченную полосу окно DDC
|
||||
// переедет само (TuneSliceInBand), за границы диапазона — команда отбрасывается.
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if Id > 0 then FController.TuneSliceInBand(Id, FSyncFreq);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetVfoB(V: Double);
|
||||
begin FShadowVfoB := V; end; // теневой VFO: эфир не трогаем
|
||||
|
||||
procedure TCATSliceEndpoint.SetMode(V: Integer);
|
||||
begin CmdInt(V, @SyncMode); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncMode;
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if (Id > 0) and (FSyncInt >= MODE_MIN) and (FSyncInt <= MODE_MAX) then
|
||||
FController.SetSliceMode(Id, FSyncInt);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetFilterIdx(V: Integer);
|
||||
begin CmdInt(V, @SyncFilterIdx); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncFilterIdx;
|
||||
var Id: Integer; S: TCtrlSlice;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if (Id > 0) and FController.GetSlice(Id, S) then
|
||||
FController.SetSliceModeBW(Id, S.Mode, FController.FilterBWFor(S.Mode, FSyncInt));
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetFilterLow(V: Integer);
|
||||
begin CmdInt(V, @SyncFilterLow); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncFilterLow;
|
||||
var Id: Integer; S: TCtrlSlice;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if (Id > 0) and FController.GetSlice(Id, S) and (FSyncInt < S.FilterHi) then
|
||||
FController.SetSliceFilter(Id, FSyncInt, S.FilterHi);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetFilterHigh(V: Integer);
|
||||
begin CmdInt(V, @SyncFilterHigh); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncFilterHigh;
|
||||
var Id: Integer; S: TCtrlSlice;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if (Id > 0) and FController.GetSlice(Id, S) and (FSyncInt > S.FilterLo) then
|
||||
FController.SetSliceFilter(Id, S.FilterLo, FSyncInt);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetAGCMode(V: Integer);
|
||||
begin CmdInt(V, @SyncAGC); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncAGC;
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if Id > 0 then
|
||||
FController.SetSliceAGCMode(Id, TRadioController.AGCModeFromUI(FSyncInt));
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetVolume(V: Integer);
|
||||
begin CmdInt(V, @SyncVolume); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncVolume;
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if Id > 0 then
|
||||
FController.SetSliceVolume(Id, EnsureRange(FSyncInt, 0, 100) / 100.0);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetDriveLevel(V: Integer);
|
||||
begin CmdInt(V, @SyncDrive); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncDrive;
|
||||
begin FController.SetDrive(FSyncInt); end;
|
||||
|
||||
procedure TCATSliceEndpoint.ApplyDSP(NR, NB: Integer; SNBOn, ANFOn: Boolean);
|
||||
// SetSliceDSP принимает весь набор сразу — недостающее берём из состояния слайса.
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if Id > 0 then FController.SetSliceDSP(Id, NR, NB, SNBOn, ANFOn);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetNRMode(V: Integer);
|
||||
begin CmdInt(V, @SyncNR); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncNR;
|
||||
begin ApplyDSP(FSyncInt, GetNBMode, GetSNB, GetANF); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetNBMode(V: Integer);
|
||||
begin CmdInt(V, @SyncNB); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncNB;
|
||||
begin ApplyDSP(GetNRMode, FSyncInt, GetSNB, GetANF); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetSNB(V: Boolean);
|
||||
begin CmdBool(V, @SyncSNB); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncSNB;
|
||||
begin ApplyDSP(GetNRMode, GetNBMode, FSyncBool, GetANF); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetANF(V: Boolean);
|
||||
begin CmdBool(V, @SyncANF); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncANF;
|
||||
begin ApplyDSP(GetNRMode, GetNBMode, GetSNB, FSyncBool); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetTransmitting(V: Boolean);
|
||||
begin CmdBool(V, @SyncTx); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncTx;
|
||||
// Вся логика Auto TX и защита «передаёт только один» — в контроллере.
|
||||
var Id: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if Id > 0 then FController.RequestSliceTx(Id, FSyncBool);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetSquelchOn(V: Boolean);
|
||||
begin CmdBool(V, @SyncSquelchOn); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncSquelchOn;
|
||||
var Id: Integer; S: TCtrlSlice;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if (Id > 0) and FController.GetSlice(Id, S) then
|
||||
FController.SetSliceFMSquelch(Id, FSyncBool, S.FMSQLevel);
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.SetSquelchLevel(V: Integer);
|
||||
begin CmdInt(V, @SyncSquelchLevel); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncSquelchLevel;
|
||||
var Id: Integer; S: TCtrlSlice; Lvl: Integer;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if (Id > 0) and FController.GetSlice(Id, S) then
|
||||
begin
|
||||
Lvl := EnsureRange(FSyncInt, 0, 100);
|
||||
// Как у главного тракта: ненулевой порог включает шумоподавитель.
|
||||
FController.SetSliceFMSquelch(Id, S.FMSQOn or (Lvl > 0), Lvl);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TCATSliceEndpoint.DoTuneUp;
|
||||
begin CmdInt( 10, @SyncTuneBy); end;
|
||||
|
||||
procedure TCATSliceEndpoint.DoTuneDown;
|
||||
begin CmdInt(-10, @SyncTuneBy); end;
|
||||
|
||||
procedure TCATSliceEndpoint.SyncTuneBy;
|
||||
var Id: Integer; S: TCtrlSlice;
|
||||
begin
|
||||
Id := SliceId;
|
||||
if (Id > 0) and FController.GetSlice(Id, S) then
|
||||
FController.TuneSliceInBand(Id, S.TargetHz + FSyncInt);
|
||||
end;
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user