mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
add trancverter
This commit is contained in:
+143
@@ -11,6 +11,7 @@ uses SysUtils, Classes, Math, fpJSON, jsonparser, jsonscanner;
|
||||
const
|
||||
SETTINGS_FILE = 'hpsdr_settings.json';
|
||||
CFG_BAND_COUNT = 11;
|
||||
CFG_XVTR_COUNT = 8; // количество XVTR-слотов (трансверторов)
|
||||
CFG_BAND_DEFAULT_FREQ: array[0..CFG_BAND_COUNT-1] of Double = (
|
||||
1900000, 3750000, 5357000, 7100000, 10125000,
|
||||
14200000, 18120000, 21200000, 24940000, 28500000, 50150000);
|
||||
@@ -103,6 +104,33 @@ type
|
||||
TXSpecGridStep: Double; // grid step in dB, e.g. 10
|
||||
end;
|
||||
|
||||
// Transverter (XVTR) entry — один трансвертер.
|
||||
// Видимая частота VFO в [FreqBegin..FreqEnd] транслируется в IF-частоту,
|
||||
// которую слышит трансивер: f_IF = f_visible - LOOffset + LOError.
|
||||
// Примеры: 2m XVTR с LO=116 MHz → visible 144.200 ⇒ IF 28.200 MHz.
|
||||
TXvtrEntry = record
|
||||
Enabled: Boolean;
|
||||
ButtonText: string[15]; // подпись на band-кнопке (например, '2m', '70cm')
|
||||
FreqBegin: Double; // Hz
|
||||
FreqEnd: Double; // Hz
|
||||
LOOffset: Double; // Hz, отнимается от видимой частоты для получения IF
|
||||
LOError: Double; // Hz, корректировка LO (положительная — IF растёт)
|
||||
RXGain: Double; // dB, компенсация усиления RX тракта
|
||||
RXOnly: Boolean; // True — запрещает TX на этом трансверторе
|
||||
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
|
||||
XVTRRF: Boolean; // True — трансвертер использует основной HF RF путь
|
||||
// (а не отдельный XVTR DDC IN на Alex)
|
||||
DisablePA: Boolean; // True — отключить внутренний PA при TX (XVTR имеет свой)
|
||||
RXAntenna: Byte; // 0=по-умолчанию(основной band), 1=ANT1, 2=ANT2, 3=ANT3
|
||||
LastFreq: Double; // Hz — последняя tuned visible частота (BandStack)
|
||||
end;
|
||||
|
||||
TXvtrSettings = record
|
||||
Entries: array[0..CFG_XVTR_COUNT-1] of TXvtrEntry;
|
||||
// Use XVTR TUN power — кнопка TUN использует XVTR-specific power.
|
||||
UseXVTRTunePower: Boolean;
|
||||
end;
|
||||
|
||||
// Alex (Antenna + Filter board) settings per-device.
|
||||
// Хранятся в JSON-секции "alex" под MAC.
|
||||
// Безопасные дефолты: ANT1 для всех диапазонов, вторичный вход выключен.
|
||||
@@ -133,6 +161,7 @@ type
|
||||
WfAGCEnabled: Boolean;
|
||||
WfNFEnabled: Boolean;
|
||||
LastBand: Integer;
|
||||
LastXvtr: Integer; // -1 = HF, 0..CFG_XVTR_COUNT-1 = активный трансвертер при выходе
|
||||
SampleRate: Integer; // глобальный — один для всех диапазонов
|
||||
WindowLeft: Integer;
|
||||
WindowTop: Integer;
|
||||
@@ -231,6 +260,10 @@ type
|
||||
class procedure DefaultAlex(out A: TAlexSettings);
|
||||
function LoadAlex(const MAC: array of Byte; out A: TAlexSettings): Boolean;
|
||||
procedure SaveAlex(const MAC: array of Byte; const A: TAlexSettings);
|
||||
// XVTR (Transverter) settings per-device (JSON-секция "xvtr" под MAC).
|
||||
class procedure DefaultXvtr(out X: TXvtrSettings);
|
||||
function LoadXvtr(const MAC: array of Byte; out X: TXvtrSettings): Boolean;
|
||||
procedure SaveXvtr(const MAC: array of Byte; const X: TXvtrSettings);
|
||||
// Тема — глобальная настройка, не привязана к устройству
|
||||
procedure SaveTheme(ALightTheme: Boolean);
|
||||
function LoadTheme: Boolean;
|
||||
@@ -285,6 +318,7 @@ begin
|
||||
G.Volume := 70; G.DriveLevel := 50; G.ActiveVfo := 0;
|
||||
G.NRMode := 0; G.NBMode := 0; G.SNBEnabled := False; G.ANFEnabled := False;
|
||||
G.AGCSlope := 0; G.AGCHangThreshold := 100; G.LastBand := 5;
|
||||
G.LastXvtr := -1;
|
||||
G.WfAGCEnabled := True;
|
||||
G.WfNFEnabled := False;
|
||||
G.SampleRate := 192000;
|
||||
@@ -470,6 +504,7 @@ begin
|
||||
G.WfAGCEnabled := JB(GObj,'wf_agc_enabled',True);
|
||||
G.WfNFEnabled := JB(GObj,'wf_nf_enabled',False);
|
||||
G.LastBand := JI(GObj,'last_band',5);
|
||||
G.LastXvtr := JI(GObj,'last_xvtr',-1);
|
||||
G.SampleRate := JI(GObj,'sample_rate',192000);
|
||||
// Display settings
|
||||
G.FFTSize := JI(GObj,'fft_size',131072);
|
||||
@@ -574,6 +609,7 @@ begin
|
||||
JW(O,'wf_agc_enabled',G.WfAGCEnabled);
|
||||
JW(O,'wf_nf_enabled',G.WfNFEnabled);
|
||||
JW(O,'last_band',G.LastBand);
|
||||
JW(O,'last_xvtr',G.LastXvtr);
|
||||
JW(O,'sample_rate',G.SampleRate);
|
||||
// Display settings
|
||||
JW(O,'fft_size',G.FFTSize);
|
||||
@@ -994,6 +1030,113 @@ begin
|
||||
JW(O, 'enable_xvtr_hf', A.EnableXvtrHf);
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// XVTR settings (per-device, JSON-секция "xvtr" под MAC)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class procedure TSettingsManager.DefaultXvtr(out X: TXvtrSettings);
|
||||
var i: Integer;
|
||||
begin
|
||||
FillChar(X, SizeOf(X), 0);
|
||||
X.UseXVTRTunePower := False;
|
||||
// Все слоты по умолчанию disabled. Первые два предзаполнены типичными
|
||||
// значениями (2m с LO=116MHz, 70cm с LO=404MHz) — чтобы пользователь
|
||||
// мог быстро включить.
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
X.Entries[i].Enabled := False;
|
||||
X.Entries[i].ButtonText := '';
|
||||
X.Entries[i].FreqBegin := 0.0;
|
||||
X.Entries[i].FreqEnd := 0.0;
|
||||
X.Entries[i].LOOffset := 0.0;
|
||||
X.Entries[i].LOError := 0.0;
|
||||
X.Entries[i].RXGain := 0.0;
|
||||
X.Entries[i].RXOnly := False;
|
||||
X.Entries[i].TXPower := 100;
|
||||
X.Entries[i].XVTRRF := False;
|
||||
X.Entries[i].DisablePA := False;
|
||||
X.Entries[i].RXAntenna := 0;
|
||||
X.Entries[i].LastFreq := 0.0;
|
||||
end;
|
||||
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
|
||||
X.Entries[0].ButtonText := '2m';
|
||||
X.Entries[0].FreqBegin := 144000000;
|
||||
X.Entries[0].FreqEnd := 148000000;
|
||||
X.Entries[0].LOOffset := 116000000;
|
||||
X.Entries[0].LastFreq := 144300000;
|
||||
X.Entries[1].ButtonText := '70cm';
|
||||
X.Entries[1].FreqBegin := 430000000;
|
||||
X.Entries[1].FreqEnd := 440000000;
|
||||
X.Entries[1].LOOffset := 404000000;
|
||||
X.Entries[1].LastFreq := 432200000;
|
||||
end;
|
||||
|
||||
function TSettingsManager.LoadXvtr(const MAC: array of Byte;
|
||||
out X: TXvtrSettings): Boolean;
|
||||
var
|
||||
MacStr: string;
|
||||
DevObj, Root, EObj: TJSONObject;
|
||||
i: Integer;
|
||||
begin
|
||||
DefaultXvtr(X);
|
||||
MacStr := MacToStr(MAC);
|
||||
Result := FRoot.Find(MacStr) <> nil;
|
||||
if not Result then Exit;
|
||||
DevObj := GetDevObj(MacStr);
|
||||
if DevObj.Find('xvtr') = nil then Exit;
|
||||
Root := EnsureObj(DevObj, 'xvtr');
|
||||
X.UseXVTRTunePower := JB(Root, 'use_tun_power', False);
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
if Root.Find('e' + IntToStr(i)) = nil then Continue;
|
||||
EObj := EnsureObj(Root, 'e' + IntToStr(i));
|
||||
X.Entries[i].Enabled := JB(EObj, 'en', X.Entries[i].Enabled);
|
||||
X.Entries[i].ButtonText := JS(EObj, 'name', X.Entries[i].ButtonText);
|
||||
X.Entries[i].FreqBegin := JD(EObj, 'fbeg', X.Entries[i].FreqBegin);
|
||||
X.Entries[i].FreqEnd := JD(EObj, 'fend', X.Entries[i].FreqEnd);
|
||||
X.Entries[i].LOOffset := JD(EObj, 'lo_off', X.Entries[i].LOOffset);
|
||||
X.Entries[i].LOError := JD(EObj, 'lo_err', X.Entries[i].LOError);
|
||||
X.Entries[i].RXGain := JD(EObj, 'rx_gain', X.Entries[i].RXGain);
|
||||
X.Entries[i].RXOnly := JB(EObj, 'rx_only', X.Entries[i].RXOnly);
|
||||
X.Entries[i].TXPower := EnsureRange(JI(EObj, 'tx_pwr', X.Entries[i].TXPower), 0, 100);
|
||||
X.Entries[i].XVTRRF := JB(EObj, 'xvtr_rf', X.Entries[i].XVTRRF);
|
||||
X.Entries[i].DisablePA := JB(EObj, 'dis_pa', X.Entries[i].DisablePA);
|
||||
X.Entries[i].RXAntenna := EnsureRange(JI(EObj, 'rx_ant', X.Entries[i].RXAntenna), 0, 3);
|
||||
X.Entries[i].LastFreq := JD(EObj, 'last_freq', X.Entries[i].LastFreq);
|
||||
// Валидация: LastFreq должен быть в [FreqBegin..FreqEnd]
|
||||
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
|
||||
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
|
||||
X.Entries[i].LastFreq := (X.Entries[i].FreqBegin + X.Entries[i].FreqEnd) / 2.0;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveXvtr(const MAC: array of Byte;
|
||||
const X: TXvtrSettings);
|
||||
var
|
||||
Root, EObj: TJSONObject;
|
||||
i: Integer;
|
||||
begin
|
||||
Root := EnsureObj(GetDevObj(MacToStr(MAC)), 'xvtr');
|
||||
JW(Root, 'use_tun_power', X.UseXVTRTunePower);
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
EObj := EnsureObj(Root, 'e' + IntToStr(i));
|
||||
JW(EObj, 'en', X.Entries[i].Enabled);
|
||||
JWS(EObj, 'name', X.Entries[i].ButtonText);
|
||||
JW(EObj, 'fbeg', X.Entries[i].FreqBegin);
|
||||
JW(EObj, 'fend', X.Entries[i].FreqEnd);
|
||||
JW(EObj, 'lo_off', X.Entries[i].LOOffset);
|
||||
JW(EObj, 'lo_err', X.Entries[i].LOError);
|
||||
JW(EObj, 'rx_gain', X.Entries[i].RXGain);
|
||||
JW(EObj, 'rx_only', X.Entries[i].RXOnly);
|
||||
JW(EObj, 'tx_pwr', X.Entries[i].TXPower);
|
||||
JW(EObj, 'xvtr_rf', X.Entries[i].XVTRRF);
|
||||
JW(EObj, 'dis_pa', X.Entries[i].DisablePA);
|
||||
JW(EObj, 'rx_ant', Integer(X.Entries[i].RXAntenna));
|
||||
JW(EObj, 'last_freq', X.Entries[i].LastFreq);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveTheme(ALightTheme: Boolean);
|
||||
var O: TJSONObject;
|
||||
begin
|
||||
|
||||
Reference in New Issue
Block a user