mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Внешняя программа управляет отдельным слайсом как отдельным трансивером
через свой 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>
155 lines
6.0 KiB
ObjectPascal
155 lines
6.0 KiB
ObjectPascal
unit BoardUtils;
|
|
|
|
{$IFDEF FPC}
|
|
{$MODE Delphi}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
type
|
|
// Один диапазон плана (имя кнопки + частота по умолчанию). Общий тип для
|
|
// desktop/web UI — единый источник band-плана через TRadioController.BandPlan.
|
|
TBandInfo = record
|
|
Name: string;
|
|
FreqHz: Double;
|
|
end;
|
|
TBandPlanArray = array of TBandInfo;
|
|
|
|
function BoardTypeName(BoardType: Integer): string;
|
|
// Короткое человекочитаемое имя AD936x-платы по hw_model (Pluto / LibreSDR / прочие).
|
|
function PlutoModelName(const HwModel: string): string;
|
|
// Индекс диапазона по частоте. Pluto=True → VHF/UHF план, иначе HF (openHPSDR).
|
|
function FreqToBandIdx(Hz: Double; Pluto: Boolean = False): Integer;
|
|
// Короткое имя диапазона для кнопки (зависит от плана).
|
|
function BandName(Idx: Integer; Pluto: Boolean = False): string;
|
|
// Частота по умолчанию при входе в диапазон (если нет сохранённой/в диапазоне).
|
|
function BandDefaultFreq(Idx: Integer; Pluto: Boolean = False): Double;
|
|
// Границы диапазона по плану. False = слот пуст/индекс вне плана (Lo=Hi=0).
|
|
function BandEdges(Idx: Integer; Pluto: Boolean; out LoHz, HiHz: Double): Boolean;
|
|
// Полный band-план активного бэкенда (имена + дефолтные частоты).
|
|
function BuildBandPlan(Pluto: Boolean): TBandPlanArray;
|
|
|
|
implementation
|
|
|
|
uses SysUtils;
|
|
|
|
const
|
|
// Максимум слотов среди всех планов (кнопки/кэши бэндов рассчитаны на это
|
|
// число). VHF/UHF-план Pluto использует все 12; HF-план (openHPSDR) — только
|
|
// 11, 12-й слот пустой (кнопка прячется, см. TMainForm.RelabelBands).
|
|
BAND_COUNT = 12;
|
|
|
|
// ---- HF/6m план (openHPSDR), 11 диапазонов + пустой 12-й слот ----
|
|
HF_NAMES: array[0..BAND_COUNT-1] of string = (
|
|
'160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m','');
|
|
HF_LO: array[0..BAND_COUNT-1] of Double = (
|
|
1800000, 3500000, 5330000, 7000000, 10100000,
|
|
14000000, 18068000, 21000000, 24890000, 28000000, 50000000, 0);
|
|
HF_HI: array[0..BAND_COUNT-1] of Double = (
|
|
2000000, 4000000, 5410000, 7300000, 10150000,
|
|
14350000, 18168000, 21450000, 24990000, 29700000, 54000000, 0);
|
|
HF_DEF: array[0..BAND_COUNT-1] of Double = (
|
|
1840000, 3700000, 5357000, 7100000, 10120000,
|
|
14200000, 18130000, 21300000, 24940000, 28400000, 50150000, 0);
|
|
|
|
// ---- VHF/UHF/SHF план (Pluto / AD936x), 12 слотов ----
|
|
// OIRT (65..74 МГц) — вещательный УКВ-диапазон, кнопка стоит перед FM.
|
|
VU_NAMES: array[0..BAND_COUNT-1] of string = (
|
|
'6m','4m','2m','1.25m','70cm','23cm','13cm','9cm','OIRT','FM','Air','ADS-B');
|
|
VU_LO: array[0..BAND_COUNT-1] of Double = (
|
|
50000000, 70000000, 144000000, 222000000, 430000000,
|
|
1240000000, 2300000000, 3300000000, 65000000, 88000000, 118000000, 1087000000);
|
|
VU_HI: array[0..BAND_COUNT-1] of Double = (
|
|
54000000, 70500000, 148000000, 225000000, 440000000,
|
|
1300000000, 2450000000, 3500000000, 74000000, 108000000, 137000000, 1093000000);
|
|
VU_DEF: array[0..BAND_COUNT-1] of Double = (
|
|
50150000, 70200000, 145000000, 223500000, 433500000,
|
|
1296200000, 2320000000, 3400100000, 69000000, 100000000, 124000000, 1090000000);
|
|
|
|
function BoardTypeName(BoardType: Integer): string;
|
|
begin
|
|
case BoardType of
|
|
1: Result := 'HERMES (ANAN-10/100)';
|
|
2: Result := 'HERMES-E (ANAN-10E/100B)';
|
|
3: Result := 'ANGELIA (ANAN-100D)';
|
|
4: Result := 'ORION (ANAN-200D)';
|
|
5: Result := 'ORION MkII (ANAN-7000/8000)';
|
|
6: Result := 'HERMES-LITE 2';
|
|
10: Result := 'SATURN (G2)';
|
|
else Result := Format('Unknown Board #%d', [BoardType]);
|
|
end;
|
|
end;
|
|
|
|
function PlutoModelName(const HwModel: string): string;
|
|
var
|
|
L: string;
|
|
begin
|
|
// hw_model клонов AD936x обычно содержит маркер платы. Узнаём Pluto/LibreSDR,
|
|
// иначе показываем сырой hw_model, а при его отсутствии — обобщённое имя.
|
|
L := LowerCase(HwModel);
|
|
if Pos('libre', L) > 0 then Result := 'LibreSDR'
|
|
else if Pos('pluto', L) > 0 then Result := 'PlutoSDR'
|
|
else if HwModel <> '' then Result := HwModel
|
|
else Result := 'AD936x SDR';
|
|
end;
|
|
|
|
function BandEdges(Idx: Integer; Pluto: Boolean; out LoHz, HiHz: Double): Boolean;
|
|
begin
|
|
LoHz := 0; HiHz := 0;
|
|
Result := (Idx >= 0) and (Idx < BAND_COUNT);
|
|
if not Result then Exit;
|
|
if Pluto then begin LoHz := VU_LO[Idx]; HiHz := VU_HI[Idx]; end
|
|
else begin LoHz := HF_LO[Idx]; HiHz := HF_HI[Idx]; end;
|
|
Result := HiHz > LoHz; // пустой 12-й слот HF-плана
|
|
end;
|
|
|
|
function FreqToBandIdx(Hz: Double; Pluto: Boolean): Integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := -1;
|
|
if Pluto then
|
|
begin
|
|
for i := 0 to BAND_COUNT - 1 do
|
|
if (Hz >= VU_LO[i]) and (Hz <= VU_HI[i]) then Exit(i);
|
|
end
|
|
else
|
|
begin
|
|
for i := 0 to BAND_COUNT - 1 do
|
|
if (Hz >= HF_LO[i]) and (Hz <= HF_HI[i]) then Exit(i);
|
|
end;
|
|
end;
|
|
|
|
function BandName(Idx: Integer; Pluto: Boolean): string;
|
|
begin
|
|
if (Idx < 0) or (Idx >= BAND_COUNT) then Exit('');
|
|
if Pluto then Result := VU_NAMES[Idx] else Result := HF_NAMES[Idx];
|
|
end;
|
|
|
|
function BandDefaultFreq(Idx: Integer; Pluto: Boolean): Double;
|
|
begin
|
|
if (Idx < 0) or (Idx >= BAND_COUNT) then Exit(0);
|
|
if Pluto then Result := VU_DEF[Idx] else Result := HF_DEF[Idx];
|
|
end;
|
|
|
|
function BuildBandPlan(Pluto: Boolean): TBandPlanArray;
|
|
var i: Integer;
|
|
begin
|
|
SetLength(Result, BAND_COUNT);
|
|
for i := 0 to BAND_COUNT - 1 do
|
|
begin
|
|
if Pluto then
|
|
begin
|
|
Result[i].Name := VU_NAMES[i];
|
|
Result[i].FreqHz := VU_DEF[i];
|
|
end
|
|
else
|
|
begin
|
|
Result[i].Name := HF_NAMES[i];
|
|
Result[i].FreqHz := HF_DEF[i];
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end.
|