mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat: unify sample-rate presets across desktop + web UIs
Sample-rate (span) presets were duplicated in three places and none was authoritative: HPSDR set hardcoded in the desktop overlay (SPAN_RATES), Pluto set in backend caps, and a separate hardcoded list in the web JS. The web showed HPSDR rates even on Pluto, and the web adapter silently dropped any rate outside a hardcoded HPSDR whitelist (so Pluto-only rates like 960k/2304k never switched). Single source of truth = backend caps -> controller: - HPSDRNetwork.Caps now fills RatePresets [48k..1536k] (was nil). - RadioBackend: named type TBackendRateArray. - TRadioController.SampleRatePresets returns BackendCaps.RatePresets. Both UIs derive from it: - Desktop: ApplyBackendCapsToUI always feeds the overlay from SampleRatePresets (overlay no longer owns the HPSDR list). - Web: WebServer.SetRatePresets + rate_presets in state JSON; hosts (daemon OnState/startup, GUI ApplyBackendCapsToUI/startup) push SampleRatePresets; JS builds span buttons dynamically from it. - WebAdapter.SyncSpan validates against SampleRatePresets instead of a hardcoded whitelist, so any backend rate is accepted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+8
-1
@@ -633,7 +633,14 @@ begin
|
|||||||
Result.MinSampleRate := 48000;
|
Result.MinSampleRate := 48000;
|
||||||
Result.MaxSampleRate := 1536000;
|
Result.MaxSampleRate := 1536000;
|
||||||
Result.SampleRateMode := srmDiscrete;
|
Result.SampleRateMode := srmDiscrete;
|
||||||
Result.RatePresets := nil; // контроллер использует свой HPSDR-набор
|
// Бэкенд — единственный источник своих рейтов (общий для desktop/web UI).
|
||||||
|
SetLength(Result.RatePresets, 6);
|
||||||
|
Result.RatePresets[0] := 48000;
|
||||||
|
Result.RatePresets[1] := 96000;
|
||||||
|
Result.RatePresets[2] := 192000;
|
||||||
|
Result.RatePresets[3] := 384000;
|
||||||
|
Result.RatePresets[4] := 768000;
|
||||||
|
Result.RatePresets[5] := 1536000;
|
||||||
Result.MinFreqHz := 0;
|
Result.MinFreqHz := 0;
|
||||||
Result.MaxFreqHz := 61440000;
|
Result.MaxFreqHz := 61440000;
|
||||||
end;
|
end;
|
||||||
|
|||||||
+13
-4
@@ -478,6 +478,7 @@ type
|
|||||||
procedure OnAlexSettingsChange(const A: TAlexSettings);
|
procedure OnAlexSettingsChange(const A: TAlexSettings);
|
||||||
procedure OnXvtrSettingsChange(const X: TXvtrSettings);
|
procedure OnXvtrSettingsChange(const X: TXvtrSettings);
|
||||||
procedure PushXvtrToWeb;
|
procedure PushXvtrToWeb;
|
||||||
|
procedure PushRatePresetsToWeb; // backend sample-rate пресеты → web
|
||||||
// XVTR helpers
|
// XVTR helpers
|
||||||
function FindXvtrIdxForFreq(FreqHz: Double): Integer;
|
function FindXvtrIdxForFreq(FreqHz: Double): Integer;
|
||||||
function XvtrTranslate(VisibleHz: Double): Double;
|
function XvtrTranslate(VisibleHz: Double): Double;
|
||||||
@@ -882,6 +883,7 @@ begin
|
|||||||
if FWebEnabled then FWebServer.Start;
|
if FWebEnabled then FWebServer.Start;
|
||||||
PushXvtrToWeb;
|
PushXvtrToWeb;
|
||||||
PushDeviceListToWeb; // saved-устройства сразу видны web-overlay
|
PushDeviceListToWeb; // saved-устройства сразу видны web-overlay
|
||||||
|
PushRatePresetsToWeb; // дефолтный бэкенд до connect; перезапишется на смене
|
||||||
FillChar(FCATLastGlobal, SizeOf(FCATLastGlobal), 0);
|
FillChar(FCATLastGlobal, SizeOf(FCATLastGlobal), 0);
|
||||||
FCATLastGlobal.CATTcpPort := 19090;
|
FCATLastGlobal.CATTcpPort := 19090;
|
||||||
for i := 0 to 3 do
|
for i := 0 to 3 do
|
||||||
@@ -3678,12 +3680,11 @@ begin
|
|||||||
Caps := FController.BackendCaps;
|
Caps := FController.BackendCaps;
|
||||||
if Assigned(FSampleRateOverlay) then
|
if Assigned(FSampleRateOverlay) then
|
||||||
begin
|
begin
|
||||||
if (Caps.SampleRateMode = srmContinuous) and (Length(Caps.RatePresets) > 0) then
|
// Единый источник — контроллер (caps бэкенда). UI больше не владеет списком.
|
||||||
FSampleRateOverlay.SetRatePresets(Caps.RatePresets)
|
FSampleRateOverlay.SetRatePresets(FController.SampleRatePresets);
|
||||||
else
|
|
||||||
FSampleRateOverlay.SetRatePresetsDefault; // HPSDR 48k..1536k
|
|
||||||
FSampleRateOverlay.SetCurrentRate(FController.FSampleRate);
|
FSampleRateOverlay.SetCurrentRate(FController.FSampleRate);
|
||||||
end;
|
end;
|
||||||
|
PushRatePresetsToWeb; // тот же источник зеркалим в web-морду
|
||||||
|
|
||||||
// Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже.
|
// Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже.
|
||||||
LayoutLeftPanel(Caps.HasHWGain);
|
LayoutLeftPanel(Caps.HasHWGain);
|
||||||
@@ -5366,6 +5367,14 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.PushRatePresetsToWeb;
|
||||||
|
// Пресеты sample-rate бэкенда → web (HPSDR/Pluto разные). Единый источник —
|
||||||
|
// FController.SampleRatePresets, как и для desktop-оверлея.
|
||||||
|
begin
|
||||||
|
if FWebServer = nil then Exit;
|
||||||
|
FWebServer.SetRatePresets(FController.SampleRatePresets);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.PushXvtrToWeb;
|
procedure TMainForm.PushXvtrToWeb;
|
||||||
var
|
var
|
||||||
Arr: TWebXvtrArray;
|
Arr: TWebXvtrArray;
|
||||||
|
|||||||
+2
-1
@@ -29,6 +29,7 @@ uses
|
|||||||
type
|
type
|
||||||
TBackendKind = (bkHPSDR, bkPluto);
|
TBackendKind = (bkHPSDR, bkPluto);
|
||||||
TSampleRateMode = (srmDiscrete, srmContinuous);
|
TSampleRateMode = (srmDiscrete, srmContinuous);
|
||||||
|
TBackendRateArray = array of Integer; // пресеты sample-rate (общий тип UI)
|
||||||
|
|
||||||
// Описание возможностей бэкенда. Контроллер гейтит UI/поведение по этим полям.
|
// Описание возможностей бэкенда. Контроллер гейтит UI/поведение по этим полям.
|
||||||
TBackendCaps = record
|
TBackendCaps = record
|
||||||
@@ -46,7 +47,7 @@ type
|
|||||||
MinSampleRate: Integer;
|
MinSampleRate: Integer;
|
||||||
MaxSampleRate: Integer;
|
MaxSampleRate: Integer;
|
||||||
SampleRateMode: TSampleRateMode;
|
SampleRateMode: TSampleRateMode;
|
||||||
RatePresets: array of Integer; // пресеты для SampleRateOverlay
|
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
|
||||||
MinFreqHz: Double;
|
MinFreqHz: Double;
|
||||||
MaxFreqHz: Double;
|
MaxFreqHz: Double;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -544,6 +544,9 @@ type
|
|||||||
procedure EnsureBackend(Kind: TBackendKind);
|
procedure EnsureBackend(Kind: TBackendKind);
|
||||||
// Возможности текущего бэкенда (для гейтинга UI / пресетов sample rate).
|
// Возможности текущего бэкенда (для гейтинга UI / пресетов sample rate).
|
||||||
function BackendCaps: TBackendCaps;
|
function BackendCaps: TBackendCaps;
|
||||||
|
// Единый источник пресетов sample-rate для UI (desktop overlay + web).
|
||||||
|
// Берётся из caps текущего бэкенда (HPSDR 48k..1536k / Pluto 576k..5760k).
|
||||||
|
function SampleRatePresets: TBackendRateArray;
|
||||||
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
||||||
function IsPluto: Boolean;
|
function IsPluto: Boolean;
|
||||||
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
|
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
|
||||||
@@ -3154,6 +3157,11 @@ begin
|
|||||||
else FillChar(Result, SizeOf(Result), 0);
|
else FillChar(Result, SizeOf(Result), 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TRadioController.SampleRatePresets: TBackendRateArray;
|
||||||
|
begin
|
||||||
|
Result := BackendCaps.RatePresets;
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.IsPluto: Boolean;
|
function TRadioController.IsPluto: Boolean;
|
||||||
begin
|
begin
|
||||||
Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto);
|
Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto);
|
||||||
|
|||||||
+8
-6
@@ -199,14 +199,16 @@ procedure TWebAdapter.OnSpan(Hz: Integer);
|
|||||||
begin FSyncInt := Hz; FController.Invoke(@SyncSpan); end;
|
begin FSyncInt := Hz; FController.Invoke(@SyncSpan); end;
|
||||||
|
|
||||||
procedure TWebAdapter.SyncSpan;
|
procedure TWebAdapter.SyncSpan;
|
||||||
// span ≡ sample rate. Принимаем только валидные значения (как десктоп-оверлей);
|
// span ≡ sample rate. Валидируем по пресетам текущего бэкенда (единый источник,
|
||||||
|
// как десктоп-оверлей) — иначе Pluto-рейты (960k/2304k/…) отбрасывались бы.
|
||||||
// SetSampleRate сам пересоздаёт WDSP-канал + персистит (idempotent при том же rate).
|
// SetSampleRate сам пересоздаёт WDSP-канал + персистит (idempotent при том же rate).
|
||||||
const
|
var
|
||||||
ValidSpans: array[0..5] of Integer = (48000, 96000, 192000, 384000, 768000, 1536000);
|
Presets: TBackendRateArray;
|
||||||
var i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
for i := 0 to High(ValidSpans) do
|
Presets := FController.SampleRatePresets;
|
||||||
if ValidSpans[i] = FSyncInt then
|
for i := 0 to High(Presets) do
|
||||||
|
if Presets[i] = FSyncInt then
|
||||||
begin
|
begin
|
||||||
FController.SetSampleRate(FSyncInt);
|
FController.SetSampleRate(FSyncInt);
|
||||||
Exit;
|
Exit;
|
||||||
|
|||||||
+10
-5
@@ -16,7 +16,7 @@ unit WebPageHtml;
|
|||||||
- Opus: decodeFrame() правильный метод, async/await
|
- Opus: decodeFrame() правильный метод, async/await
|
||||||
- Opus: opQueue буферизует пакеты пока WASM не готов
|
- Opus: opQueue буферизует пакеты пока WASM не готов
|
||||||
- Opus: AudioContext создаётся только по user gesture (оверлей)
|
- Opus: AudioContext создаётся только по user gesture (оверлей)
|
||||||
- SPANS: только 48K..1536K
|
- SPANS: строятся из state.rate_presets (HPSDR/Pluto разные наборы)
|
||||||
- (Mode fix) modTglBtn теперь отображает ТЕКУЩИЙ активный режим
|
- (Mode fix) modTglBtn теперь отображает ТЕКУЩИЙ активный режим
|
||||||
(LSB/USB, CWL/CWU, AM/SAM, FM), а не следующий. При клике
|
(LSB/USB, CWL/CWU, AM/SAM, FM), а не следующий. При клике
|
||||||
переключает на парный режим внутри группы.
|
переключает на парный режим внутри группы.
|
||||||
@@ -516,11 +516,13 @@ begin
|
|||||||
'q("btnSwap").onclick=()=>{const t=vfoA;vfoA=vfoB;vfoB=t;renderVfos();pend("freq");' +
|
'q("btnSwap").onclick=()=>{const t=vfoA;vfoA=vfoB;vfoB=t;renderVfos();pend("freq");' +
|
||||||
'ws2({cmd:"freq_a",hz:Math.round(vfoA)});ws2({cmd:"freq_b",hz:Math.round(vfoB)});};' +
|
'ws2({cmd:"freq_a",hz:Math.round(vfoA)});ws2({cmd:"freq_b",hz:Math.round(vfoB)});};' +
|
||||||
|
|
||||||
// SPAN кнопки: только 48K..1536K
|
// SPAN/sample-rate кнопки: строятся из state.rate_presets (HPSDR и Pluto
|
||||||
'const SPANS=[48000,96000,192000,384000,768000,1536000];' +
|
// имеют разные наборы; единый источник — backend caps в контроллере).
|
||||||
'const SPAN_L=["48K","96K","192K","384K","768K","1536K"];' +
|
'let SPANS=[48000,96000,192000,384000,768000,1536000];' +
|
||||||
'(function(){const c=q("spanOv");SPANS.forEach((hz,i)=>{const b=document.createElement("button");b.className="sp-btn";b.id="sb"+i;b.textContent=SPAN_L[i];b.onclick=()=>ws2({cmd:"span",hz});c.appendChild(b);});})();' +
|
'function fmtRate(hz){return Math.round(hz/1000)+"K";}' +
|
||||||
|
'function buildSpanButtons(){const c=q("spanOv");if(!c)return;c.innerHTML="";SPANS.forEach((hz,i)=>{const b=document.createElement("button");b.className="sp-btn";b.id="sb"+i;b.textContent=fmtRate(hz);b.onclick=()=>ws2({cmd:"span",hz});c.appendChild(b);});updSpan(localSpan);}' +
|
||||||
'function updSpan(s){SPANS.forEach((hz,i)=>{const b=q("sb"+i);if(b)b.classList.toggle("on",hz===s);});}' +
|
'function updSpan(s){SPANS.forEach((hz,i)=>{const b=q("sb"+i);if(b)b.classList.toggle("on",hz===s);});}' +
|
||||||
|
'buildSpanButtons();' +
|
||||||
|
|
||||||
'const BAND_N=["160m","80m","60m","40m","30m","20m","17m","15m","12m","10m","6m"];' +
|
'const BAND_N=["160m","80m","60m","40m","30m","20m","17m","15m","12m","10m","6m"];' +
|
||||||
'const BAND_F=[1900000,3750000,5357000,7100000,10125000,14200000,18120000,21200000,24940000,28500000,50150000];' +
|
'const BAND_F=[1900000,3750000,5357000,7100000,10125000,14200000,18120000,21200000,24940000,28500000,50150000];' +
|
||||||
@@ -684,6 +686,9 @@ begin
|
|||||||
'if(bandsChanged)rebuildBandSel();' +
|
'if(bandsChanged)rebuildBandSel();' +
|
||||||
'else if(curChanged){const s=q("bandSel");if(s&&XVTR_CUR>=0)s.value="x"+XVTR_CUR;}' +
|
'else if(curChanged){const s=q("bandSel");if(s&&XVTR_CUR>=0)s.value="x"+XVTR_CUR;}' +
|
||||||
'}' +
|
'}' +
|
||||||
|
// Пресеты sample-rate сменились (другой бэкенд) — перестраиваем span-кнопки.
|
||||||
|
'if(msg.rate_presets!=null&&Array.isArray(msg.rate_presets)&&msg.rate_presets.length){' +
|
||||||
|
'if(JSON.stringify(msg.rate_presets)!==JSON.stringify(SPANS)){SPANS=msg.rate_presets;buildSpanButtons();}}' +
|
||||||
// Обновляем localCenter/localSpan из сервера только когда пользователь не тянет
|
// Обновляем localCenter/localSpan из сервера только когда пользователь не тянет
|
||||||
'if(!dragging&&Date.now()-wheelActiveTs>300){' +
|
'if(!dragging&&Date.now()-wheelActiveTs>300){' +
|
||||||
// В CTUN без DUP: TX-спектр WDSP центрирован по vfo_a_hz, а не по DDC LO.
|
// В CTUN без DUP: TX-спектр WDSP центрирован по vfo_a_hz, а не по DDC LO.
|
||||||
|
|||||||
+30
-3
@@ -297,6 +297,7 @@ type
|
|||||||
FFreqMhzDigits: Integer; // 3=999MHz, 4=9.999GHz, 5=99.999GHz
|
FFreqMhzDigits: Integer; // 3=999MHz, 4=9.999GHz, 5=99.999GHz
|
||||||
FXvtrBands: TWebXvtrArray; // список enabled XVTR (для web-UI)
|
FXvtrBands: TWebXvtrArray; // список enabled XVTR (для web-UI)
|
||||||
FDevices: TWebDeviceArray; // saved+discovered устройства (для web-overlay)
|
FDevices: TWebDeviceArray; // saved+discovered устройства (для web-overlay)
|
||||||
|
FRatePresets: array of Integer; // sample-rate пресеты текущего бэкенда (для span-кнопок)
|
||||||
FCurrentXvtr: Integer; // -1 = HF, иначе индекс активного XVTR
|
FCurrentXvtr: Integer; // -1 = HF, иначе индекс активного XVTR
|
||||||
FOnXvtrBand: TWebCmdXvtrBand;
|
FOnXvtrBand: TWebCmdXvtrBand;
|
||||||
|
|
||||||
@@ -392,6 +393,7 @@ type
|
|||||||
property OnXvtrBand: TWebCmdXvtrBand read FOnXvtrBand write FOnXvtrBand;
|
property OnXvtrBand: TWebCmdXvtrBand read FOnXvtrBand write FOnXvtrBand;
|
||||||
procedure SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
|
procedure SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
|
||||||
procedure SetDeviceList(const ADevices: TWebDeviceArray);
|
procedure SetDeviceList(const ADevices: TWebDeviceArray);
|
||||||
|
procedure SetRatePresets(const ARates: array of Integer);
|
||||||
property OnSpan: TWebCmdSpan read FOnSpan write FOnSpan;
|
property OnSpan: TWebCmdSpan read FOnSpan write FOnSpan;
|
||||||
property OnVolume: TWebCmdVolume read FOnVolume write FOnVolume;
|
property OnVolume: TWebCmdVolume read FOnVolume write FOnVolume;
|
||||||
property OnWfAGC: TWebCmdWfAGC read FOnWfAGC write FOnWfAGC;
|
property OnWfAGC: TWebCmdWfAGC read FOnWfAGC write FOnWfAGC;
|
||||||
@@ -535,6 +537,8 @@ begin
|
|||||||
FCurrentXvtr := -1;
|
FCurrentXvtr := -1;
|
||||||
FFreqMhzDigits := 3;
|
FFreqMhzDigits := 3;
|
||||||
SetLength(FXvtrBands, 0);
|
SetLength(FXvtrBands, 0);
|
||||||
|
// Bootstrap-набор рейтов (до connect): хост перезапишет под бэкенд (HPSDR/Pluto).
|
||||||
|
SetRatePresets([48000, 96000, 192000, 384000, 768000, 1536000]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWebServer.SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
|
procedure TWebServer.SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
|
||||||
@@ -564,6 +568,21 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TWebServer.SetRatePresets(const ARates: array of Integer);
|
||||||
|
// Список пресетов sample-rate текущего бэкенда (из TRadioController). Web строит
|
||||||
|
// span-кнопки из него — HPSDR и Pluto имеют разные наборы.
|
||||||
|
var i: Integer;
|
||||||
|
begin
|
||||||
|
FStateLock.Enter;
|
||||||
|
try
|
||||||
|
SetLength(FRatePresets, Length(ARates));
|
||||||
|
for i := 0 to High(ARates) do
|
||||||
|
FRatePresets[i] := ARates[i];
|
||||||
|
finally
|
||||||
|
FStateLock.Leave;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
destructor TWebServer.Destroy;
|
destructor TWebServer.Destroy;
|
||||||
begin
|
begin
|
||||||
Stop;
|
Stop;
|
||||||
@@ -1542,13 +1561,21 @@ const
|
|||||||
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
|
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
|
||||||
var
|
var
|
||||||
FS: TFormatSettings;
|
FS: TFormatSettings;
|
||||||
XvtrJson, DevJson: string;
|
XvtrJson, DevJson, RateJson: string;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
FS := DefaultFormatSettings;
|
FS := DefaultFormatSettings;
|
||||||
FS.DecimalSeparator := '.';
|
FS.DecimalSeparator := '.';
|
||||||
FStateLock.Enter;
|
FStateLock.Enter;
|
||||||
try
|
try
|
||||||
|
// Сборка массива rate_presets: [48000,96000,...] (span-кнопки web).
|
||||||
|
RateJson := '[';
|
||||||
|
for i := 0 to High(FRatePresets) do
|
||||||
|
begin
|
||||||
|
if i > 0 then RateJson := RateJson + ',';
|
||||||
|
RateJson := RateJson + IntToStr(FRatePresets[i]);
|
||||||
|
end;
|
||||||
|
RateJson := RateJson + ']';
|
||||||
// Сборка массива xvtr_bands: [{"idx":0,"name":"2m"},...]
|
// Сборка массива xvtr_bands: [{"idx":0,"name":"2m"},...]
|
||||||
XvtrJson := '[';
|
XvtrJson := '[';
|
||||||
for i := 0 to High(FXvtrBands) do
|
for i := 0 to High(FXvtrBands) do
|
||||||
@@ -1586,7 +1613,7 @@ begin
|
|||||||
'"status_text":"%s","board_text":"%s","ip_text":"%s","supply_text":"%s",' +
|
'"status_text":"%s","board_text":"%s","ip_text":"%s","supply_text":"%s",' +
|
||||||
'"pll_text":"%s","rx_text":"%s","tx_text":"%s","seq_text":"%s",' +
|
'"pll_text":"%s","rx_text":"%s","tx_text":"%s","seq_text":"%s",' +
|
||||||
'"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d,' +
|
'"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d,' +
|
||||||
'"fmstep_idx":%d,"devices":%s}',
|
'"fmstep_idx":%d,"devices":%s,"rate_presets":%s}',
|
||||||
[FFreq, FVfoB, FActiveVfo,
|
[FFreq, FVfoB, FActiveVfo,
|
||||||
FMode, MODE_N[FMode mod 8],
|
FMode, MODE_N[FMode mod 8],
|
||||||
FFilterIdx, FFilterBW,
|
FFilterIdx, FFilterBW,
|
||||||
@@ -1615,7 +1642,7 @@ begin
|
|||||||
JsonEscape(FSupplyText), JsonEscape(FPLLText), JsonEscape(FRXText),
|
JsonEscape(FSupplyText), JsonEscape(FPLLText), JsonEscape(FRXText),
|
||||||
JsonEscape(FTXText), JsonEscape(FSeqText),
|
JsonEscape(FTXText), JsonEscape(FSeqText),
|
||||||
FCurrentXvtr, XvtrJson, FFreqMhzDigits,
|
FCurrentXvtr, XvtrJson, FFreqMhzDigits,
|
||||||
FFMStepIdx, DevJson
|
FFMStepIdx, DevJson, RateJson
|
||||||
], FS);
|
], FS);
|
||||||
finally
|
finally
|
||||||
FStateLock.Leave;
|
FStateLock.Leave;
|
||||||
|
|||||||
+8
-1
@@ -55,6 +55,7 @@ type
|
|||||||
procedure ConnectTo(const Dev: THPSDRDevice);
|
procedure ConnectTo(const Dev: THPSDRDevice);
|
||||||
procedure PushDeviceList;
|
procedure PushDeviceList;
|
||||||
procedure PushXvtr;
|
procedure PushXvtr;
|
||||||
|
procedure PushRatePresets; // sample-rate пресеты бэкенда → web
|
||||||
// Входящие web-команды (из WS-потока): запоминаем параметр и маршалим в Sync*.
|
// Входящие web-команды (из WS-потока): запоминаем параметр и маршалим в Sync*.
|
||||||
procedure WebRun(On_: Boolean);
|
procedure WebRun(On_: Boolean);
|
||||||
procedure WebConnect(const IP: string);
|
procedure WebConnect(const IP: string);
|
||||||
@@ -196,7 +197,7 @@ begin
|
|||||||
// выходные web-списки, которых нет в периодическом PushState.
|
// выходные web-списки, которых нет в периодическом PushState.
|
||||||
case Field of
|
case Field of
|
||||||
rfDeviceList: PushDeviceList; // discovered обновился
|
rfDeviceList: PushDeviceList; // discovered обновился
|
||||||
rfXvtr, rfDevice: begin PushXvtr; PushDeviceList; end; // connect / xvtr-смена
|
rfXvtr, rfDevice: begin PushXvtr; PushDeviceList; PushRatePresets; end; // connect / xvtr / backend
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -291,6 +292,11 @@ begin
|
|||||||
FWeb.SetXvtrBands(Arr, FCtrl.FCurrentXvtr);
|
FWeb.SetXvtrBands(Arr, FCtrl.FCurrentXvtr);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure THeadlessHost.PushRatePresets;
|
||||||
|
begin
|
||||||
|
FWeb.SetRatePresets(FCtrl.SampleRatePresets);
|
||||||
|
end;
|
||||||
|
|
||||||
// --- Входящие web-команды (WS-поток) → маршалинг в главный поток ---
|
// --- Входящие web-команды (WS-поток) → маршалинг в главный поток ---
|
||||||
|
|
||||||
procedure THeadlessHost.WebRun(On_: Boolean);
|
procedure THeadlessHost.WebRun(On_: Boolean);
|
||||||
@@ -382,6 +388,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
Log('Web control on http://' + WebCfg.BindAddr + ':' + IntToStr(WebCfg.Port));
|
Log('Web control on http://' + WebCfg.BindAddr + ':' + IntToStr(WebCfg.Port));
|
||||||
PushDeviceList;
|
PushDeviceList;
|
||||||
|
PushRatePresets; // дефолтный бэкенд (HPSDR) до connect; перезапишется на смене
|
||||||
|
|
||||||
// FRunning ставим ДО autostart: первый WDSP-open может занять время, и если за
|
// FRunning ставим ДО autostart: первый WDSP-open может занять время, и если за
|
||||||
// него прилетит SIGINT (RequestStop→FRunning:=False), цикл сразу завершится, а
|
// него прилетит SIGINT (RequestStop→FRunning:=False), цикл сразу завершится, а
|
||||||
|
|||||||
Reference in New Issue
Block a user