unit uPLCDevice; interface uses System.Win.ScktComp,uSafeLog,uErrorInfo,System.SysUtils,FMX.Types, System.StrUtils,System.Classes,WinSock,WinSock2,Winapi.Windows; type TPlcWorkEvent = procedure(Sender: TObject;sValue:string) of object; TPlcOpenEvent = procedure(Sender: TObject;sValue:string) of object; //500000FFFF0300 0C00 010001040000 7A0000 A8 0100 //500000FFFF0300 0e00 010001140000 7A0000 A8 0100 c9 00 TMelsec=class //三棱plc private bIsDestory:boolean; FTimer: TTimer; FOnWork:TPlcWorkEvent; FOnShowState:TPlcWorkEvent; FOnOpen: TPlcOpenEvent; FSocket:TClientSocket; FNum:Integer; //编号 procedure TimerTimer(Sender: TObject); //判断连接状态,断开的话,自动重连 procedure SocketRead(Sender: TObject; Socket: TCustomWinSocket); //读缓存数据 procedure SocketError(Sender: TObject; Socket: TCustomWinSocket; //出错代码处理 ErrorEvent: TErrorEvent; var ErrorCode: Integer); procedure SocketConnect(Sender: TObject; Socket: TCustomWinSocket); procedure SocketDisConnect(Sender: TObject; Socket: TCustomWinSocket); procedure SocketConnecting(Sender: TObject; Socket: TCustomWinSocket); public property Num:Integer read FNum write FNum; constructor Create(sIp:string;iPort:integer); destructor Destroy; override; procedure Close(); procedure Connect(); procedure DisConnect(); procedure Open(Sender: TObject;sValue: string); property OnWork: TPlcWorkEvent read FOnWork write FOnWork;//业务处理事件 property OnShowState:TPlcWorkEvent read FOnShowState write FOnShowState;//显示连接断开提示 property OnOpen: TPlcOpenEvent read FOnOpen write FOnOpen;//提交协议plc处理事件 end; TOmron=class //欧姆龙plc private bIsDestory:boolean; FTimer: TTimer; FOnWork:TPlcWorkEvent; FOnShowState:TPlcWorkEvent; FOnOpen: TPlcOpenEvent; FSocket:TClientSocket; FNum:Integer; //编号 procedure TimerTimer(Sender: TObject); //判断连接状态,断开的话,自动重连 procedure SocketRead(Sender: TObject; Socket: TCustomWinSocket); //读缓存数据 procedure SocketError(Sender: TObject; Socket: TCustomWinSocket; //出错代码处理 ErrorEvent: TErrorEvent; var ErrorCode: Integer); procedure SocketConnect(Sender: TObject; Socket: TCustomWinSocket); procedure SocketDisConnect(Sender: TObject; Socket: TCustomWinSocket); procedure SocketConnecting(Sender: TObject; Socket: TCustomWinSocket); public property Num:Integer read FNum write FNum; constructor Create(sIp:string;iPort:integer); destructor Destroy; override; procedure Open(); procedure Close(); procedure Connect(); procedure DisConnect(); property OnWork: TPlcWorkEvent read FOnWork write FOnWork;//业务处理事件 property OnShowState:TPlcWorkEvent read FOnShowState write FOnShowState;//显示连接断开提示 property OnOpen: TPlcOpenEvent read FOnOpen write FOnOpen;//提交协议plc处理事件 end; implementation { TMelsec } /////////////////////////////////////////////////////////////////////////////// constructor TMelsec.Create(sIp:string;iPort:integer); //参数固定 begin inherited Create; bIsDestory:=false; FNum:=1; WorkLog.Debug('创建Melsec读头'); FSocket:=TClientSocket.Create(nil); FSocket.OnRead:=SocketRead; FSocket.OnError:=SocketError; FSocket.OnConnect:=SocketConnect; FSocket.OnDisconnect:=SocketDisConnect; FSocket.OnConnecting:=SocketConnecting; FSocket.Address:=sIp; FSocket.Port:=iPort; FTimer:=TTimer.Create(nil); FTimer.OnTimer:=TimerTimer; FTimer.Enabled:=false; end; procedure TMelsec.Connect(); var tmp:string; begin tmp:=Format('连接Melsec读头%d:%s,%d',[FNum,FSocket.Address,FSocket.Port]); WorkLog.Debug(tmp); if Assigned(FOnShowState) then FOnShowState(self,tmp); FSocket.Active:=true; end; procedure TMelsec.DisConnect(); begin FSocket.Close; FTimer.Enabled:=false; end; procedure TMelsec.SocketConnecting(Sender: TObject; Socket: TCustomWinSocket); begin WorkLog.Debug(Format('Melsec读头%d连接中...',[FNum])); if Assigned(FOnShowState) then FOnShowState(self,Format('Melsec读头%d连接中...',[FNum])); end; destructor TMelsec.Destroy; begin bIsDestory:=true; FSocket.Close; FTimer.Enabled:=false; WorkLog.Debug('销毁Melsec读头'); FSocket.Free; FTimer.Free; inherited Destroy; end; procedure TMelsec.SocketDisConnect(Sender: TObject; Socket: TCustomWinSocket); begin WorkLog.Debug(Format('Melsec读头%d连接断开',[FNum])); if Assigned(FOnShowState) then FOnShowState(self,Format('Melsec读头%d连接断开',[FNum])); end; //procedure TMelsec.SetKeepAlive(Socket: TCustomWinSocket); //var // opt:DWORD; // klive, outKlive: TTCP_KEEPALIVE; // i,j:integer; // // OptVal: DWORD; //begin // opt := 1; // if setsockopt(Socket.SocketHandle,SOL_SOCKET, SO_KEEPALIVE, PAnsiChar(@opt), SizeOf(opt)) = SOCKET_ERROR then // begin // //showInfo(Format('WinSock Error %d', [WSAGetLastError()])); // end; // klive.onoff := 1; // klive.keepalivetime := 5000; // klive.keepaliveinterval := 1; // // if WSAIoctl(Socket.SocketHandle, SIO_KEEPALIVE_VALS, PAnsiChar(@klive), // SizeOf(TTCP_KEEPALIVE), PAnsiChar(@outKlive), // SizeOf(TTCP_KEEPALIVE), opt,0,nil) = SOCKET_ERROR then // begin // //showInfo(Format('WinSock Error %d', [WSAGetLastError()])); // end; // //end; procedure TMelsec.TimerTimer(Sender: TObject); begin if FTimer.Enabled then begin FTimer.Enabled:=false; FSocket.Close; FSocket.Open; end; end; procedure TMelsec.SocketRead(Sender: TObject; Socket: TCustomWinSocket); var buf:array[0..1024] of byte; sHexString:string; i:Integer; sData:string; function ByteToString(const Value:PByte;iLen:Integer): String; var I: integer; S : String; begin S := ''; for I := 0 to iLen-1 do begin if Value[i]=$0D then break; S := S+Chr(Value[I]); end; Result := S; end; begin if bIsDestory then Exit; sHexString:=''; for i:= 0 to Socket.ReceiveBuf(buf,1024) do begin sHexString:=sHexString+buf[i].ToHexString; end; WorkLog.Debug('Melsec读头%d读到数据:%s',[FNum,sHexString]); sData:=ByteToString(@buf[0],Length(sHexString)div 2); if Assigned(FOnWork) then FOnWork(self,sData); end; procedure TMelsec.SocketError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); var tmpError:string; begin if (ErrorCode=10060) OR (ErrorCode=10065) then begin tmpError:=Format('Melsec读头%d连接出错(%d):%s',[FNum,ErrorCode,SysErrorMessage(ErrorCode)]); if Assigned(FOnShowState) then FOnShowState(self,Format('Melsec读头%d连接失败,重新连接',[FNum])); end else begin tmpError:=Format('Melsec读头%d连接出错(%d):%s',[FNum,ErrorCode,SysErrorMessage(ErrorCode)]); if Assigned(FOnShowState) then FOnShowState(self,'Melsec读头连接出错:'+SysErrorMessage(ErrorCode)); end; workLog.Error(tmpError); ErrorCode:=0; if bIsDestory then Exit; FTimer.Enabled:=true; end; procedure TMelsec.SocketConnect(Sender: TObject; Socket: TCustomWinSocket); begin WorkLog.Debug(Format('Melsec读头%d连接成功',[FNum])); if Assigned(FOnShowState) then FOnShowState(self, Format('Melsec读头%d连接成功',[FNum])); end; procedure TMelsec.Close; //4C4F46460D VAR bBtye:array[0..4] of byte; begin bBtye[0]:=$4C;bBtye[1]:=$4F;bBtye[2]:=$46; bBtye[3]:=$46;bBtye[4]:=$0D; fSocket.Socket.SendBuf(bBtye[0],5); WorkLog.Debug(Format('Melsec读头%d关闭',[FNum])); end; { * 50 00 00 FF FF 03 00 0C 00 10 00 01 04 00 00 64 00 00 A8 14 0050 00 00 FF FF 03 00 0C 00 10 00 01 04 00 00 64 00 00 A8 14 00 * * 电脑读命令: 50 00(命令) :表示发起指令,固定50 00; 00(网路编号) :上位访问下位,固定00; FF(PLC编号) : 上位访问下位,固定FF; FF 03(请求目标模块IO编号) : 值要从小到大看,也就是反过来看,三菱所有的协值都是这样,所以这里是03FF,十进制是1023; 也是固定的; 00(请求目标模块站编号) : 上位访问下位,固定00; 0C 00 (应答数据物理长度): 也要反过来,值是000C,也就是12;表示后面的报文内容的长度是12(手工数一下,后面报文长度真的是12) 10 00 (cpu监视定时器) : 表示等待PLC响应的timeout时间;这里 值是0010,十进制是16 ;相当与最大等待时间250ms*16=4秒;实际上PLC一般2,3个毫秒内就响应了; 01 04 (命令) : 值是0401(所有值都要反过来看,再说就啰嗦了,后面不说了);表示批量读取;如果是1401就是随机读取; 00 00 (子命令) : 值是0表示按字读取(1个字=16位),如果值是1就按位读取; 64 00 00(首地址):地址因为跨度比较大,所以用了3个字节;这里的值是000064,十进制就是100 A8 (软元件) : 表示读取PLC寄存器的类型: 这里的A8表示D点;其他常见的有: 90-M点;9C-X点;9D-Y点;B0-ZR外部存储卡 14 00(读取长度) :值是0014,十进制就是20; } procedure TMelsec.Open(Sender: TObject;sValue: string); //4C4F4E0d VAR bBtye:array[0..4] of byte; begin if bIsDestory then Exit; bBtye[0]:=$4C;bBtye[1]:=$4F;bBtye[2]:=$4E; bBtye[3]:=$0D; fSocket.Socket.SendBuf(bBtye[0],4); WorkLog.Debug(Format('Melsec读头%d打开',[FNum])); end; { TOmron } /////////////////////////////////////////////////////////////////////////////// constructor TOmron.Create(sIp:string;iPort:integer); //参数固定 begin inherited Create; bIsDestory:=false; FNum:=1; WorkLog.Debug('创建Omron_plc读头'); FSocket:=TClientSocket.Create(nil); FSocket.OnRead:=SocketRead; FSocket.OnError:=SocketError; FSocket.OnConnect:=SocketConnect; FSocket.OnDisconnect:=SocketDisConnect; FSocket.OnConnecting:=SocketConnecting; FSocket.Address:=sIp; FSocket.Port:=iPort; FTimer:=TTimer.Create(nil); FTimer.OnTimer:=TimerTimer; FTimer.Enabled:=false; end; procedure TOmron.Connect(); var tmp:string; begin tmp:=Format('连接Omron_plc读头%d:%s,%d',[FNum,FSocket.Address,FSocket.Port]); WorkLog.Debug(tmp); if Assigned(FOnShowState) then FOnShowState(self, tmp); FSocket.Active:=true; end; procedure TOmron.DisConnect(); begin FSocket.Close; FTimer.Enabled:=false; end; procedure TOmron.SocketConnecting(Sender: TObject; Socket: TCustomWinSocket); begin WorkLog.Debug(Format('Omron_plc读头%d连接中...',[FNum])); if Assigned(FOnShowState) then FOnShowState(self,Format('Omron_plc读头%d连接中...',[FNum])); end; destructor TOmron.Destroy; begin bIsDestory:=true; FSocket.Close; FTimer.Enabled:=false; WorkLog.Debug('销毁Omron_plc读头'); FSocket.Free; FTimer.Free; inherited Destroy; end; procedure TOmron.SocketDisConnect(Sender: TObject; Socket: TCustomWinSocket); begin WorkLog.Debug(Format('Omron_plc读头%d连接断开',[FNum])); if Assigned(FOnShowState) then FOnShowState(self, Format('Omron_plc读头%d连接断开',[FNum])); end; procedure TOmron.TimerTimer(Sender: TObject); begin if FTimer.Enabled then begin FTimer.Enabled:=false; FSocket.Close; FSocket.Open; end; end; procedure TOmron.SocketRead(Sender: TObject; Socket: TCustomWinSocket); var buf:array[0..1024] of byte; sHexString:string; i:Integer; sData:string; function ByteToString(const Value:PByte;iLen:Integer): String; var I: integer; S : String; begin S := ''; for I := 0 to iLen-1 do begin if Value[i]=$0D then break; S := S+Chr(Value[I]); end; Result := S; end; begin if bIsDestory then Exit; sHexString:=''; for i:= 0 to Socket.ReceiveBuf(buf,1024) do begin sHexString:=sHexString+buf[i].ToHexString; end; WorkLog.Debug('Omron_plc读头%d读到数据:%s',[FNum,sHexString]); sData:=ByteToString(@buf[0],Length(sHexString)div 2); if Assigned(FOnWork) then FOnWork(self, sData); end; procedure TOmron.SocketError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); var tmpError:string; begin if (ErrorCode=10060) OR (ErrorCode=10065) then begin tmpError:=Format('Omron_plc读头%d连接出错(%d):%s',[FNum,ErrorCode,SysErrorMessage(ErrorCode)]); if Assigned(FOnShowState) then FOnShowState(self, Format('Omron_plc读头%d连接失败,重新连接',[FNum])); end else begin tmpError:=Format('Omron_plc读头%d连接出错(%d):%s',[FNum,ErrorCode,SysErrorMessage(ErrorCode)]); if Assigned(FOnShowState) then FOnShowState(self, 'Omron_plc读头连接出错:'+SysErrorMessage(ErrorCode)); end; workLog.Error(tmpError); ErrorCode:=0; if bIsDestory then Exit; FTimer.Enabled:=true; end; procedure TOmron.SocketConnect(Sender: TObject; Socket: TCustomWinSocket); begin WorkLog.Debug(Format('Omron_plc读头%d连接成功',[FNum])); if Assigned(FOnShowState) then FOnShowState(self, Format('Omron_plc读头%d连接成功',[FNum])); end; procedure TOmron.Close; //4C4F46460D Var bBtye:array[0..4] of byte; begin bBtye[0]:=$4C;bBtye[1]:=$4F;bBtye[2]:=$46; bBtye[3]:=$46;bBtye[4]:=$0D; fSocket.Socket.SendBuf(bBtye[0],5); WorkLog.Debug(Format('Omron_plc读头%d关闭',[FNum])); end; {* * 头标识 46494E53 即为ASCII码:FINS 长度 0000000C 后续字节长度=12 命令码 00000000 为0 错误代码 00000000 为0 客户端节点地址 00000000 to 000000FE 0到254,为0服务端会自动分配节点号 * * 服务端接收到连接请求后,返回帧格式如下: 名称 内容 说明 头标识 46494E53 ASCII:FINS 长度 00000010 从命令码开始的数据长度 命令码 00000001 固定值00000001 错误码 4个字节错误信息 参考错误信息码表 客户端节点地址 00000001 to 000000FE 1到254 服务端节点地址 00000001 to 000000FE 1到254 46 49 4E 53 00 00 00 10 00 00 00 01 00 00 00 00 00 00 00 71 00 00 00 0A *} procedure TOmron.Open; //4C4F4E0d VAR bBtye:array[0..4] of byte; begin if bIsDestory then Exit; bBtye[0]:=$4C;bBtye[1]:=$4F;bBtye[2]:=$4E; bBtye[3]:=$0D; fSocket.Socket.SendBuf(bBtye[0],4); WorkLog.Debug(Format('Omron_plc读头%d打开',[FNum])); end; end.