unit Settings; { Settings.pas — JSON-конфигурация HPSDR SDR приложения. Структура hpsdr_settings.json: { "AA:BB:CC:DD:EE:FF": { "global": {...}, "bands": {"5": {...}} } } } {$mode objfpc}{$H+} interface uses SysUtils, Classes, Math, fpJSON, jsonparser, jsonscanner; const SETTINGS_FILE = 'hpsdr_settings.json'; CFG_BAND_COUNT = 11; CFG_BAND_DEFAULT_FREQ: array[0..CFG_BAND_COUNT-1] of Double = ( 1900000, 3750000, 5357000, 7100000, 10125000, 14200000, 18120000, 21200000, 24940000, 28500000, 50150000); CFG_BAND_DEFAULT_MODE: array[0..CFG_BAND_COUNT-1] of Integer = ( 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1); type TBandSettings = record VfoA: Double; VfoB: Double; Mode: Integer; FilterIdx: Integer; FilterBW: Integer; AGCMode: Integer; AGCTop: Integer; CTun: Boolean; SpanHz: Double; end; TGlobalSettings = record Volume: Integer; DriveLevel: Integer; ActiveVfo: Integer; NRMode: Integer; NBMode: Integer; SNBEnabled: Boolean; ANFEnabled: Boolean; AGCSlope: Integer; AGCHangThreshold: Integer; WfAGCEnabled: Boolean; WfNFEnabled: Boolean; LastBand: Integer; SampleRate: Integer; // глобальный — один для всех диапазонов WindowLeft: Integer; WindowTop: Integer; WindowWidth: Integer; WindowHeight: Integer; // --- Display settings --- FFTSize: Integer; // 4096..262144 WindowType: Integer; // 0=Rect,1=BH4,2=Hann,3=FlatTop,4=Hamming,5=Kaiser,6=BH7 SpecDetector: Integer; // 0=Peak,1=Rosenfell,2=Average,3=Sample,4=RMS SpecAvgMode: Integer; // 0=None,1=Recursive,2=TimeWindow,3=LogRecursive SpecAvgTimeMS: Double; // averaging time in milliseconds WfDetector: Integer; // waterfall detector WfAvgMode: Integer; // waterfall average mode WfAvgTimeMS: Double; // waterfall averaging time in milliseconds WfManualHigh: Double; // manual waterfall high threshold WfManualLow: Double; // manual waterfall low threshold WfAGCOffset: Double; // AGC offset in dB SpecRefLevel: Double; // dBm reference (top of scale), e.g. -20 SpecRange: Double; // dB range, e.g. 110 (bottom = ref - range) SpecGridStep: Double; // grid step in dB, e.g. 10 // --- Audio settings --- AudioOutDevice: string; // PortAudio output device name ('' = default) AudioSampleRate: Integer; // PortAudio stream sample rate (e.g. 48000) AudioInDevice: string; // PortAudio input device name for TX mic ('' = none) // --- Visibility settings --- ShowSpectrum: Boolean; // True = draw spectrum on main form ShowWaterfall: Boolean; // True = draw waterfall on main form // --- Display FPS --- DisplayFPS: Integer; // spectrum/waterfall timer fps (1..100) // --- PA (Power Amplifier) settings --- PAMaxPower: Double; // максимальная выходная мощность, Вт (5..200) PABandCal: array[0..CFG_BAND_COUNT-1] of Double; // калибровка на диапазон 38.8..100.0 // --- CAT (Computer Aided Transceiver) settings --- // До 4 последовательных портов (Linux: /dev/ttyS0, Windows: COM1 и т.д.) CATSerialEnabled: array[0..3] of Boolean; CATSerialPort: array[0..3] of string; // имя порта ОС CATSerialBaud: array[0..3] of Integer; // скорость: 1200..115200 CATSerialDataBits: array[0..3] of Integer; // 7 или 8 CATSerialStopBits: array[0..3] of Integer; // 1 или 2 CATSerialParity: array[0..3] of Integer; // 0=None, 1=Odd, 2=Even // TCP CAT сервер (один, многоклиентский) CATTcpEnabled: Boolean; CATTcpPort: Integer; // порт, по умолчанию 19090 end; TSettingsManager = class private FFilePath: string; FRoot: TJSONObject; function EnsureObj(P: TJSONObject; const K: string): TJSONObject; function GetDevObj(const M: string): TJSONObject; function GetGlobalObj(D: TJSONObject): TJSONObject; function GetBandObj(D: TJSONObject; Idx: Integer): TJSONObject; function JI(O: TJSONObject; const K: string; Def: Integer): Integer; function JD(O: TJSONObject; const K: string; Def: Double): Double; function JB(O: TJSONObject; const K: string; Def: Boolean): Boolean; function JS(O: TJSONObject; const K: string; const Def: string): string; procedure JW(O: TJSONObject; const K: string; V: Integer); overload; procedure JW(O: TJSONObject; const K: string; V: Double); overload; procedure JW(O: TJSONObject; const K: string; V: Boolean); overload; procedure JWS(O: TJSONObject; const K: string; const V: string); public constructor Create(const FilePath: string = SETTINGS_FILE); destructor Destroy; override; procedure Load; procedure Save; function LoadDevice(const MAC: array of Byte; out G: TGlobalSettings; var Bands: array of TBandSettings): Boolean; procedure SaveGlobal(const MAC: array of Byte; const G: TGlobalSettings); procedure SaveBand(const MAC: array of Byte; BandIdx: Integer; const B: TBandSettings); function LoadBand(const MAC: array of Byte; BandIdx: Integer; out B: TBandSettings): Boolean; class function MacToStr(const MAC: array of Byte): string; class procedure DefaultBand(BandIdx: Integer; out B: TBandSettings); class procedure DefaultGlobal(out G: TGlobalSettings); // Размер окна — не привязан к MAC, хранится в корне JSON procedure SaveWindowBounds(L, T, W, H: Integer); procedure LoadWindowBounds(out L, T, W, H: Integer); procedure SaveStartupPreview(VfoA, VfoB: Double; SampleRate: Integer); function LoadStartupPreview(out VfoA, VfoB: Double; out SampleRate: Integer): Boolean; // CAT settings — не привязаны к MAC, хранятся в корне JSON (доступны до подключения) procedure SaveCATSettings(const G: TGlobalSettings); procedure LoadCATSettings(var G: TGlobalSettings); end; implementation function LegacyAverageTimeMS(FPS, AvgMode, AvgCount: Integer; Backmult, DefaultMS: Double): Double; var FPSv: Integer; begin if AvgMode <= 0 then Exit(0.0); FPSv := EnsureRange(FPS, 1, 100); if (Backmult > 0.0) and (Backmult < 1.0) then Result := -1000.0 / (FPSv * Ln(Backmult)) else Result := (1000.0 * Max(1, AvgCount)) / FPSv; if IsNan(Result) or IsInfinite(Result) or (Result <= 0.0) then Result := DefaultMS; Result := EnsureRange(Result, 10.0, 5000.0); end; class function TSettingsManager.MacToStr(const MAC: array of Byte): string; begin Result := Format('%02X:%02X:%02X:%02X:%02X:%02X', [MAC[0],MAC[1],MAC[2],MAC[3],MAC[4],MAC[5]]); end; class procedure TSettingsManager.DefaultBand(BandIdx: Integer; out B: TBandSettings); begin FillChar(B, SizeOf(B), 0); B.VfoA := CFG_BAND_DEFAULT_FREQ[BandIdx]; B.VfoB := CFG_BAND_DEFAULT_FREQ[BandIdx]; B.Mode := CFG_BAND_DEFAULT_MODE[BandIdx]; B.FilterIdx := 5; B.FilterBW := 2700; B.AGCMode := 1; B.AGCTop := 90; B.CTun := False; B.SpanHz := 192000; end; class procedure TSettingsManager.DefaultGlobal(out G: TGlobalSettings); var i: Integer; begin FillChar(G, SizeOf(G), 0); 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.WfAGCEnabled := True; G.WfNFEnabled := False; G.SampleRate := 192000; G.PAMaxPower := 100.0; for i := 0 to CFG_BAND_COUNT-1 do G.PABandCal[i] := 100.0; // Display defaults G.FFTSize := 131072; G.WindowType := 2; // Hann G.SpecDetector := 0; // Peak G.SpecAvgMode := 3; // Log Recursive G.SpecAvgTimeMS := 30.0; G.WfDetector := 0; // Peak G.WfAvgMode := 3; // Log Recursive G.WfAvgTimeMS := 120.0; G.WfManualHigh := -80.0; G.WfManualLow := -130.0; G.WfAGCOffset := 0.0; G.SpecRefLevel := -20.0; G.SpecRange := 110.0; G.SpecGridStep := 10.0; G.AudioOutDevice := ''; G.AudioSampleRate := 48000; G.AudioInDevice := ''; G.ShowSpectrum := True; G.ShowWaterfall := True; G.DisplayFPS := 60; // CAT defaults for i := 0 to 3 do begin G.CATSerialEnabled[i] := False; {$IFDEF MSWINDOWS} G.CATSerialPort[i] := 'COM' + IntToStr(i+1); {$ELSE} G.CATSerialPort[i] := '/dev/ttyS' + IntToStr(i); {$ENDIF} G.CATSerialBaud[i] := 9600; G.CATSerialDataBits[i] := 8; G.CATSerialStopBits[i] := 1; G.CATSerialParity[i] := 0; // None end; G.CATTcpEnabled := False; G.CATTcpPort := 19090; end; constructor TSettingsManager.Create(const FilePath: string); begin inherited Create; FFilePath := FilePath; FRoot := TJSONObject.Create; end; destructor TSettingsManager.Destroy; begin FRoot.Free; inherited; end; procedure TSettingsManager.Load; var F: TFileStream; P: TJSONParser; D: TJSONData; begin if not FileExists(FFilePath) then Exit; try F := TFileStream.Create(FFilePath, fmOpenRead or fmShareDenyNone); try P := TJSONParser.Create(F, [joUTF8]); try D := P.Parse; if (D <> nil) and (D is TJSONObject) then begin FRoot.Free; FRoot := TJSONObject(D); end else D.Free; finally P.Free; end; finally F.Free; end; except FreeAndNil(FRoot); FRoot := TJSONObject.Create; end; end; procedure TSettingsManager.Save; var S: string; F: TFileStream; Buf: TBytes; begin try S := FRoot.FormatJSON([], 2); Buf := TEncoding.UTF8.GetBytes(S); F := TFileStream.Create(FFilePath, fmCreate); try if Length(Buf) > 0 then F.WriteBuffer(Buf[0], Length(Buf)); finally F.Free; end; except end; end; function TSettingsManager.EnsureObj(P: TJSONObject; const K: string): TJSONObject; var D: TJSONData; Idx: Integer; begin D := P.Find(K); if (D <> nil) and (D is TJSONObject) then Result := TJSONObject(D) else begin if D <> nil then begin Idx := P.IndexOfName(K); if Idx >= 0 then P.Delete(Idx); end; Result := TJSONObject.Create; P.Add(K, Result); end; end; function TSettingsManager.GetDevObj(const M: string): TJSONObject; begin Result := EnsureObj(FRoot, M); end; function TSettingsManager.GetGlobalObj(D: TJSONObject): TJSONObject; begin Result := EnsureObj(D, 'global'); end; function TSettingsManager.GetBandObj(D: TJSONObject; Idx: Integer): TJSONObject; begin Result := EnsureObj(EnsureObj(D, 'bands'), IntToStr(Idx)); end; function TSettingsManager.JI(O: TJSONObject; const K: string; Def: Integer): Integer; var D: TJSONData; begin D := O.Find(K); if D<>nil then try Result:=D.AsInteger; except Result:=Def; end else Result:=Def; end; function TSettingsManager.JD(O: TJSONObject; const K: string; Def: Double): Double; var D: TJSONData; begin D := O.Find(K); if D<>nil then try Result:=D.AsFloat; except Result:=Def; end else Result:=Def; end; function TSettingsManager.JB(O: TJSONObject; const K: string; Def: Boolean): Boolean; var D: TJSONData; begin D := O.Find(K); if D<>nil then try Result:=D.AsBoolean; except Result:=Def; end else Result:=Def; end; procedure TSettingsManager.JW(O: TJSONObject; const K: string; V: Integer); var Idx: Integer; begin Idx := O.IndexOfName(K); if Idx >= 0 then O.Delete(Idx); O.Add(K, V); end; procedure TSettingsManager.JW(O: TJSONObject; const K: string; V: Double); var Idx: Integer; begin Idx := O.IndexOfName(K); if Idx >= 0 then O.Delete(Idx); O.Add(K, V); end; procedure TSettingsManager.JW(O: TJSONObject; const K: string; V: Boolean); var Idx: Integer; begin Idx := O.IndexOfName(K); if Idx >= 0 then O.Delete(Idx); O.Add(K, V); end; function TSettingsManager.JS(O: TJSONObject; const K: string; const Def: string): string; var D: TJSONData; begin D := O.Find(K); if D<>nil then try Result:=D.AsString; except Result:=Def; end else Result:=Def; end; procedure TSettingsManager.JWS(O: TJSONObject; const K: string; const V: string); var Idx: Integer; begin Idx := O.IndexOfName(K); if Idx >= 0 then O.Delete(Idx); O.Add(K, V); end; function TSettingsManager.LoadDevice(const MAC: array of Byte; out G: TGlobalSettings; var Bands: array of TBandSettings): Boolean; var MacStr: string; DevObj,GObj,BObj: TJSONObject; i: Integer; begin MacStr := MacToStr(MAC); Result := FRoot.Find(MacStr) <> nil; DevObj := GetDevObj(MacStr); GObj := GetGlobalObj(DevObj); G.Volume := JI(GObj,'volume',70); G.DriveLevel := JI(GObj,'drive_level',50); G.ActiveVfo := JI(GObj,'active_vfo',0); G.NRMode := JI(GObj,'nr_mode',Ord(JB(GObj,'nr_enabled',False))); G.NBMode := JI(GObj,'nb_mode',Ord(JB(GObj,'nb_enabled',False))); G.SNBEnabled := JB(GObj,'snb_enabled',False); G.ANFEnabled := JB(GObj,'anf_enabled',False); G.AGCSlope := JI(GObj,'agc_slope',0); G.AGCHangThreshold := JI(GObj,'agc_hang_threshold',100); G.WfAGCEnabled := JB(GObj,'wf_agc_enabled',True); G.WfNFEnabled := JB(GObj,'wf_nf_enabled',False); G.LastBand := JI(GObj,'last_band',5); G.SampleRate := JI(GObj,'sample_rate',192000); // Display settings G.FFTSize := JI(GObj,'fft_size',131072); G.WindowType := JI(GObj,'win_type',2); G.SpecDetector := JI(GObj,'spec_detector',0); G.SpecAvgMode := JI(GObj,'spec_avg_mode',3); if GObj.Find('spec_avg_time_ms') <> nil then G.SpecAvgTimeMS := JD(GObj,'spec_avg_time_ms',30.0) else if (GObj.Find('spec_avg_count') <> nil) or (GObj.Find('spec_backmult') <> nil) then begin G.SpecAvgTimeMS := LegacyAverageTimeMS( JI(GObj,'display_fps',60), G.SpecAvgMode, JI(GObj,'spec_avg_count',1), JD(GObj,'spec_backmult',0.0), 30.0); end else G.SpecAvgTimeMS := 30.0; G.WfDetector := JI(GObj,'wf_detector',0); G.WfAvgMode := JI(GObj,'wf_avg_mode',3); if GObj.Find('wf_avg_time_ms') <> nil then G.WfAvgTimeMS := JD(GObj,'wf_avg_time_ms',120.0) else if (GObj.Find('wf_avg_count') <> nil) or (GObj.Find('wf_backmult') <> nil) then begin G.WfAvgTimeMS := LegacyAverageTimeMS( JI(GObj,'display_fps',60), G.WfAvgMode, JI(GObj,'wf_avg_count',2), JD(GObj,'wf_backmult',0.25), 120.0); end else G.WfAvgTimeMS := 120.0; G.WfManualHigh := JD(GObj,'wf_manual_high',-80.0); G.WfManualLow := JD(GObj,'wf_manual_low',-130.0); G.WfAGCOffset := JD(GObj,'wf_agc_offset',0.0); G.SpecRefLevel := JD(GObj,'spec_ref_level',-20.0); G.SpecRange := JD(GObj,'spec_range',110.0); G.SpecGridStep := JD(GObj,'spec_grid_step',10.0); // Audio settings G.AudioOutDevice := JS(GObj,'audio_out_device',''); G.AudioSampleRate := JI(GObj,'audio_sample_rate',48000); G.AudioInDevice := JS(GObj,'audio_in_device',''); // Visibility settings G.ShowSpectrum := JB(GObj,'show_spectrum',True); G.ShowWaterfall := JB(GObj,'show_waterfall',True); G.DisplayFPS := JI(GObj,'display_fps',60); // PA settings G.PAMaxPower := JD(GObj,'pa_max_power',100.0); for i := 0 to CFG_BAND_COUNT-1 do G.PABandCal[i] := JD(GObj,'pa_band_cal_'+IntToStr(i),100.0); // CAT serial settings for i := 0 to 3 do begin G.CATSerialEnabled[i] := JB(GObj,'cat_serial_en_'+IntToStr(i), False); {$IFDEF MSWINDOWS} G.CATSerialPort[i] := JS(GObj,'cat_serial_port_'+IntToStr(i), 'COM'+IntToStr(i+1)); {$ELSE} G.CATSerialPort[i] := JS(GObj,'cat_serial_port_'+IntToStr(i), '/dev/ttyS'+IntToStr(i)); {$ENDIF} G.CATSerialBaud[i] := JI(GObj,'cat_serial_baud_'+IntToStr(i), 9600); G.CATSerialDataBits[i] := JI(GObj,'cat_serial_dbits_'+IntToStr(i), 8); G.CATSerialStopBits[i] := JI(GObj,'cat_serial_sbits_'+IntToStr(i), 1); G.CATSerialParity[i] := JI(GObj,'cat_serial_parity_'+IntToStr(i), 0); end; G.CATTcpEnabled := JB(GObj,'cat_tcp_enabled', False); G.CATTcpPort := JI(GObj,'cat_tcp_port', 19090); for i := 0 to CFG_BAND_COUNT-1 do begin DefaultBand(i, Bands[i]); BObj := GetBandObj(DevObj, i); Bands[i].VfoA := JD(BObj,'vfo_a', Bands[i].VfoA); Bands[i].VfoB := JD(BObj,'vfo_b', Bands[i].VfoB); Bands[i].Mode := JI(BObj,'mode', Bands[i].Mode); Bands[i].FilterIdx := JI(BObj,'filter_idx', Bands[i].FilterIdx); Bands[i].FilterBW := JI(BObj,'filter_bw', Bands[i].FilterBW); Bands[i].AGCMode := JI(BObj,'agc_mode', Bands[i].AGCMode); Bands[i].AGCTop := JI(BObj,'agc_top', Bands[i].AGCTop); Bands[i].CTun := JB(BObj,'ctun', Bands[i].CTun); Bands[i].SpanHz := JD(BObj,'span_hz', Bands[i].SpanHz); end; end; procedure TSettingsManager.SaveGlobal(const MAC: array of Byte; const G: TGlobalSettings); var O: TJSONObject; i: Integer; begin O := GetGlobalObj(GetDevObj(MacToStr(MAC))); JW(O,'volume',G.Volume); JW(O,'drive_level',G.DriveLevel); JW(O,'active_vfo',G.ActiveVfo); JW(O,'nr_mode',G.NRMode); JW(O,'nb_mode',G.NBMode); JW(O,'nr_enabled',G.NRMode <> 0); JW(O,'nb_enabled',G.NBMode <> 0); JW(O,'snb_enabled',G.SNBEnabled); JW(O,'anf_enabled',G.ANFEnabled); JW(O,'agc_slope',G.AGCSlope); JW(O,'agc_hang_threshold',G.AGCHangThreshold); JW(O,'wf_agc_enabled',G.WfAGCEnabled); JW(O,'wf_nf_enabled',G.WfNFEnabled); JW(O,'last_band',G.LastBand); JW(O,'sample_rate',G.SampleRate); // Display settings JW(O,'fft_size',G.FFTSize); JW(O,'win_type',G.WindowType); JW(O,'spec_detector',G.SpecDetector); JW(O,'spec_avg_mode',G.SpecAvgMode); JW(O,'spec_avg_time_ms',G.SpecAvgTimeMS); JW(O,'wf_detector',G.WfDetector); JW(O,'wf_avg_mode',G.WfAvgMode); JW(O,'wf_avg_time_ms',G.WfAvgTimeMS); JW(O,'wf_manual_high',G.WfManualHigh); JW(O,'wf_manual_low',G.WfManualLow); JW(O,'wf_agc_offset',G.WfAGCOffset); JW(O,'spec_ref_level',G.SpecRefLevel); JW(O,'spec_range',G.SpecRange); JW(O,'spec_grid_step',G.SpecGridStep); // Audio settings JWS(O,'audio_out_device',G.AudioOutDevice); JW(O,'audio_sample_rate',G.AudioSampleRate); JWS(O,'audio_in_device',G.AudioInDevice); // Visibility settings JW(O,'show_spectrum',G.ShowSpectrum); JW(O,'show_waterfall',G.ShowWaterfall); JW(O,'display_fps',G.DisplayFPS); // PA settings JW(O,'pa_max_power',G.PAMaxPower); for i := 0 to CFG_BAND_COUNT-1 do JW(O,'pa_band_cal_'+IntToStr(i),G.PABandCal[i]); // CAT serial settings for i := 0 to 3 do begin JW(O,'cat_serial_en_'+IntToStr(i), G.CATSerialEnabled[i]); JWS(O,'cat_serial_port_'+IntToStr(i),G.CATSerialPort[i]); JW(O,'cat_serial_baud_'+IntToStr(i), G.CATSerialBaud[i]); JW(O,'cat_serial_dbits_'+IntToStr(i),G.CATSerialDataBits[i]); JW(O,'cat_serial_sbits_'+IntToStr(i),G.CATSerialStopBits[i]); JW(O,'cat_serial_parity_'+IntToStr(i),G.CATSerialParity[i]); end; JW(O,'cat_tcp_enabled',G.CATTcpEnabled); JW(O,'cat_tcp_port',G.CATTcpPort); end; procedure TSettingsManager.SaveBand(const MAC: array of Byte; BandIdx: Integer; const B: TBandSettings); var O: TJSONObject; begin if (BandIdx < 0) or (BandIdx >= CFG_BAND_COUNT) then Exit; O := GetBandObj(GetDevObj(MacToStr(MAC)), BandIdx); JW(O,'vfo_a',B.VfoA); JW(O,'vfo_b',B.VfoB); JW(O,'mode',B.Mode); JW(O,'filter_idx',B.FilterIdx); JW(O,'filter_bw',B.FilterBW); JW(O,'agc_mode',B.AGCMode); JW(O,'agc_top',B.AGCTop); JW(O,'ctun',B.CTun); JW(O,'span_hz',B.SpanHz); end; function TSettingsManager.LoadBand(const MAC: array of Byte; BandIdx: Integer; out B: TBandSettings): Boolean; var MacStr: string; O: TJSONObject; begin DefaultBand(BandIdx, B); MacStr := MacToStr(MAC); Result := FRoot.Find(MacStr) <> nil; if not Result then Exit; O := GetBandObj(GetDevObj(MacStr), BandIdx); B.VfoA := JD(O,'vfo_a', B.VfoA); B.VfoB := JD(O,'vfo_b', B.VfoB); B.Mode := JI(O,'mode', B.Mode); B.FilterIdx := JI(O,'filter_idx', B.FilterIdx); B.FilterBW := JI(O,'filter_bw', B.FilterBW); B.AGCMode := JI(O,'agc_mode', B.AGCMode); B.AGCTop := JI(O,'agc_top', B.AGCTop); B.CTun := JB(O,'ctun', B.CTun); B.SpanHz := JD(O,'span_hz', B.SpanHz); end; procedure TSettingsManager.SaveWindowBounds(L, T, W, H: Integer); var O: TJSONObject; begin O := EnsureObj(FRoot, 'window'); JW(O, 'left', L); JW(O, 'top', T); JW(O, 'width', W); JW(O, 'height', H); end; procedure TSettingsManager.LoadWindowBounds(out L, T, W, H: Integer); var O: TJSONObject; begin L := 80; T := 80; W := 1400; H := 900; if FRoot.Find('window') = nil then Exit; O := EnsureObj(FRoot, 'window'); L := JI(O, 'left', 80); T := JI(O, 'top', 80); W := JI(O, 'width', 1400); H := JI(O, 'height', 900); end; procedure TSettingsManager.SaveStartupPreview(VfoA, VfoB: Double; SampleRate: Integer); var O: TJSONObject; begin O := EnsureObj(FRoot, 'startup'); JW(O, 'vfo_a', VfoA); JW(O, 'vfo_b', VfoB); JW(O, 'sample_rate', SampleRate); end; function TSettingsManager.LoadStartupPreview(out VfoA, VfoB: Double; out SampleRate: Integer): Boolean; var O: TJSONObject; begin Result := FRoot.Find('startup') <> nil; if not Result then Exit; O := EnsureObj(FRoot, 'startup'); VfoA := JD(O, 'vfo_a', 14200000); VfoB := JD(O, 'vfo_b', 7100000); SampleRate := JI(O, 'sample_rate', 192000); end; procedure TSettingsManager.SaveCATSettings(const G: TGlobalSettings); var O: TJSONObject; i: Integer; begin O := EnsureObj(FRoot, 'cat'); JW(O, 'tcp_en', G.CATTcpEnabled); JW(O, 'tcp_port', G.CATTcpPort); for i := 0 to 3 do begin JW(O, 'ser_en_' +IntToStr(i), G.CATSerialEnabled[i]); JWS(O, 'ser_port_' +IntToStr(i), G.CATSerialPort[i]); JW(O, 'ser_baud_' +IntToStr(i), G.CATSerialBaud[i]); JW(O, 'ser_dbits_' +IntToStr(i), G.CATSerialDataBits[i]); JW(O, 'ser_sbits_' +IntToStr(i), G.CATSerialStopBits[i]); JW(O, 'ser_parity_'+IntToStr(i), G.CATSerialParity[i]); end; end; procedure TSettingsManager.LoadCATSettings(var G: TGlobalSettings); var O: TJSONObject; i: Integer; begin if FRoot.Find('cat') = nil then Exit; O := EnsureObj(FRoot, 'cat'); G.CATTcpEnabled := JB(O, 'tcp_en', False); G.CATTcpPort := JI(O, 'tcp_port', 19090); for i := 0 to 3 do begin G.CATSerialEnabled[i] := JB(O, 'ser_en_' +IntToStr(i), False); {$IFDEF MSWINDOWS} G.CATSerialPort[i] := JS(O, 'ser_port_' +IntToStr(i), 'COM'+IntToStr(i+1)); {$ELSE} G.CATSerialPort[i] := JS(O, 'ser_port_' +IntToStr(i), '/dev/ttyS'+IntToStr(i)); {$ENDIF} G.CATSerialBaud[i] := JI(O, 'ser_baud_' +IntToStr(i), 9600); G.CATSerialDataBits[i] := JI(O, 'ser_dbits_' +IntToStr(i), 8); G.CATSerialStopBits[i] := JI(O, 'ser_sbits_' +IntToStr(i), 1); G.CATSerialParity[i] := JI(O, 'ser_parity_'+IntToStr(i), 0); end; end; end.