From a59501f2c4d5aa116a26e36209b2feec58bffbc4 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Mon, 18 May 2026 22:27:14 +0300 Subject: [PATCH] Save/restore CTun, AGC, FM step per XVTR slot; add FM step to web - Add LastCTun, LastAGCMode, LastAGCTop to TXvtrEntry; SaveCurrentBand now captures all three in the XVTR branch; ActivateXvtrBand restores them on slot switch/startup (previously CTun was always reset to False and AGC was never saved/restored for XVTR) - WebServer: expose fmstep_idx in state JSON so web clients sync the FM step from desktop state - WebPageHtml: swap stepSel options to FM steps (6.25k/12.5k/20k/25k) when mode=FM, restore HF steps when leaving FM; syncs fmstep_idx from server on connect and mode change Co-Authored-By: Claude Sonnet 4.6 --- MainForm.pas | 28 ++++++++++++++++++++-------- Settings.pas | 12 ++++++++++++ WebPageHtml.pas | 13 ++++++++++++- WebServer.pas | 13 +++++++++---- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index 90e7814..496b224 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -1030,6 +1030,9 @@ begin FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSToneIdx := FFMCTCSSToneIdx; FXvtrSettings.Entries[FCurrentXvtr].LastFMStepOn := FFMStepOn; FXvtrSettings.Entries[FCurrentXvtr].LastFMStepIdx := FFMStepIdx; + FXvtrSettings.Entries[FCurrentXvtr].LastCTun := FCTun; + FXvtrSettings.Entries[FCurrentXvtr].LastAGCMode := FAGCMode; + FXvtrSettings.Entries[FCurrentXvtr].LastAGCTop := FAGCTop; end; FSettings.SaveXvtr(FDevMAC, FXvtrSettings); Exit; @@ -4801,6 +4804,7 @@ begin FBtnFMSteps[OldIdx].Active := False; if FBtnFMSteps[Idx] <> nil then FBtnFMSteps[Idx].Active := True; + if FWebServer <> nil then FWebServer.FMStepIdx := Idx; end; procedure TMainForm.BtnFMStepClick(Sender: TObject); @@ -6243,17 +6247,25 @@ begin FFMStepOn := E.LastFMStepOn; SetFMStep(E.LastFMStepIdx); if BtnFMStep <> nil then StyleButton(BtnFMStep, FFMStepOn); - // VFO на видимую частоту; FCenterFreq тоже visible (как и для HF — DDC переход - // в IF происходит только в момент UpdateState/SetRunAndFreq через XvtrTranslate). + // AGC + FAGCMode := E.LastAGCMode; + FAGCTop := E.LastAGCTop; + for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FAGCMode); + TrkAGC.Position := FAGCTop; + LblAGCTop.Caption := Format('%ddB', [FAGCTop]); + if FWDSPReady then + begin + FDSPEngine.SetAGCTop(FAGCTop); + FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); + end; + FSpecView.AGCTop := FAGCTop; + // CTUN + FCTun := E.LastCTun; + StyleButton(BtnCTun, FCTun); + // VFO на видимую частоту; FCenterFreq тоже visible FVfoA := Vis; FCenterFreq := Vis; FreqDispA.Frequency := Round(FVfoA); - // Сбросить CTUN — не имеет смысла в начальном состоянии XVTR-band - if FCTun then - begin - FCTun := False; - StyleButton(BtnCTun, False); - end; if FWDSPReady then FDSPEngine.SetShift(0.0); // Применяем драйв (если есть кастомный TX power для этого XVTR) // Сделано на стороне ActiveTXFreqHz / TX path — здесь только обновляем дисплей. diff --git a/Settings.pas b/Settings.pas index 55d1e74..261a026 100644 --- a/Settings.pas +++ b/Settings.pas @@ -144,6 +144,9 @@ type LastCTCSSToneIdx: Integer; LastFMStepOn: Boolean; LastFMStepIdx: Integer; + LastCTun: Boolean; + LastAGCMode: Integer; + LastAGCTop: Integer; end; TXvtrSettings = record @@ -1121,6 +1124,9 @@ begin X.Entries[i].LastCTCSSToneIdx := 0; X.Entries[i].LastFMStepOn := True; X.Entries[i].LastFMStepIdx := 3; // 25 kHz + X.Entries[i].LastCTun := False; + X.Entries[i].LastAGCMode := 1; + X.Entries[i].LastAGCTop := 90; end; // Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились) X.Entries[0].ButtonText := '2m'; @@ -1174,6 +1180,9 @@ begin X.Entries[i].LastCTCSSToneIdx := EnsureRange(JI(EObj, 'last_ctcss_idx', X.Entries[i].LastCTCSSToneIdx), 0, 37); X.Entries[i].LastFMStepOn := JB(EObj, 'last_fmstep_on', X.Entries[i].LastFMStepOn); X.Entries[i].LastFMStepIdx := EnsureRange(JI(EObj, 'last_fmstep_idx', X.Entries[i].LastFMStepIdx), 0, 3); + X.Entries[i].LastCTun := JB(EObj, 'last_ctun', X.Entries[i].LastCTun); + X.Entries[i].LastAGCMode := EnsureRange(JI(EObj, 'last_agc_mode', X.Entries[i].LastAGCMode), 0, 4); + X.Entries[i].LastAGCTop := EnsureRange(JI(EObj, 'last_agc_top', X.Entries[i].LastAGCTop), 0, 120); // Валидация: LastFreq должен быть в [FreqBegin..FreqEnd] if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or (X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then @@ -1212,6 +1221,9 @@ begin JW(EObj, 'last_ctcss_idx',X.Entries[i].LastCTCSSToneIdx); JW(EObj, 'last_fmstep_on', X.Entries[i].LastFMStepOn); JW(EObj, 'last_fmstep_idx',X.Entries[i].LastFMStepIdx); + JW(EObj, 'last_ctun', X.Entries[i].LastCTun); + JW(EObj, 'last_agc_mode', X.Entries[i].LastAGCMode); + JW(EObj, 'last_agc_top', X.Entries[i].LastAGCTop); end; end; diff --git a/WebPageHtml.pas b/WebPageHtml.pas index c0c83ee..98646df 100644 --- a/WebPageHtml.pas +++ b/WebPageHtml.pas @@ -216,6 +216,9 @@ begin 'const stEl=document.getElementById("stEl"),latEl=document.getElementById("latEl");' + 'let cur={},vfoA=14200000,vfoB=14200000,activeVfo="A";' + + 'const FM_STEP_HZ=[6250,12500,20000,25000];' + + 'const FM_STEP_LBL=["6.25k","12.5k","20k","25k"];' + + 'let fmStepIdx=3,lastHFStep=100,inFMStep=false;' + 'let localCenter=14200000,localSpan=192000;' + // локальные копии — обновляются мгновенно при действии пользователя 'let dragging=false,dragX0=0,dragCen0=0,dragVfo0=0,dragMs=0,wheelActiveTs=0;' + 'let hoverX=-1,wTarget="",markerOn=false,markerX=0;' + @@ -566,6 +569,7 @@ begin 'if(!isPending("freq")){vfoA=cur.vfo_a_hz||vfoA;}' + 'if(msg.vfo_b_hz!=null)vfoB=msg.vfo_b_hz;' + 'if(msg.freq_mhz_digits!=null){freqMhzDigits=Math.max(3,Math.min(5,msg.freq_mhz_digits|0));freqMaxHz=Math.pow(10,freqMhzDigits+6)-1;}' + + 'if(msg.fmstep_idx!=null){fmStepIdx=Math.max(0,Math.min(3,msg.fmstep_idx|0));if(inFMStep)syncStepSel();}' + 'if(msg.active_vfo!=null&&!isPending("freq")){' + 'const sv=msg.active_vfo===0?"A":"B";' + 'if(sv!==activeVfo){activeVfo=sv;' + @@ -583,7 +587,7 @@ begin 'if(!isPending("mox"))updMoxBtn(!!cur.transmitting);' + 'if(!isPending("tun"))updToneBtn(!!cur.tuning);' + 'if(!isPending("drive")){const ts=q("txSl");if(ts&&!ts.matches(":active")){ts.value=cur.drive||0;var tv=q("txVal");if(tv)tv.textContent=cur.drive||0;}}' + - 'if(!isPending("mode")){syncModGrp(cur.mode||0);}' + + 'if(!isPending("mode")){syncModGrp(cur.mode||0);syncStepSel();}' + 'if(!isPending("attn")){const at=q("attSel");if(at)at.value=cur.attn_idx||0;}' + 'updBand(activeVfo==="A"?(cur.vfo_a_hz||0):(vfoB||0));updSpan(cur.span_hz||192000);' + 'paintSM(Number(cur.smeter_dbm||-130));' + @@ -642,6 +646,13 @@ begin 'if(smCv.width!==(sw*dpr|0)||smCv.height!==(sh*dpr|0)){smCv.width=sw*dpr|0;smCv.height=sh*dpr|0;mctx.setTransform(dpr,0,0,dpr,0,0);}' + 'if(wfBuf.width!==wfCv.clientWidth||wfBuf.height!==wfCv.clientHeight){wfBuf.width=wfCv.clientWidth;wfBuf.height=wfCv.clientHeight;wfBCtx.fillStyle="#000";wfBCtx.fillRect(0,0,wfBuf.width,wfBuf.height);}}' + + 'function syncStepSel(){const isFM=(cur.mode===5);const sel=q("stepSel");if(!sel)return;' + + 'if(isFM&&!inFMStep){lastHFStep=+sel.value||100;sel.innerHTML="";' + + 'FM_STEP_LBL.forEach((l,i)=>{const o=document.createElement("option");o.value=FM_STEP_HZ[i];o.textContent=l;sel.appendChild(o);});' + + 'sel.value=FM_STEP_HZ[fmStepIdx];inFMStep=true;}' + + 'else if(!isFM&&inFMStep){sel.innerHTML=["10 Hz","100 Hz","1 kHz","10 kHz"].map((l,i)=>``).join("");' + + 'sel.value=lastHFStep;inFMStep=false;}' + + 'else if(isFM){sel.value=FM_STEP_HZ[fmStepIdx];}}' + 'function tuneStep(e){const s=+q("stepSel").value||100;if(e.ctrlKey&&e.shiftKey)return 1000000;if(e.ctrlKey)return 1000;if(e.shiftKey)return 10;return s;}' + 'function activeVfoHz(){return activeVfo==="A"?vfoA:vfoB;}' + 'function setActiveVfoHz(f){if(activeVfo==="A")vfoA=f;else vfoB=f;}' + diff --git a/WebServer.pas b/WebServer.pas index 8c0e2e4..5a917be 100644 --- a/WebServer.pas +++ b/WebServer.pas @@ -212,6 +212,7 @@ type FFilterIdx: Integer; FVfoB: Double; FActiveVfo: Integer; // 0=A, 1=B + FFMStepIdx: Integer; FStateLock: TCriticalSection; // ── Callbacks ── @@ -304,6 +305,7 @@ type property WebClientActive: Boolean read FWebClientActive; property FreqMhzDigits: Integer read FFreqMhzDigits write FFreqMhzDigits; + property FMStepIdx: Integer read FFMStepIdx write FFMStepIdx; property OnFreq: TWebCmdFreq read FOnFreq write FOnFreq; property OnMode: TWebCmdMode read FOnMode write FOnMode; @@ -425,8 +427,9 @@ begin FClientLock := TCriticalSection.Create; FStateLock := TCriticalSection.Create; // Начальные значения состояния - FFreq := 14200000; - FMode := 1; + FFreq := 14200000; + FMode := 1; + FFMStepIdx := 3; // 25 kHz default FFilterBW:= 2700; FAGCMode := 1; FAGCTop := 90; @@ -1296,7 +1299,8 @@ begin '"nr_mode":%d,"nr":%s,"nb_mode":%d,"nb":%s,"snb":%s,"anf":%s,"connected":%s,' + '"transmitting":%s,"drive":%d,"attn_idx":%d,"tuning":%s,"dup":%s,' + '"fwd_w":%.1f,"swr":%.2f,"pa_max_power":%.0f,' + - '"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d}', + '"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d,' + + '"fmstep_idx":%d}', [FFreq, FVfoB, FActiveVfo, FMode, MODE_N[FMode mod 8], FFilterIdx, FFilterBW, @@ -1321,7 +1325,8 @@ begin BoolToStr(FTuning, 'true', 'false'), BoolToStr(FDuplex, 'true', 'false'), FFwdW, FSWR, FPAMaxPower, - FCurrentXvtr, XvtrJson, FFreqMhzDigits + FCurrentXvtr, XvtrJson, FFreqMhzDigits, + FFMStepIdx ], FS); finally FStateLock.Leave;