Files
ewsdr/ChannelStore.pas
T

277 lines
7.7 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
unit ChannelStore;
{ TChannelStore — хранилище каналов (channel memory).
Формат файла channels.json (рядом с hpsdr_settings.json). }
{$mode objfpc}{$H+}
interface
uses
SysUtils, Classes, Math, fpJSON, jsonparser, jsonscanner, PlatformUtils;
const
CHANNELS_FILE = 'channels.json';
CHANNEL_MODE_MAX = 11; // LSB..DMR
type
TChannel = record
Group: string;
Name: string;
RXFreq: Double; // Hz
TXFreq: Double; // Hz
Mode: Integer; // 0=LSB ... 10=WFM 11=DMR
RptDir: Integer; // 0=none 1=minus 2=plus
RptOffsetHz: Double; // Hz
CTCSSOn: Boolean;
CTCSSToneIdx: Integer; // 0..37
Power: Integer; // 0..100
end;
TChannelStore = class
private
FChannels: array of TChannel;
FFilePath: string;
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;
public
constructor Create(const AFilePath: string = '');
procedure Load;
procedure Save;
function Count: Integer;
function GetChannel(Idx: Integer): TChannel;
procedure AddChannel(const Ch: TChannel);
procedure UpdateChannel(Idx: Integer; const Ch: TChannel);
procedure DeleteChannel(Idx: Integer);
procedure MoveUp(Idx: Integer);
procedure MoveDown(Idx: Integer);
class procedure DefaultChannel(out Ch: TChannel);
end;
implementation
class procedure TChannelStore.DefaultChannel(out Ch: TChannel);
begin
FillChar(Ch, SizeOf(Ch), 0);
Ch.Group := '';
Ch.Name := 'New Channel';
Ch.RXFreq := 145500000.0;
Ch.TXFreq := 145500000.0;
Ch.Mode := 5; // FM
Ch.RptDir := 0;
Ch.RptOffsetHz := 600000.0;
Ch.CTCSSOn := False;
Ch.CTCSSToneIdx := 0;
Ch.Power := 100;
end;
constructor TChannelStore.Create(const AFilePath: string);
begin
inherited Create;
if AFilePath = '' then
FFilePath := GetAppCfgDir + CHANNELS_FILE
else
FFilePath := AFilePath;
SetLength(FChannels, 0);
end;
function TChannelStore.Count: Integer;
begin
Result := Length(FChannels);
end;
function TChannelStore.GetChannel(Idx: Integer): TChannel;
begin
if (Idx >= 0) and (Idx < Length(FChannels)) then
Result := FChannels[Idx]
else
DefaultChannel(Result);
end;
procedure TChannelStore.AddChannel(const Ch: TChannel);
var N: Integer;
begin
N := Length(FChannels);
SetLength(FChannels, N + 1);
FChannels[N] := Ch;
end;
procedure TChannelStore.UpdateChannel(Idx: Integer; const Ch: TChannel);
begin
if (Idx >= 0) and (Idx < Length(FChannels)) then
FChannels[Idx] := Ch;
end;
procedure TChannelStore.DeleteChannel(Idx: Integer);
var i: Integer;
begin
if (Idx < 0) or (Idx >= Length(FChannels)) then Exit;
for i := Idx to Length(FChannels) - 2 do
FChannels[i] := FChannels[i + 1];
SetLength(FChannels, Length(FChannels) - 1);
end;
procedure TChannelStore.MoveUp(Idx: Integer);
var Tmp: TChannel;
begin
if (Idx < 1) or (Idx >= Length(FChannels)) then Exit;
Tmp := FChannels[Idx];
FChannels[Idx] := FChannels[Idx - 1];
FChannels[Idx - 1] := Tmp;
end;
procedure TChannelStore.MoveDown(Idx: Integer);
var Tmp: TChannel;
begin
if (Idx < 0) or (Idx >= Length(FChannels) - 1) then Exit;
Tmp := FChannels[Idx];
FChannels[Idx] := FChannels[Idx + 1];
FChannels[Idx + 1] := Tmp;
end;
// ---------------------------------------------------------------------------
// JSON helpers
// ---------------------------------------------------------------------------
function TChannelStore.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 TChannelStore.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 TChannelStore.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;
function TChannelStore.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;
// ---------------------------------------------------------------------------
// Load / Save
// ---------------------------------------------------------------------------
procedure TChannelStore.Load;
var
F: TFileStream;
P: TJSONParser;
Root: TJSONData;
Arr: TJSONData;
Item: TJSONData;
O: TJSONObject;
Ch: TChannel;
i: Integer;
begin
SetLength(FChannels, 0);
if not FileExists(FFilePath) then Exit;
try
F := TFileStream.Create(FFilePath, fmOpenRead or fmShareDenyNone);
try
P := TJSONParser.Create(F, [joUTF8]);
try
Root := P.Parse;
if (Root <> nil) and (Root is TJSONObject) then
begin
Arr := TJSONObject(Root).Find('channels');
if (Arr <> nil) and (Arr is TJSONArray) then
begin
SetLength(FChannels, TJSONArray(Arr).Count);
for i := 0 to TJSONArray(Arr).Count - 1 do
begin
Item := TJSONArray(Arr).Items[i];
DefaultChannel(Ch);
if Item is TJSONObject then
begin
O := TJSONObject(Item);
Ch.Group := JS(O, 'group', '');
Ch.Name := JS(O, 'name', 'Channel');
Ch.RXFreq := JD(O, 'rx_freq', 145500000.0);
Ch.TXFreq := JD(O, 'tx_freq', 145500000.0);
Ch.Mode := EnsureRange(JI(O, 'mode', 5), 0, CHANNEL_MODE_MAX);
Ch.RptDir := EnsureRange(JI(O, 'rpt_dir', 0), 0, 2);
Ch.RptOffsetHz:= JD(O, 'rpt_offset', 600000.0);
Ch.CTCSSOn := JB(O, 'ctcss_on', False);
Ch.CTCSSToneIdx := EnsureRange(JI(O, 'ctcss_idx', 0), 0, 37);
Ch.Power := EnsureRange(JI(O, 'power', 100), 0, 100);
end;
FChannels[i] := Ch;
end;
end;
Root.Free;
end;
finally
P.Free;
end;
finally
F.Free;
end;
except
SetLength(FChannels, 0);
end;
end;
procedure TChannelStore.Save;
var
F: TFileStream;
Root: TJSONObject;
Arr: TJSONArray;
Obj: TJSONObject;
i: Integer;
S: string;
begin
Root := TJSONObject.Create;
try
Arr := TJSONArray.Create;
Root.Add('channels', Arr);
for i := 0 to Length(FChannels) - 1 do
begin
Obj := TJSONObject.Create;
Obj.Add('group', FChannels[i].Group);
Obj.Add('name', FChannels[i].Name);
Obj.Add('rx_freq', FChannels[i].RXFreq);
Obj.Add('tx_freq', FChannels[i].TXFreq);
Obj.Add('mode', FChannels[i].Mode);
Obj.Add('rpt_dir', FChannels[i].RptDir);
Obj.Add('rpt_offset', FChannels[i].RptOffsetHz);
Obj.Add('ctcss_on', FChannels[i].CTCSSOn);
Obj.Add('ctcss_idx', FChannels[i].CTCSSToneIdx);
Obj.Add('power', FChannels[i].Power);
Arr.Add(Obj);
end;
try
S := Root.FormatJSON([], 2);
F := TFileStream.Create(FFilePath, fmCreate);
try
if Length(S) > 0 then F.WriteBuffer(S[1], Length(S));
finally
F.Free;
end;
except
end;
finally
Root.Free;
end;
end;
end.