1405 lines
39 KiB
ObjectPascal
1405 lines
39 KiB
ObjectPascal
unit U_LSPLCAgent;
|
||
|
||
interface
|
||
|
||
uses
|
||
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
|
||
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Win.ScktComp,
|
||
Vcl.ExtCtrls, cxGraphics, cxControls, cxLookAndFeels, cxLookAndFeelPainters,
|
||
cxStyles, cxCustomData, cxFilter, cxData, cxDataStorage, cxEdit, cxNavigator,
|
||
dxDateRanges, dxScrollbarAnnotations, cxGridCustomTableView, cxGridTableView,
|
||
cxGridLevel, cxClasses, cxGridCustomView, cxGrid, Data.DB, Datasnap.DBClient,
|
||
math, IniFiles, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error,
|
||
FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool,
|
||
FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Stan.Param, FireDAC.DatS,
|
||
FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Phys.PGDef, FireDAC.VCLUI.Wait,
|
||
FireDAC.Comp.UI, FireDAC.Phys.PG, FireDAC.Comp.DataSet, FireDAC.Comp.Client,
|
||
System.StrUtils;
|
||
|
||
type
|
||
TF_LSPLCAgent = class(TForm)
|
||
GroupBox1: TGroupBox;
|
||
Label14: TLabel;
|
||
Edit5: TEdit;
|
||
Label15: TLabel;
|
||
Edit6: TEdit;
|
||
Button11: TButton;
|
||
ClientSocket1: TClientSocket;
|
||
Button1: TButton;
|
||
ListBox1: TListBox;
|
||
ListBox2: TListBox;
|
||
Splitter1: TSplitter;
|
||
GridAddressList: TcxGrid;
|
||
GridAddressListTV: TcxGridTableView;
|
||
GridAddressListTVColumn1: TcxGridColumn;
|
||
GridAddressListTVColumn2: TcxGridColumn;
|
||
GridAddressListTVColumn3: TcxGridColumn;
|
||
GridAddressListTVColumn4: TcxGridColumn;
|
||
GridAddressListTVColumn5: TcxGridColumn;
|
||
GridAddressListLv: TcxGridLevel;
|
||
Label1: TLabel;
|
||
Edit1: TEdit;
|
||
Panel1: TPanel;
|
||
Memo1: TMemo;
|
||
Splitter2: TSplitter;
|
||
Memo2: TMemo;
|
||
Button2: TButton;
|
||
Button3: TButton;
|
||
Button4: TButton;
|
||
Button5: TButton;
|
||
Tmr_PLCRead: TTimer;
|
||
Label2: TLabel;
|
||
Edit2: TEdit;
|
||
Button6: TButton;
|
||
Button7: TButton;
|
||
TrayIcon1: TTrayIcon;
|
||
tmrExit: TTimer;
|
||
CheckBox1: TCheckBox;
|
||
FDConnection1: TFDConnection;
|
||
FDQuery1: TFDQuery;
|
||
FDPhysPgDriverLink1: TFDPhysPgDriverLink;
|
||
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
|
||
RadioButton1: TRadioButton;
|
||
RadioButton2: TRadioButton;
|
||
Tmr_DBInsert: TTimer;
|
||
FDQuery2: TFDQuery;
|
||
Panel2: TPanel;
|
||
Label17: TLabel;
|
||
Label16: TLabel;
|
||
ComboBox5: TComboBox;
|
||
Button13: TButton;
|
||
Edit10: TEdit;
|
||
Label3: TLabel;
|
||
cb_logview: TCheckBox;
|
||
procedure Button11Click(Sender: TObject);
|
||
procedure ClientSocket1Connecting(Sender: TObject;
|
||
Socket: TCustomWinSocket);
|
||
procedure ClientSocket1Disconnect(Sender: TObject;
|
||
Socket: TCustomWinSocket);
|
||
procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
|
||
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
|
||
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
|
||
procedure Button13Click(Sender: TObject);
|
||
procedure FormCreate(Sender: TObject);
|
||
procedure Button1Click(Sender: TObject);
|
||
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
|
||
procedure Button2Click(Sender: TObject);
|
||
procedure Button3Click(Sender: TObject);
|
||
procedure Button4Click(Sender: TObject);
|
||
procedure Button5Click(Sender: TObject);
|
||
procedure Button6Click(Sender: TObject);
|
||
procedure Button7Click(Sender: TObject);
|
||
procedure Tmr_PLCReadTimer(Sender: TObject);
|
||
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
||
procedure TrayIcon1Click(Sender: TObject);
|
||
procedure tmrExitTimer(Sender: TObject);
|
||
procedure CheckBox1Click(Sender: TObject);
|
||
procedure Tmr_DBInsertTimer(Sender: TObject);
|
||
procedure FormClose(Sender: TObject; var Action: TCloseAction);
|
||
private
|
||
{ Private declarations }
|
||
inifilename : string;
|
||
ExitOk : boolean;
|
||
close_ck : Boolean;
|
||
TxBuf,
|
||
RxBuf : array [0..1024-1] of byte;
|
||
|
||
procedure BuildReadPacket(str:string);
|
||
procedure BuildReadPacketMulti(const AddrList: TStrings);
|
||
function PLCDataLoad: boolean;
|
||
function PLCDataLoadMulti: boolean;
|
||
procedure AppEvtMinimize(Sender: TObject);
|
||
procedure ParseMultiReadResponse(var RxBuf: array of Byte;
|
||
AddrCount: Integer; out Values: TArray<Word>);
|
||
procedure insertSQL(sql: string);
|
||
function DB_MainTableCreate: Boolean;
|
||
function DB_MainTableDescriptCreate: Boolean;
|
||
function DB_LogTableCreate: Boolean;
|
||
procedure Memo1_ADD(msg: string; ck:Integer = 0);
|
||
procedure Memo2_ADD(msg: string);
|
||
procedure PLC_connect;
|
||
public
|
||
{ Public declarations }
|
||
dbIP_PG, dbUser_PG, dbPW_PG, dbSchema_PG : string;
|
||
dbPort_PG : integer;
|
||
|
||
function DB_Connect:Boolean;
|
||
function DB_Close:Boolean;
|
||
procedure DB_Insert;
|
||
end;
|
||
|
||
var
|
||
F_LSPLCAgent: TF_LSPLCAgent;
|
||
LoadCK : Boolean;
|
||
PLCconnectCK : Integer;
|
||
|
||
const
|
||
PLC_NO = 1; // <20>Ұ<EFBFBD><D2B0><EFBFBD> 1ȣ
|
||
|
||
implementation
|
||
|
||
uses WinSock;
|
||
|
||
|
||
{$R *.dfm}
|
||
procedure TF_LSPLCAgent.Memo1_ADD(msg:string; ck:Integer = 0);
|
||
begin
|
||
while Memo1.Lines.Count > 50 do begin
|
||
Memo1.Lines.Delete(0);
|
||
end;
|
||
|
||
Memo1.Lines.Add(msg);
|
||
|
||
if ck = 1 then begin
|
||
// <20>α<EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Memo2_ADD(msg:string);
|
||
begin
|
||
while Memo2.Lines.Count > 50 do begin
|
||
Memo2.Lines.Delete(0);
|
||
end;
|
||
|
||
Memo2.Lines.ADD(msg);
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.BuildReadPacket(str:string);
|
||
var
|
||
i, varcnt, sum: Integer;
|
||
addrStr: string;
|
||
begin
|
||
FillChar(TxBuf, SizeOf(TxBuf), 0);
|
||
|
||
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
TxBuf[0] := $4C; // L
|
||
TxBuf[1] := $53; // S
|
||
TxBuf[2] := $49; // I
|
||
TxBuf[3] := $53; // S
|
||
TxBuf[4] := $2d; // -
|
||
TxBuf[5] := $58; // X
|
||
TxBuf[6] := $47; // G
|
||
TxBuf[7] := $54; // T
|
||
TxBuf[8] := $00; // NULL
|
||
TxBuf[9] := $00; // NULL
|
||
|
||
|
||
TxBuf[10] := $00; // Ŭ<><C5AC><EFBFBD>̾<EFBFBD>Ʈ -> <20><><EFBFBD><EFBFBD> (0x00)
|
||
TxBuf[11] := $00;
|
||
|
||
|
||
TxBuf[12] := $A0; // XGK 0xA0 , XGB(MK) 0xB0 , XGI 0xA4 , XGB(IEC) 0x11 , XGR 0xA8
|
||
|
||
|
||
TxBuf[13] := $33; // Ŭ<><C5AC><EFBFBD>̾<EFBFBD>Ʈ(HMI) -> <20><><EFBFBD><EFBFBD>(PLC) (0x33) / <20><><EFBFBD><EFBFBD>(PLC) -> Ŭ<><C5AC><EFBFBD>̾<EFBFBD>Ʈ(HMI) (0x11)
|
||
|
||
|
||
TxBuf[14] := $00; // <20><><EFBFBD><EFBFBD><EFBFBD>Ӱ<EFBFBD><D3B0><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϱ<EFBFBD> <20><><EFBFBD><EFBFBD> ID
|
||
TxBuf[15] := $00;
|
||
|
||
|
||
TxBuf[16] := 16; // Length (<28><><EFBFBD>ɿ<EFBFBD><C9BF><EFBFBD> 16<31><36><EFBFBD><EFBFBD>Ʈ) - <20><><EFBFBD>ɾ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>Ʈ ũ<><C5A9>
|
||
|
||
|
||
TxBuf[17] := $00; // <20>̴<EFBFBD><CCB4><EFBFBD><EFBFBD><EFBFBD>ġ
|
||
|
||
|
||
TxBuf[18] := $00; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> (0x00)
|
||
|
||
// CheckSum <20><><EFBFBD><EFBFBD>
|
||
sum := 0;
|
||
for i := 0 to 18 do begin
|
||
sum := sum + TxBuf[i];
|
||
if sum > 255 then sum := sum - 256;
|
||
end;
|
||
TxBuf[19] := sum;
|
||
|
||
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
TxBuf[20] := $54; // READ
|
||
TxBuf[21] := 0; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>Ǵ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
TxBuf[22] := $02; // DataType = WORD <20><><EFBFBD><EFBFBD> / $0E - <20><><EFBFBD><EFBFBD>
|
||
TxBuf[23] := 0; // $30; DataType (<28>̾<EFBFBD><CCBE><EFBFBD>)
|
||
TxBuf[24] := 0; // $30; LinkID (2)
|
||
TxBuf[25] := 0; // $30; (<28>̾<EFBFBD><CCBE><EFBFBD>)
|
||
TxBuf[26] := $01; // Variable count
|
||
TxBuf[27] := 0; // $30; (<28>̾<EFBFBD><CCBE><EFBFBD>)
|
||
|
||
addrStr := Trim(str);
|
||
|
||
varcnt := Length(addrStr);
|
||
TxBuf[28] := varcnt;
|
||
TxBuf[29] := 0;
|
||
for i := 1 to varcnt do TxBuf[29 + i] := Ord(addrStr[i]);
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.BuildReadPacketMulti(const AddrList: TStrings);
|
||
var
|
||
i, j, sum, pos: Integer;
|
||
addrStr: string;
|
||
addrLen: Integer;
|
||
totalLen: Integer;
|
||
begin
|
||
FillChar(TxBuf, SizeOf(TxBuf), 0);
|
||
|
||
// 1. Header ('LSIS-XGT')
|
||
TxBuf[0] := $4C; // L
|
||
TxBuf[1] := $53; // S
|
||
TxBuf[2] := $49; // I
|
||
TxBuf[3] := $53; // S
|
||
TxBuf[4] := $2d; // -
|
||
TxBuf[5] := $58; // X
|
||
TxBuf[6] := $47; // G
|
||
TxBuf[7] := $54; // T
|
||
TxBuf[8] := $00; // NULL
|
||
TxBuf[9] := $00; // NULL
|
||
|
||
// 2. Fixed header data
|
||
TxBuf[10] := $00; // Ŭ<><C5AC><EFBFBD>̾<EFBFBD>Ʈ -> <20><><EFBFBD><EFBFBD> (0x00)
|
||
TxBuf[11] := $00;
|
||
|
||
TxBuf[12] := $A0; // XGK 0xA0 , XGB(MK) 0xB0 , XGI 0xA4 , XGB(IEC) 0x11 , XGR 0xA8
|
||
|
||
TxBuf[13] := $33; // Ŭ<><C5AC><EFBFBD>̾<EFBFBD>Ʈ(HMI) -> <20><><EFBFBD><EFBFBD>(PLC) (0x33) / <20><><EFBFBD><EFBFBD>(PLC) -> Ŭ<><C5AC><EFBFBD>̾<EFBFBD>Ʈ(HMI) (0x11)
|
||
|
||
TxBuf[14] := $00; // <20><><EFBFBD><EFBFBD><EFBFBD>Ӱ<EFBFBD><D3B0><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϱ<EFBFBD> <20><><EFBFBD><EFBFBD> ID
|
||
TxBuf[15] := $00;
|
||
|
||
// <20>ϴ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ְ<EFBFBD> <20><><EFBFBD>߿<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
TxBuf[16] := $00; // Length (<28><><EFBFBD>ɿ<EFBFBD><C9BF><EFBFBD> 16<31><36><EFBFBD><EFBFBD>Ʈ) - <20><><EFBFBD>ɾ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>Ʈ ũ<><C5A9>
|
||
|
||
TxBuf[17] := $00; // <20>̴<EFBFBD><CCB4><EFBFBD><EFBFBD><EFBFBD>ġ
|
||
|
||
TxBuf[18] := $00; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> (0x00)
|
||
|
||
|
||
///////////////////////// 3. Checksum <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>߿<EFBFBD> ó<><C3B3><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><>ġ<EFBFBD><C4A1> [19]
|
||
|
||
|
||
// 4. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
TxBuf[20] := $54; // READ
|
||
TxBuf[21] := 0; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>Ǵ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
TxBuf[22] := $02; // DataType = WORD <20><><EFBFBD><EFBFBD> / $0E - <20><><EFBFBD><EFBFBD>
|
||
TxBuf[23] := 0; // $30; DataType (<28>̾<EFBFBD><CCBE><EFBFBD>)
|
||
TxBuf[24] := 0; // $30; LinkID (2)
|
||
TxBuf[25] := 0; // $30; (<28>̾<EFBFBD><CCBE><EFBFBD>)
|
||
|
||
|
||
TxBuf[26] := AddrList.Count; // Variable Count
|
||
|
||
|
||
TxBuf[27] := $00; // $30; (<28>̾<EFBFBD><CCBE><EFBFBD>)
|
||
|
||
pos := 28; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>ġ
|
||
|
||
for i := 0 to AddrList.Count - 1 do
|
||
begin
|
||
addrStr := Trim(AddrList[i]); // <20><>: %MW301
|
||
addrLen := Length(addrStr);
|
||
|
||
TxBuf[pos] := addrLen; // <20><><EFBFBD>ڿ<EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
TxBuf[pos + 1] := 0; // Reserved
|
||
|
||
for j := 1 to addrLen do
|
||
TxBuf[pos + 1 + j] := Ord(addrStr[j]);
|
||
|
||
Inc(pos, addrLen + 2); // <20><><EFBFBD><EFBFBD> <20><>ġ <20>̵<EFBFBD>
|
||
end;
|
||
|
||
// 5. <20><>ü Length <20><><EFBFBD><EFBFBD>
|
||
totalLen := pos - 20; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
TxBuf[16] := totalLen;
|
||
TxBuf[17] := 0;
|
||
|
||
// 6. Checksum <20><><EFBFBD><EFBFBD> (0~18<31><38><EFBFBD><EFBFBD> <20>ջ<EFBFBD>)
|
||
sum := 0;
|
||
for i := 0 to 18 do
|
||
begin
|
||
sum := sum + TxBuf[i];
|
||
if sum > 255 then sum := sum - 256;
|
||
end;
|
||
TxBuf[19] := sum;
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> TxBuf <20><> ũ<><C5A9><EFBFBD><EFBFBD> pos <20><><EFBFBD><EFBFBD>Ʈ<EFBFBD><C6AE><EFBFBD><EFBFBD> <20><>ȿ<EFBFBD><C8BF>
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button11Click(Sender: TObject);
|
||
begin
|
||
{
|
||
TThread.Synchronize(nil,
|
||
procedure
|
||
begin
|
||
try
|
||
LoadCK := False;
|
||
if Button11.Caption = 'Connect' then begin
|
||
ClientSocket1.Host := Edit5.Text;
|
||
ClientSocket1.Port := StrToIntDef(Edit6.Text,2004);
|
||
ClientSocket1.Active := True;
|
||
LoadCK := True;
|
||
end
|
||
else begin
|
||
ClientSocket1.Active := False;
|
||
end;
|
||
|
||
except
|
||
on E: Exception do
|
||
begin
|
||
Memo1_ADD('PLC Connect Except : ' + E.Message);
|
||
end;
|
||
end;
|
||
end
|
||
);
|
||
}
|
||
|
||
|
||
{ 20251013
|
||
if PLCconnectCK = 0 then begin // <20>ʱⰪ : 0 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
PLCconnectCK := 1; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
try
|
||
LoadCK := False;
|
||
if Button11.Caption = 'Connect' then begin
|
||
ClientSocket1.Host := Edit5.Text;
|
||
ClientSocket1.Port := StrToIntDef(Edit6.Text,2004);
|
||
ClientSocket1.Active := True;
|
||
LoadCK := True;
|
||
end
|
||
else begin
|
||
ClientSocket1.Active := False;
|
||
end;
|
||
|
||
except
|
||
on E: Exception do
|
||
begin
|
||
Memo1_ADD('Except : ' + E.Message);
|
||
PLCconnectCK := 0; // <20>ʱⰪ : 0 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
if PLCconnectCK = 1 then begin
|
||
if Pos('<27><><EFBFBD><EFBFBD> <20>õ<EFBFBD>',Memo1.Lines[Memo1.Lines.Count-1]) > 0 then begin
|
||
Memo1.Lines[Memo1.Lines.Count-1] := Memo1.Lines[Memo1.Lines.Count-1]+'.';
|
||
end;
|
||
end;
|
||
}
|
||
|
||
if Button11.Caption = 'Connect' then begin
|
||
PLC_connect;
|
||
end
|
||
else begin
|
||
ClientSocket1.Active := False;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.PLC_connect;
|
||
begin
|
||
try
|
||
if PLCconnectCK = 0 then begin // <20>ʱⰪ : 0 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
PLCconnectCK := 1; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
try
|
||
LoadCK := False;
|
||
if Button11.Caption = 'Connect' then begin
|
||
ClientSocket1.Host := Edit5.Text;
|
||
ClientSocket1.Port := StrToIntDef(Edit6.Text,2004);
|
||
ClientSocket1.Active := True;
|
||
LoadCK := True;
|
||
end
|
||
else begin
|
||
ClientSocket1.Active := False;
|
||
end;
|
||
|
||
except
|
||
on E: Exception do
|
||
begin
|
||
Memo1_ADD('Except : ' + E.Message);
|
||
PLCconnectCK := 0; // <20>ʱⰪ : 0 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
if PLCconnectCK = 1 then begin
|
||
if Pos('<27><><EFBFBD><EFBFBD> <20>õ<EFBFBD>',Memo1.Lines[Memo1.Lines.Count-1]) > 0 then begin
|
||
Memo1.Lines[Memo1.Lines.Count-1] := Memo1.Lines[Memo1.Lines.Count-1]+'.';
|
||
end;
|
||
end;
|
||
except
|
||
on E: Exception do begin
|
||
Memo1_ADD('PLC_connect Error : ' + E.Message);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button13Click(Sender: TObject);
|
||
var
|
||
Sent, Recv: Integer;
|
||
begin
|
||
if not ClientSocket1.Active then PLC_connect;
|
||
|
||
if ClientSocket1.Active then begin
|
||
try
|
||
BuildReadPacket(ComboBox5.Text+Edit10.Text);
|
||
|
||
Sent := ClientSocket1.Socket.SendBuf(TxBuf, 36);
|
||
if Sent <> 36 then begin
|
||
Memo1_ADD('<27><>Ŷ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> : [ '+ComboBox5.Text+Edit10.Text+' ]');
|
||
Exit;
|
||
end;
|
||
|
||
Recv := ClientSocket1.Socket.ReceiveBuf(RxBuf, 34);
|
||
if Recv < 34 then begin
|
||
Memo1_ADD('<27><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> : [ '+ComboBox5.Text+Edit10.Text+' ]');
|
||
Exit;
|
||
end;
|
||
|
||
if cb_logview.Checked then begin
|
||
// 1. Memo1: <20><><EFBFBD><EFBFBD> HEX
|
||
var hexStr := '';
|
||
for var i := 0 to Recv - 1 do hexStr := hexStr + IntToHex(RxBuf[i], 2) + ' ';
|
||
Memo2_ADD(hexStr);
|
||
|
||
// 2. Memo2: <20>ؼ<EFBFBD><D8BC><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
var value := RxBuf[32] + (RxBuf[33] shl 8);
|
||
Memo2_ADD('DEC: ' + IntToStr(value));
|
||
// StringGrid1.Cells[1,1] := Trim(Edit2.Text) + '=' + IntToStr(value);
|
||
Memo2_ADD('HEX: ' + IntToHex(value, 4));
|
||
end;
|
||
except
|
||
on E: ESocketError do
|
||
begin
|
||
Memo1_ADD('Ŭ<><C5AC><EFBFBD>̾<EFBFBD>Ʈ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>: ' + E.Message);
|
||
// <20><><EFBFBD><EFBFBD> <20><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
ClientSocket1.Active := False;
|
||
end;
|
||
on E: Exception do
|
||
begin
|
||
Memo1_ADD('<27><><EFBFBD><EFBFBD>ġ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>: ' + E.Message);
|
||
ClientSocket1.Active := False;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function TF_LSPLCAgent.PLCDataLoad:boolean;
|
||
var
|
||
i : integer;
|
||
Sent, Recv: Integer;
|
||
LastError: Integer;
|
||
ErrorMsg: string;
|
||
begin
|
||
if PLCconnectCK <> 2 then exit;
|
||
|
||
Button1.Enabled := False;
|
||
|
||
try
|
||
try
|
||
GridAddressListTV.DataController.BeginUpdate;
|
||
|
||
for i := 0 to GridAddressListTV.DataController.RecordCount - 1 do begin
|
||
with GridAddressListTV.DataController do begin
|
||
Values[i, GridAddressListTVColumn5.Index] := '';
|
||
|
||
if ClientSocket1.Active then begin
|
||
BuildReadPacket(Values[i, GridAddressListTVColumn2.Index]);
|
||
|
||
Sent := ClientSocket1.Socket.SendBuf(TxBuf, 36);
|
||
if Sent <> 36 then begin
|
||
if cb_logview.Checked then
|
||
Memo1_ADD('<27><>Ŷ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> PLCDataLoad: ['+IntToStr(Sent)+']-[ '+Values[i, GridAddressListTVColumn2.Index]+' ]');
|
||
LoadCK := False;
|
||
// Exit;
|
||
end
|
||
else begin
|
||
Sleep(StrToIntDef(Edit1.Text,0));
|
||
Recv := ClientSocket1.Socket.ReceiveBuf(RxBuf, 34);
|
||
if Recv < 34 then begin
|
||
if cb_logview.Checked then begin
|
||
Memo1_ADD('<27><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> PLCDataLoad: [Sent:'+IntToStr(Sent)+']-[Recv:'+IntToStr(Recv)+']-[ '+Values[i, GridAddressListTVColumn2.Index]+' ]');
|
||
|
||
if Recv = -1 then begin
|
||
// -1: <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><EFBFBD> (WSAGetLastError ȣ<><C8A3>)
|
||
LastError := WSAGetLastError;
|
||
// <20><><EFBFBD><EFBFBD> <20>ڵ忡 <20><><EFBFBD><EFBFBD> <20><EFBFBD><DEBD><EFBFBD> <20><><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD>, <20>ʿ信 <20><><EFBFBD><EFBFBD> Ȯ<><C8AE> <20><><EFBFBD><EFBFBD>)
|
||
case LastError of
|
||
10054: ErrorMsg := 'Connection reset by peer (PLC<4C><43><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>)';
|
||
10060: ErrorMsg := 'Connection timed out (<28><><EFBFBD><EFBFBD> Ÿ<>Ӿƿ<D3BE>)';
|
||
10004: ErrorMsg := 'Interrupted function call (<28><><EFBFBD><EFBFBD>ŷ <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>)';
|
||
// <20><> <20><> <20>ڵ忡 <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Winsock <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
else
|
||
ErrorMsg := 'Unknown socket error';
|
||
end;
|
||
if cb_logview.Checked then
|
||
Memo1_ADD('<27><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> Recv: ['+IntToStr(Recv)+'] - WSAGetLastError: ['+IntToStr(LastError)+'] - Message: ['+ErrorMsg+']');
|
||
// LoadCK := False; // <20><><EFBFBD><EFBFBD> <20><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ε<EFBFBD> <20><><EFBFBD><EFBFBD> ó<><C3B3>
|
||
end else if Recv = 0 then begin
|
||
// 0: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> (PLC <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ݾ<EFBFBD><DDBE><EFBFBD>)
|
||
if cb_logview.Checked then
|
||
Memo1_ADD('<27><><EFBFBD><EFBFBD> PLC<4C><43><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (Recv: 0) - <20>ּ<EFBFBD>: ['+Values[i, GridAddressListTVColumn2.Index]+']');
|
||
end else begin
|
||
// 0 < Recv < 34: <20><>Ŷ<EFBFBD><C5B6> <20>Ϻθ<CFBA> <20><><EFBFBD><EFBFBD><EFBFBD>߰ų<DFB0>, PLC<4C><43><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>Ŷ(<28><><EFBFBD><EFBFBD> ũ<><C5A9>)<29><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>ɼ<EFBFBD>
|
||
if cb_logview.Checked then
|
||
Memo1_ADD('<27><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ҿ<EFBFBD><D2BF><EFBFBD> (Recv < 34) PLCDataLoad: [Sent:'+IntToStr(Sent)+']-[Recv:'+IntToStr(Recv)+']-[ '+Values[i, GridAddressListTVColumn2.Index]+' ]');
|
||
// <20><> <20><><EFBFBD><EFBFBD>, <20><><EFBFBD>ŵ<EFBFBD> RxBuf[0] ~ RxBuf[Recv-1]<5D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>м<EFBFBD><D0BC>Ͽ<EFBFBD>
|
||
// LSIS XGT <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ڵ带 Ȯ<><C8AE><EFBFBD>ϴ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʿ<EFBFBD><CABF>մϴ<D5B4>.
|
||
end;
|
||
// LoadCK := False;
|
||
// Exit; // <20>ʿ信 <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ߴ<EFBFBD><DFB4>ϰ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20>ֽ<EFBFBD><D6BD>ϴ<EFBFBD>.
|
||
end;
|
||
//LoadCK := False;
|
||
// Exit;
|
||
end
|
||
else begin
|
||
if cb_logview.Checked then begin
|
||
// 1. Memo1: <20><><EFBFBD><EFBFBD> HEX
|
||
var hexStr := '';
|
||
for var x := 0 to Recv - 1 do hexStr := hexStr + IntToHex(RxBuf[x], 2) + ' ';
|
||
Memo2_ADD(hexStr);
|
||
end;
|
||
|
||
// 2. Memo2: <20>ؼ<EFBFBD><D8BC><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
var value := RxBuf[32] + (RxBuf[33] shl 8);
|
||
|
||
if cb_logview.Checked then begin
|
||
Memo2_ADD('DEC: ' + IntToStr(value));
|
||
// StringGrid1.Cells[1,1] := Trim(Edit2.Text) + '=' + IntToStr(value);
|
||
Memo2_ADD('HEX: ' + IntToHex(value, 4));
|
||
end;
|
||
|
||
Values[i, GridAddressListTVColumn3.Index] := IntToStr(value);
|
||
|
||
Values[i, GridAddressListTVColumn4.Index] := IntToHex(value, 4);
|
||
|
||
Values[i, GridAddressListTVColumn5.Index] := FormatDateTime('hhnnss.zzz',Now);
|
||
|
||
LoadCK := True;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
// Sleep(StrToIntDef(Edit1.Text,0));
|
||
// Refresh;
|
||
end;
|
||
except
|
||
on E: Exception do begin
|
||
Memo1_ADD('PLCDataLoad Error : ' + E.Message);
|
||
LoadCK := False;
|
||
end;
|
||
end;
|
||
|
||
finally
|
||
Result := True;
|
||
GridAddressListTV.DataController.EndUpdate;
|
||
Button1.Enabled := True;
|
||
Refresh;
|
||
end;
|
||
end;
|
||
|
||
function TF_LSPLCAgent.PLCDataLoadMulti:boolean;
|
||
var
|
||
t, i, j, t_count : integer;
|
||
Sent, Recv: Integer;
|
||
addrList: TStringList;
|
||
|
||
addr_Values: TArray<Word>;
|
||
Addr_Count: Integer;
|
||
begin
|
||
Button1.Enabled := False;
|
||
addrList := TStringList.Create;
|
||
|
||
try
|
||
try
|
||
GridAddressListTV.DataController.BeginUpdate;
|
||
|
||
t_count := GridAddressListTV.DataController.RecordCount;
|
||
|
||
if (t_count mod 10) > 0 then begin
|
||
t := (t_count div 10) +1;
|
||
end
|
||
else begin
|
||
if t_count < 10 then t := 1 else t := (t_count div 10);
|
||
end;
|
||
|
||
for i := 0 to t - 1 do begin
|
||
Addr_Count := 0;
|
||
|
||
with GridAddressListTV.DataController do begin
|
||
addrList.Clear;
|
||
for j := 0 to 9 do begin
|
||
if ((i*10) + j) <= t_count then begin
|
||
// Values[(i*10) + j, GridAddressListTVColumn5.Index] := '';
|
||
addrList.Add(Values[(i*10) + j, GridAddressListTVColumn2.Index]);
|
||
end
|
||
else begin
|
||
Break;
|
||
end;
|
||
end;
|
||
|
||
if ClientSocket1.Active then begin
|
||
BuildReadPacketMulti(addrList);
|
||
|
||
Sent := ClientSocket1.Socket.SendBuf(TxBuf, 36);
|
||
|
||
if Sent <> 36 then begin
|
||
Memo1_ADD('<27><>Ŷ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> : [ '+Values[i, GridAddressListTVColumn2.Index]+' ]');
|
||
// Exit;
|
||
end
|
||
else begin
|
||
Recv := ClientSocket1.Socket.ReceiveBuf(RxBuf, 34);
|
||
if Recv < 34 then begin
|
||
Memo1_ADD('<27><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> : [ '+Values[i, GridAddressListTVColumn2.Index]+' ]');
|
||
// Exit;
|
||
end
|
||
else begin
|
||
|
||
ParseMultiReadResponse(RxBuf, addrList.Count, addr_Values);
|
||
|
||
// addr_Values <20>迭<EFBFBD><E8BFAD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
// ShowMessage('ù<><C3B9>° <20>ּҰ<D6BC>: ' + IntToStr(Values[0]));
|
||
|
||
for j := 0 to 9 do begin
|
||
if ((i*10) + j) <= t_count then begin
|
||
Values[(i*10) + j, GridAddressListTVColumn3.Index] := addr_Values[j].ToString;
|
||
Values[(i*10) + j, GridAddressListTVColumn4.Index] := addr_Values[j].ToHexString;
|
||
Values[(i*10) + j, GridAddressListTVColumn5.Index] := FormatDateTime('hhnnss.zzz',Now);
|
||
end
|
||
else begin
|
||
Break;
|
||
end;
|
||
end;
|
||
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
Sleep(StrToIntDef(Edit1.Text,0));
|
||
Refresh;
|
||
end;
|
||
except
|
||
on E: Exception do begin
|
||
Memo1_ADD('PLCDataLoadMulti Error : ' + E.Message);
|
||
end;
|
||
end;
|
||
|
||
finally
|
||
Result := True;
|
||
GridAddressListTV.DataController.EndUpdate;
|
||
Button1.Enabled := True;
|
||
addrList.Free;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.insertSQL(sql : string);
|
||
begin
|
||
try
|
||
if not FDConnection1.Connected then FDConnection1.Connected := True;
|
||
if FDConnection1.Connected then
|
||
begin
|
||
FDQuery1.Active := false;
|
||
FDQuery1.SQL.Clear;
|
||
FDQuery1.SQL.Add(sql);
|
||
FDQuery1.ExecSQL;
|
||
end;
|
||
|
||
except
|
||
on e : exception do
|
||
begin
|
||
Memo2_ADD('[insertSQL()]Database Connect Error :: '+e.Message);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.ParseMultiReadResponse(var RxBuf: array of Byte; AddrCount: Integer; out Values: TArray<Word>);
|
||
var
|
||
i, pos: Integer;
|
||
begin
|
||
SetLength(Values, AddrCount);
|
||
|
||
// <20>⺻ <20><><EFBFBD><EFBFBD> 20<32><30><EFBFBD><EFBFBD>Ʈ<EFBFBD><C6AE> <20>dzʶ<C7B3>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>ġ
|
||
pos := 20;
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD> üũ
|
||
if RxBuf[pos] <> $54 then
|
||
raise Exception.Create('Invalid response command code.');
|
||
|
||
pos := pos + 7;
|
||
// pos<6F><73> 27<32><37> <20><> (<28><>û <20><> Variable count <20><>ġ)
|
||
|
||
// Variable count Ȯ<><C8AE> (<28><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>)
|
||
if RxBuf[pos] <> AddrCount then
|
||
raise Exception.Create('Response variable count mismatch.');
|
||
|
||
pos := pos + 2; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><>ġ
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>б<EFBFBD> (WORD Ÿ<><C5B8>, 2<><32><EFBFBD><EFBFBD>Ʈ<EFBFBD><C6AE>)
|
||
for i := 0 to AddrCount - 1 do
|
||
begin
|
||
if pos + 1 > High(RxBuf) then
|
||
raise Exception.Create('Response packet too short.');
|
||
|
||
Values[i] := (RxBuf[pos] shl 8) or RxBuf[pos + 1];
|
||
Inc(pos, 2);
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Tmr_DBInsertTimer(Sender: TObject);
|
||
begin
|
||
Tmr_DBInsert.Enabled := False;
|
||
if LoadCK and (not close_ck) then begin
|
||
DB_Insert;
|
||
Tmr_DBInsert.Enabled := True;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Tmr_PLCReadTimer(Sender: TObject);
|
||
begin
|
||
Tmr_PLCRead.Enabled := False;
|
||
if not close_ck then begin
|
||
if RadioButton1.Checked then
|
||
Button1.Click
|
||
else
|
||
Button5.Click;
|
||
|
||
Tmr_PLCRead.Enabled := True;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.tmrExitTimer(Sender: TObject);
|
||
begin
|
||
case tmrExit.Tag of
|
||
0 : begin
|
||
tmrExit.Tag := tmrExit.Tag+1;
|
||
end;
|
||
1..4 : tmrExit.Tag := tmrExit.Tag+1;
|
||
5 : begin
|
||
tmrExit.Tag := tmrExit.Tag+1;
|
||
tmrExit.Enabled := false;
|
||
ExitOk := true;
|
||
Close;
|
||
end;
|
||
end;//case..
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.TrayIcon1Click(Sender: TObject);
|
||
begin
|
||
TrayIcon1.Visible := False;
|
||
Show;
|
||
WindowState := wsNormal;
|
||
Application.BringToFront;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button1Click(Sender: TObject);
|
||
begin
|
||
if (not ClientSocket1.Active) or (PLCconnectCK <> 2) then begin
|
||
PLC_connect;
|
||
end
|
||
else begin
|
||
// if PLCconnectCK = 2 then begin
|
||
if PLCDataLoad then begin
|
||
// LoadCK := True;
|
||
Memo1_ADD('['+FormatDateTime('yymmdd hhnnss.zzz',now)+'] PLCDataLoad - ok');
|
||
end
|
||
else begin
|
||
LoadCK := False;
|
||
end;
|
||
// end;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button2Click(Sender: TObject);
|
||
var
|
||
i : integer;
|
||
Ini: TIniFile;
|
||
begin
|
||
// ini <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>..
|
||
Ini := TIniFile.Create(inifilename);
|
||
try
|
||
Ini.WriteString('PLC', 'IP', Edit5.Text);
|
||
Ini.WriteString('PLC', 'PORT', Edit6.Text);
|
||
finally
|
||
Ini.Free;
|
||
end;
|
||
|
||
if FileExists(StringReplace(Application.ExeName,'.exe','_list1.ini',[rfReplaceAll])) then
|
||
RenameFile(StringReplace(Application.ExeName,'.exe','_list1.ini',[rfReplaceAll]),
|
||
StringReplace(Application.ExeName,'.exe','_list1_'+FormatDateTime('yymmdd_hhnnss.zzz',now)+'.ini.bak',[rfReplaceAll]));
|
||
|
||
if FileExists(StringReplace(Application.ExeName,'.exe','_list2.ini',[rfReplaceAll])) then
|
||
RenameFile(StringReplace(Application.ExeName,'.exe','_list2.ini',[rfReplaceAll]),
|
||
StringReplace(Application.ExeName,'.exe','_list2_'+FormatDateTime('yymmdd_hhnnss.zzz',now)+'.ini.bak',[rfReplaceAll]));
|
||
|
||
ListBox2.Clear;
|
||
ListBox1.Clear;
|
||
|
||
for i := 0 to GridAddressListTV.DataController.RecordCount - 1 do begin
|
||
with GridAddressListTV.DataController do begin
|
||
|
||
ListBox2.Items.Add(Values[i, GridAddressListTVColumn1.Index]);
|
||
ListBox1.Items.Add(Values[i, GridAddressListTVColumn2.Index]);
|
||
|
||
end;
|
||
end;
|
||
|
||
ListBox1.Items.SaveToFile(StringReplace(Application.ExeName,'.exe','_list1.ini',[rfReplaceAll]));
|
||
ListBox2.Items.SaveToFile(StringReplace(Application.ExeName,'.exe','_list2.ini',[rfReplaceAll]));
|
||
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button3Click(Sender: TObject);
|
||
var
|
||
SL: TStringList;
|
||
i, j: Integer;
|
||
Row: string;
|
||
begin
|
||
SL := TStringList.Create;
|
||
try
|
||
for i := 0 to GridAddressListTV.DataController.RecordCount - 1 do
|
||
begin
|
||
Row := '';
|
||
for j := 0 to GridAddressListTV.ColumnCount - 1 do
|
||
Row := Row + GridAddressListTV.DataController.DisplayTexts[i, j] + ',';
|
||
SL.Add(Copy(Row, 1, Length(Row) - 1)); // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> , <20><><EFBFBD><EFBFBD>
|
||
end;
|
||
SL.SaveToFile('.\grid.dat');
|
||
finally
|
||
SL.Free;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button4Click(Sender: TObject);
|
||
var
|
||
SL, Items: TStringList;
|
||
i, j: Integer;
|
||
begin
|
||
SL := TStringList.Create;
|
||
Items := TStringList.Create;
|
||
try
|
||
SL.LoadFromFile('.\grid.dat');
|
||
GridAddressListTV.BeginUpdate;
|
||
GridAddressListTV.DataController.RecordCount := SL.Count;
|
||
|
||
for i := 0 to SL.Count - 1 do
|
||
begin
|
||
Items.Delimiter := ','; // <20><EFBFBD><DEB8><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
Items.StrictDelimiter := True; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
Items.CommaText := SL[i];
|
||
for j := 0 to Min(Items.Count - 1, GridAddressListTV.ColumnCount - 1) do
|
||
begin
|
||
GridAddressListTV.DataController.Values[i, j] := Items[j];
|
||
end;
|
||
end;
|
||
|
||
GridAddressListTV.EndUpdate;
|
||
finally
|
||
Items.Free;
|
||
SL.Free;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button5Click(Sender: TObject); // <20>ϰ<EFBFBD>
|
||
begin
|
||
|
||
if not ClientSocket1.Active then PLC_connect;
|
||
|
||
if PLCDataLoadMulti then
|
||
Memo1_ADD('['+FormatDateTime('yymmdd hhnnss.zzz',now)+'] PLCDataLoadMulti - ok');
|
||
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button6Click(Sender: TObject);
|
||
begin
|
||
if Button11.Caption = 'Connect' then begin
|
||
PLC_connect;
|
||
end;
|
||
|
||
Button1.Enabled := False;
|
||
|
||
Tmr_PLCRead.Enabled := False;
|
||
|
||
Tmr_PLCRead.Interval := StrToIntDef(Edit2.Text,1000);
|
||
|
||
Tmr_PLCRead.Enabled := True;
|
||
|
||
|
||
Tmr_DBInsert.Enabled := False;
|
||
|
||
Tmr_DBInsert.Interval := StrToIntDef(Edit2.Text,1000);
|
||
|
||
Tmr_DBInsert.Enabled := True;
|
||
|
||
Button6.Enabled := False;
|
||
Button7.Enabled := True;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.Button7Click(Sender: TObject);
|
||
begin
|
||
Button1.Enabled := True;
|
||
Tmr_PLCRead.Enabled := False;
|
||
Button6.Enabled := True;
|
||
Button7.Enabled := False;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.CheckBox1Click(Sender: TObject);
|
||
var
|
||
Ini: TIniFile;
|
||
begin
|
||
// ini <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>..
|
||
Ini := TIniFile.Create(inifilename);
|
||
try
|
||
Ini.WriteString('START', 'AUTO', BoolToStr(CheckBox1.Checked));
|
||
finally
|
||
Ini.Free;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.ClientSocket1Connect(Sender: TObject;
|
||
Socket: TCustomWinSocket);
|
||
begin
|
||
Button11.Caption := 'DisConnect';
|
||
Memo1_ADD('['+FormatDateTime('yymmdd hhnnss.zzz',now)+'] PLC <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>!');
|
||
PLCconnectCK := 2; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.ClientSocket1Connecting(Sender: TObject;
|
||
Socket: TCustomWinSocket);
|
||
begin
|
||
Memo1_ADD('['+FormatDateTime('yymmdd hhnnss.zzz',now)+'] <20><><EFBFBD><EFBFBD> <20>õ<EFBFBD>.');
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.ClientSocket1Disconnect(Sender: TObject;
|
||
Socket: TCustomWinSocket);
|
||
begin
|
||
Memo1_ADD('['+FormatDateTime('yymmdd hhnnss.zzz',now)+'] <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>!');
|
||
Button11.Caption := 'Connect';
|
||
PLCconnectCK := 0; // <20>ʱⰪ : 0 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
LoadCK := False;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.ClientSocket1Error(Sender: TObject;
|
||
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
|
||
begin
|
||
Memo1_ADD('['+FormatDateTime('yymmdd hhnnss.zzz',now)+'] PLC <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>: ' + IntToStr(ErrorCode));
|
||
PLCconnectCK := 0; // <20>ʱⰪ : 0 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
ErrorCode := 0; // <20><><EFBFBD><EFBFBD> <20><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
LoadCK := False;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.ClientSocket1Read(Sender: TObject;
|
||
Socket: TCustomWinSocket);
|
||
var
|
||
Buf: array[0..1023] of Byte;
|
||
i, Len: Integer;
|
||
RawData: TBytes;
|
||
BufCopy: TBytes;
|
||
S: string;
|
||
Recv: Integer;
|
||
begin
|
||
try
|
||
Len := Socket.ReceiveBuf(Buf, SizeOf(Buf));
|
||
if Len > 0 then begin
|
||
SetLength(RawData, Len);
|
||
Move(Buf, RawData[0], Len);
|
||
|
||
// 1. Memo1<6F><31> <20><><EFBFBD><EFBFBD> ǥ<><C7A5>
|
||
S := '';
|
||
for i := 0 to Len - 1 do S := S + IntToHex(RawData[i], 2) + ' ';
|
||
|
||
if cb_logview.Checked then begin
|
||
Memo2_ADD('>> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> (HEX)');
|
||
Memo2_ADD(S);
|
||
end;
|
||
end;
|
||
except
|
||
on E: Exception do begin
|
||
Memo1_ADD('Read error: ' + E.Message);
|
||
LoadCK := False;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.AppEvtMinimize(Sender: TObject);
|
||
begin
|
||
Hide;
|
||
WindowState := wsMinimized;
|
||
|
||
TrayIcon1.Visible := True;
|
||
TrayIcon1.Animate := True;
|
||
TrayIcon1.ShowBalloonHint;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.FormClose(Sender: TObject; var Action: TCloseAction);
|
||
begin
|
||
close_ck := true;
|
||
Tmr_DBInsert.Enabled := False;
|
||
Tmr_PLCRead.Enabled := False;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
||
begin
|
||
if ExitOk then begin
|
||
CanClose := true;
|
||
end else begin
|
||
CanClose := false;
|
||
case MessageBox(handle, '"<22><>(Y)"<22><> Ŭ<><C5AC><EFBFBD>ϸ<EFBFBD>, LSPLC Agent01<30><31> Ʈ<><C6AE><EFBFBD>̷<EFBFBD> <20>ּ<EFBFBD>ȭ <20>˴ϴ<CBB4>.'+#$0D#$0A#$0D#$0A+'"<22>ƴϿ<C6B4>(N)"<22><> Ŭ<><C5AC><EFBFBD>ϸ<EFBFBD> <20><><EFBFBD>α<CEB1><D7B7><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>. ', 'LSPLC Agent01 :: Ʈ<><C6AE><EFBFBD>̷<EFBFBD> <20>ּ<EFBFBD>ȭ <20>ұ<EFBFBD><D2B1><EFBFBD>?', MB_YESNOCANCEL) of
|
||
ID_YES : begin
|
||
TrayIcon1.BalloonTitle := 'LSPLC Agent ȭ<><C8AD><EFBFBD><EFBFBD> <20>ٽ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>...';
|
||
TrayIcon1.BalloonHint := 'Ʈ<><C6AE><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>Ŭ<EFBFBD><C5AC><EFBFBD>ϼ<EFBFBD><CFBC><EFBFBD>.';
|
||
//TrayIcon1.BalloonFlags := bfInfo;
|
||
AppEvtMinimize(self);
|
||
end;
|
||
ID_NO : begin
|
||
//<2F><><EFBFBD><EFBFBD> Ÿ<≯<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϰ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ExitOk~
|
||
tmrExit.Tag := 0;
|
||
tmrExit.Enabled := true;
|
||
end;
|
||
end;//case..
|
||
end;//if~
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.FormCreate(Sender: TObject);
|
||
var
|
||
i, j : Integer;
|
||
Sections: TStringList;
|
||
|
||
CK_ROW : integer;
|
||
Ini: TIniFile;
|
||
begin
|
||
close_ck := False;
|
||
LoadCK := False;
|
||
PLCconnectCK := 0;
|
||
|
||
inifilename := StringReplace(Application.ExeName,'.exe','.ini',[rfReplaceAll]);
|
||
|
||
Ini := TIniFile.Create(inifilename);
|
||
try
|
||
Edit5.Text := Ini.ReadString('PLC', 'IP', '192.168.100.11');
|
||
Edit6.Text := Ini.ReadString('PLC', 'PORT', '2004');
|
||
|
||
CheckBox1.Checked := Ini.ReadBool('START', 'AUTO', False);
|
||
|
||
dbIP_PG := Ini.ReadString('DATABASE_PG','IP','192.168.200.199');
|
||
dbUser_PG := Ini.ReadString('DATABASE_PG','User','root');
|
||
dbPW_PG := Ini.ReadString('DATABASE_PG','PW','54323');
|
||
dbSchema_PG := Ini.ReadString('DATABASE_PG','Schema','neol');
|
||
dbPort_PG := Ini.ReadInteger('DATABASE_PG','Port',3306);
|
||
finally
|
||
Ini.Free;
|
||
end;
|
||
|
||
if FileExists(StringReplace(Application.ExeName,'.exe','_list1.ini',[rfReplaceAll])) then
|
||
ListBox1.Items.LoadFromFile(StringReplace(Application.ExeName,'.exe','_list1.ini',[rfReplaceAll]));
|
||
|
||
if FileExists(StringReplace(Application.ExeName,'.exe','_list2.ini',[rfReplaceAll])) then
|
||
ListBox2.Items.LoadFromFile(StringReplace(Application.ExeName,'.exe','_list2.ini',[rfReplaceAll]));
|
||
|
||
try
|
||
GridAddressListTV.DataController.BeginUpdate;
|
||
|
||
GridAddressListTV.DataController.RecordCount:=0;
|
||
|
||
finally
|
||
GridAddressListTV.DataController.EndUpdate;
|
||
end;
|
||
|
||
try
|
||
GridAddressListTV.DataController.BeginUpdate;
|
||
|
||
for i := 0 to ListBox1.Items.Count - 1 do begin
|
||
CK_ROW := 0;
|
||
with GridAddressListTV.DataController do begin
|
||
|
||
CK_ROW := AppendRecord;
|
||
|
||
Values[CK_ROW, GridAddressListTVColumn1.Index] := ListBox2.Items[i];
|
||
Values[CK_ROW, GridAddressListTVColumn2.Index] := ListBox1.Items[i];
|
||
Values[CK_ROW, GridAddressListTVColumn3.Index] := '';
|
||
Values[CK_ROW, GridAddressListTVColumn4.Index] := '';
|
||
Values[CK_ROW, GridAddressListTVColumn5.Index] := '';
|
||
end;
|
||
end;
|
||
|
||
finally
|
||
GridAddressListTV.DataController.EndUpdate;
|
||
end;
|
||
|
||
|
||
// PG <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
if not DB_Connect then begin
|
||
Memo1_ADD('['+FormatDateTime('yymmdd hhnnss.zzz',now)+'] Database Connect Error :: PG');
|
||
end;
|
||
|
||
// <20>ڵ<EFBFBD> <20>α<EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
if CheckBox1.Checked then Button6.Click;
|
||
end;
|
||
|
||
function TF_LSPLCAgent.DB_Connect:Boolean;
|
||
begin
|
||
FDPhysPgDriverLink1.VendorLib := '.\libpq-10.dll';
|
||
|
||
FDConnection1.Connected := False;
|
||
|
||
FDConnection1.Params.Clear;
|
||
FDConnection1.Params.DriverID := 'PG';
|
||
FDConnection1.Params.Database := Trim(dbSchema_PG);
|
||
FDConnection1.Params.UserName := Trim(dbUser_PG);
|
||
FDConnection1.Params.Password := Trim(dbPW_PG);
|
||
FDConnection1.Params.Add('Server=' + Trim(dbIP_PG));
|
||
FDConnection1.Params.Add('Port=' + Trim(IntToStr(dbPort_PG)));
|
||
FDConnection1.Params.Add('CharacterSet=UTF8');
|
||
|
||
try
|
||
FDConnection1.Connected := True;
|
||
|
||
DB_MainTableCreate;
|
||
|
||
DB_MainTableDescriptCreate;
|
||
|
||
DB_LogTableCreate;
|
||
|
||
Result := True;
|
||
// Memo1_ADD('<27><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>');
|
||
// Memo1_ADD('"Q1" <20><>ư<EFBFBD><C6B0> <20><><EFBFBD><EFBFBD> <20><> "<22><><EFBFBD><EFBFBD>" <20><>ư<EFBFBD><C6B0> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>!!');
|
||
|
||
except
|
||
on E: Exception do begin
|
||
Result := False;
|
||
// Memo1_ADD('<27><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>: ' + E.Message);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure TF_LSPLCAgent.DB_Insert;
|
||
var
|
||
sql, update_sql : string;
|
||
i : Integer;
|
||
ck_data : Float64;
|
||
begin
|
||
try
|
||
sql := '';
|
||
sql := 'INSERT INTO "plc_'+format('%.3d',[PLC_NO])+'" '+
|
||
' ( "Idx","CreateDT","plcID"' ;
|
||
for i := 0 to GridAddressListTV.DataController.RecordCount - 1 do begin
|
||
sql := sql +', "data'+format('%.2d',[i+1])+'"';
|
||
end;
|
||
sql := sql + ' ) ';
|
||
|
||
sql := sql + ' VALUES (DEFAULT, '+
|
||
'now(),'+
|
||
inttostr(PLC_NO) ;
|
||
|
||
update_sql := '';
|
||
update_sql := 'UPDATE "plc_Data" SET ';
|
||
|
||
for i := 0 to GridAddressListTV.DataController.RecordCount - 1 do begin
|
||
if Trim(GridAddressListTV.DataController.Values[i, GridAddressListTVColumn3.Index]) <> '' then begin
|
||
sql := sql +','+ floattostr(GridAddressListTV.DataController.Values[i, GridAddressListTVColumn3.Index]);
|
||
update_sql := update_sql +
|
||
'"data'+format('%.2d',[i+1])+'" = '+floattostr(GridAddressListTV.DataController.Values[i, GridAddressListTVColumn3.Index])+' , ';
|
||
end
|
||
else begin
|
||
sql := sql +','+ '0.0';
|
||
update_sql := update_sql +
|
||
'"data'+format('%.2d',[i+1])+'" = 0.0 , ';
|
||
end;
|
||
end;
|
||
|
||
sql := sql + ' ) ';
|
||
|
||
update_sql := update_sql +
|
||
'"UpdateDT"=now()'+
|
||
' WHERE "plcID"='+inttostr(PLC_NO);
|
||
|
||
insertSQL(update_sql);
|
||
|
||
insertSQL(sql);
|
||
|
||
except
|
||
on E: Exception do begin
|
||
Memo1_ADD('DB_Insert Error : ' + E.Message);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function TF_LSPLCAgent.DB_Close:Boolean;
|
||
begin
|
||
try
|
||
if FDConnection1.Connected then FDConnection1.Connected := False;
|
||
Result := True;
|
||
except
|
||
Result := False;
|
||
end;
|
||
end;
|
||
|
||
function TF_LSPLCAgent.DB_MainTableCreate:Boolean;
|
||
var
|
||
sql : string;
|
||
i : integer;
|
||
begin
|
||
try
|
||
sql := 'CREATE TABLE IF NOT EXISTS "plc_Data" ('+
|
||
'"Idx" SERIAL NOT NULL,'+
|
||
'"UpdateDT" TIMESTAMP ,'+
|
||
'"plcID" INTEGER ,'+
|
||
'"data01" REAL ,'+
|
||
'"data02" REAL ,'+
|
||
'"data03" REAL ,'+
|
||
'"data04" REAL ,'+
|
||
'"data05" REAL ,'+
|
||
'"data06" REAL ,'+
|
||
'"data07" REAL ,'+
|
||
'"data08" REAL ,'+
|
||
'"data09" REAL ,'+
|
||
'"data10" REAL ,'+
|
||
'"data11" REAL ,'+
|
||
'"data12" REAL ,'+
|
||
'"data13" REAL ,'+
|
||
'"data14" REAL ,'+
|
||
'"data15" REAL ,'+
|
||
'"data16" REAL ,'+
|
||
'"data17" REAL ,'+
|
||
'"data18" REAL ,'+
|
||
'"data19" REAL ,'+
|
||
'"data20" REAL ,'+
|
||
'"data21" REAL ,'+
|
||
'"data22" REAL ,'+
|
||
'"data23" REAL ,'+
|
||
'"data24" REAL ,'+
|
||
'"data25" REAL ,'+
|
||
'"data26" REAL ,'+
|
||
'"data27" REAL ,'+
|
||
'"data28" REAL ,'+
|
||
'"data29" REAL ,'+
|
||
'"data30" REAL ,'+
|
||
'CONSTRAINT plc_Data_pkey PRIMARY KEY ("Idx"));';
|
||
|
||
insertSQL(sql);
|
||
|
||
if not FDConnection1.Connected then FDConnection1.Connected := True;
|
||
if FDConnection1.Connected then
|
||
begin
|
||
FDQuery2.Close;
|
||
FDQuery2.SQL.Clear;
|
||
FDQuery2.SQL.Text := 'SELECT * FROM "plc_Data" WHERE "plcID"='+IntToStr(PLC_NO);
|
||
FDQuery2.Open;
|
||
|
||
if FDQuery2.RecordCount <= 0 then
|
||
begin
|
||
sql := '';
|
||
sql := 'INSERT INTO "plc_Data" ' +
|
||
'VALUES (DEFAULT, '+
|
||
'now(), '+
|
||
inttostr(PLC_NO)+ ' ' ;
|
||
for i := 0 to 29 do begin
|
||
sql := sql +','+ '0.0';
|
||
end;
|
||
sql := sql + ' ) ';
|
||
|
||
insertSQL(sql);
|
||
end;
|
||
end;
|
||
except
|
||
on E: Exception do begin
|
||
Memo1_ADD('DB_MainTableCreate Error : ' + E.Message);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function TF_LSPLCAgent.DB_MainTableDescriptCreate:Boolean;
|
||
var
|
||
sql : string;
|
||
i : integer;
|
||
begin
|
||
try
|
||
sql := 'CREATE TABLE IF NOT EXISTS "plc_Data_Desc" ('+
|
||
'"Idx" SERIAL NOT NULL,'+
|
||
'"UpdateDT" TIMESTAMP ,'+
|
||
'"plcID" INTEGER ,'+
|
||
'"fieldname" VARCHAR(50) ,'+
|
||
'"address" VARCHAR(50) ,'+
|
||
'"addressDescription" VARCHAR(200) ,'+
|
||
'CONSTRAINT plc_Data_Desc_pkey PRIMARY KEY ("Idx"));';
|
||
|
||
insertSQL(sql);
|
||
|
||
{
|
||
if not FDConnection1.Connected then FDConnection1.Connected := True;
|
||
if FDConnection1.Connected then
|
||
begin
|
||
FDQuery2.Close;
|
||
FDQuery2.SQL.Clear;
|
||
FDQuery2.SQL.Text := ' SELECT * FROM "plc_Data_Desc" '+
|
||
' WHERE "plcID" = '+IntToStr(PLC_NO)+
|
||
' and "address" = '''+'*****'+'''';
|
||
FDQuery2.Open;
|
||
|
||
if FDQuery2.RecordCount <= 0 then
|
||
begin
|
||
sql := '';
|
||
sql := 'INSERT INTO "plc_Data_Desc" ' +
|
||
'VALUES (DEFAULT, '+
|
||
'now(), '+
|
||
inttostr(PLC_NO)+
|
||
' '''+'*****'+''', '+
|
||
' '''+'*****'+''', '+
|
||
' '''+'*****'+'''); ';
|
||
|
||
insertSQL(sql);
|
||
end;
|
||
end;
|
||
}
|
||
except
|
||
on E: Exception do begin
|
||
Memo1_ADD('DB_MainTableDescriptCreate Error : ' + E.Message);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function TF_LSPLCAgent.DB_LogTableCreate:Boolean;
|
||
var
|
||
sql : string;
|
||
begin
|
||
try
|
||
sql := 'CREATE TABLE IF NOT EXISTS "plc_'+format('%.3d',[PLC_NO])+'" ('+
|
||
'"Idx" SERIAL NOT NULL,'+
|
||
'"CreateDT" TIMESTAMP ,'+
|
||
'"plcID" INTEGER ,'+
|
||
'"data01" REAL ,'+
|
||
'"data02" REAL ,'+
|
||
'"data03" REAL ,'+
|
||
'"data04" REAL ,'+
|
||
'"data05" REAL ,'+
|
||
'"data06" REAL ,'+
|
||
'"data07" REAL ,'+
|
||
'"data08" REAL ,'+
|
||
'"data09" REAL ,'+
|
||
'"data10" REAL ,'+
|
||
'"data11" REAL ,'+
|
||
'"data12" REAL ,'+
|
||
'"data13" REAL ,'+
|
||
'"data14" REAL ,'+
|
||
'"data15" REAL ,'+
|
||
'"data16" REAL ,'+
|
||
'"data17" REAL ,'+
|
||
'"data18" REAL ,'+
|
||
'"data19" REAL ,'+
|
||
'"data20" REAL ,'+
|
||
'"data21" REAL ,'+
|
||
'"data22" REAL ,'+
|
||
'"data23" REAL ,'+
|
||
'"data24" REAL ,'+
|
||
'"data25" REAL ,'+
|
||
'"data26" REAL ,'+
|
||
'"data27" REAL ,'+
|
||
'"data28" REAL ,'+
|
||
'"data29" REAL ,'+
|
||
'"data30" REAL ,'+
|
||
'CONSTRAINT plc_'+format('%.3d',[PLC_NO])+'_pkey PRIMARY KEY ("Idx"));';
|
||
|
||
insertSQL(sql);
|
||
except
|
||
on E: Exception do begin
|
||
Memo1_ADD('DB_LogTableCreate Error : ' + E.Message);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
end.
|