From 2c18c622022b44824283fc6da09203cfdd755ff3 Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 29 Nov 2017 19:22:49 +0800 Subject: [PATCH] add Panasonic PLC Driver --- SCADA/Program/DataExchange.sln | 22 + SCADA/Program/DataService/ExtensionMethods.cs | 73 ++ SCADA/Program/ModbusDriver/ModbusTCPDriver.cs | 3 +- .../PanasonicDriver/PanasonicDriver.csproj | 53 ++ .../PanasonicDriver/PanasonicSerialReader.cs | 708 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 + SCADA/dll/PanasonicDriver.dll | Bin 0 -> 17408 bytes 7 files changed, 893 insertions(+), 2 deletions(-) create mode 100644 SCADA/Program/PanasonicDriver/PanasonicDriver.csproj create mode 100644 SCADA/Program/PanasonicDriver/PanasonicSerialReader.cs create mode 100644 SCADA/Program/PanasonicDriver/Properties/AssemblyInfo.cs create mode 100644 SCADA/dll/PanasonicDriver.dll diff --git a/SCADA/Program/DataExchange.sln b/SCADA/Program/DataExchange.sln index 196a3f4..492bf4f 100644 --- a/SCADA/Program/DataExchange.sln +++ b/SCADA/Program/DataExchange.sln @@ -31,6 +31,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SiemensPLCDriver", "Siemens EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagConfig", "TagConfig\TagConfig\TagConfig.csproj", "{18F86945-9CB9-4149-A09C-85B8C5C7C4ED}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PanasonicDriver", "PanasonicDriver\PanasonicDriver.csproj", "{0D1C943D-594F-4E07-AA39-90CEC4E37C8E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -277,6 +279,26 @@ Global {18F86945-9CB9-4149-A09C-85B8C5C7C4ED}.Release|x64.ActiveCfg = Release|x86 {18F86945-9CB9-4149-A09C-85B8C5C7C4ED}.Release|x86.ActiveCfg = Release|x86 {18F86945-9CB9-4149-A09C-85B8C5C7C4ED}.Release|x86.Build.0 = Release|x86 + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|Itanium.ActiveCfg = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|Itanium.Build.0 = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|x64.ActiveCfg = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|x64.Build.0 = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|x86.ActiveCfg = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Debug|x86.Build.0 = Debug|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|Any CPU.Build.0 = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|Itanium.ActiveCfg = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|Itanium.Build.0 = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|x64.ActiveCfg = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|x64.Build.0 = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|x86.ActiveCfg = Release|Any CPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SCADA/Program/DataService/ExtensionMethods.cs b/SCADA/Program/DataService/ExtensionMethods.cs index d92cb40..24b0cd4 100644 --- a/SCADA/Program/DataService/ExtensionMethods.cs +++ b/SCADA/Program/DataService/ExtensionMethods.cs @@ -809,5 +809,78 @@ namespace DataService var klen = bytes[start + 1]; return Encoding.ASCII.GetString(bytes, start + 2, klen).Trim((char)0); } + + public static ushort ReverseInt16(short value) + { + ushort low = (ushort)(((ushort)value & (ushort)0xFFU) << 8); + ushort high = (ushort)(((ushort)value & (ushort)0xFF00U) >> 8); + return (ushort)(low | high); + } + + /// + /// 掐头去尾得到数据字符串 + /// + /// 全部字符串 + /// 头字符串 + /// 尾字符串 + /// + public static string Pinchstring(string allStr, string startStr, string endStr) + { + int i1 = allStr.IndexOf(startStr); + int i2 = allStr.IndexOf(endStr); + string result; + try + { + result = allStr.Substring(i1 + startStr.Length, i2 - i1 - startStr.Length); + } + catch + { + return string.Empty; + } + return result; + } + + /// + /// 异或校验 + /// + /// 传进来进行校验的字符串 + /// 校验码 + public static string XorCheck(string xorStr) + { + + try + { + //小写转换成大写,因为XOR校验区分大小写 + /***************************************************** + * XOR校验(异或校验) + * VerifyByte是得到的校验码 + ***************************************************/ + xorStr = xorStr.ToUpper(); + byte[] bt = Encoding.Default.GetBytes(xorStr); + byte VerifyByte = bt[0]; + for (int i = 1; i < bt.Length; i++) + { + VerifyByte = (byte)(VerifyByte ^ bt[i]); + } + + /********************************** + * 校验码如果小于0X10则十位补零 + * **********************************/ + string xor = ""; + if (VerifyByte < 16) + { + xor = "0" + VerifyByte.ToString("X"); + } + else + { + xor = VerifyByte.ToString("X"); + } + return xor; + } + catch + { + throw; + } + } } } diff --git a/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs b/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs index 5b03289..a4f4d6f 100644 --- a/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs +++ b/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs @@ -322,8 +322,6 @@ namespace ModbusDriver return WriteSyncData(data); } - - public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false) { ModbusTcpGroup grp = new ModbusTcpGroup(id, name, updateRate, active, this); @@ -570,6 +568,7 @@ namespace ModbusDriver byte[] rcvBytes = _plcReader.ReadBytes(area.Start, (ushort)area.Len);//从PLC读取数据 if (rcvBytes == null || rcvBytes.Length == 0) { + offset += area.Len / 2; //_plcReader.Connect(); continue; } diff --git a/SCADA/Program/PanasonicDriver/PanasonicDriver.csproj b/SCADA/Program/PanasonicDriver/PanasonicDriver.csproj new file mode 100644 index 0000000..6d6f87b --- /dev/null +++ b/SCADA/Program/PanasonicDriver/PanasonicDriver.csproj @@ -0,0 +1,53 @@ + + + + + Debug + AnyCPU + {0D1C943D-594F-4E07-AA39-90CEC4E37C8E} + Library + Properties + PanasonicDriver + PanasonicDriver + v4.6 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + {8965e389-6466-4b30-bd43-83c909044637} + DataService + + + + + \ No newline at end of file diff --git a/SCADA/Program/PanasonicDriver/PanasonicSerialReader.cs b/SCADA/Program/PanasonicDriver/PanasonicSerialReader.cs new file mode 100644 index 0000000..802ac82 --- /dev/null +++ b/SCADA/Program/PanasonicDriver/PanasonicSerialReader.cs @@ -0,0 +1,708 @@ +using DataService; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.IO.Ports; +using System.Net; +using System.Text; + +namespace PanasonicPLCriver +{ + [Description("松下协议串口通讯")] + public class PanasonicSerialReader : IPLCDriver, IMultiReadWrite + { + private SerialPort _serialPort; + List _grps = new List(); + public IEnumerable Groups + { + get { return _grps; } + } + IDataServer _server; + public IDataServer Parent + { + get { return _server; } + } + #region 本程序不会用到的方法 + short _id; + public short ID + { + get + { + return _id; + } + } + public string GetAddress(DeviceAddress address) + { + return string.Empty; + } + #endregion + public PanasonicSerialReader(IDataServer server, short id, string name, string ip, int timeOut = 500, string spare1 = null, string spare2 = null) + { + _server = server; + _id = id; + //spare1 = {COM3,9600,Odd,8,One} + _serialPort = new SerialPort("COM2",57600,Parity.Odd,8,StopBits.One); + _devId = byte.Parse(spare2); + } + public bool IsClosed + { + get + { + return _serialPort.IsOpen == false; + } + } + + public int Limit + { + get { return 60; }//应该是读取最大数的上限吧 + } + + public string Name + { + get + { + throw new NotImplementedException(); + } + } + + public int PDU + { + get + { + return 256;//每帧最大字节数 + } + } + string _port; + public string ServerName + { + get { return _port; } + set { _port = value; } + } + + private int _timeOut; + private byte _devId; + + public int TimeOut + { + get { return _timeOut; } + set { _timeOut = value; } + } + + public event ShutdownRequestEventHandler OnClose; + + public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0, bool active = false) + { + PanasonicGroup grp = new PanasonicGroup(id, name, updateRate, active, this); + _grps.Add(grp); + return grp; + } + + public bool Connect() + { + try + { + _serialPort.Open(); + _serialPort.NewLine = "\r"; + _serialPort.ReadTimeout = 10000; + return true; + } + catch (IOException error) + { + if (OnClose != null) + { + OnClose(this, new ShutdownRequestEventArgs(error.Message)); + } + return false; + } + } + + public void Dispose() + { + foreach (IGroup grp in _grps) + { + grp.Dispose(); + } + _grps.Clear(); + _serialPort.Close(); + } + private bool isBool(string area) + { + if (area == "X" || area == "Y" || area == "R" || area == "T" || area == "C") + return true; + else + return false; + } + private bool isBool(int area) + { + if (area == Panasonic.Xarea || area == Panasonic.Yarea || area == Panasonic.Rarea || area == Panasonic.Tarea || area == Panasonic.Carea) + return true; + else + return false; + } + public DeviceAddress GetDeviceAddress(string address) + { + DeviceAddress dv = DeviceAddress.Empty; + if (string.IsNullOrEmpty(address)) + return dv; + string area = address.Substring(0, 1); + if (!isBool(area))//如果输入的不是单触点寄存器地址 + { + int st; + int.TryParse(address.Substring(2), out st); + dv.Start = st; + switch (address.Substring(0, 2)) + { + case "DT": + dv.Area = Panasonic.DTarea; + break; + case "SV": + dv.Area = Panasonic.SVarea; + break; + case "EV": + dv.Area = Panasonic.EVarea; + break; + case "IX": + dv.Area = Panasonic.IXarea; + break; + case "IY": + dv.Area = Panasonic.IYarea; + break; + case "WX": + dv.Area = Panasonic.WXarea; + break; + case "WY": + dv.Area = Panasonic.WYarea; + break; + case "WR": + dv.Area = Panasonic.WRarea; + break; + } + } + else//输入的是布尔量 如R10.4 但在PLC那端定义位R104 R10.A 在PLC哪里就是.. + { + int index = address.IndexOf('.'); + if (index > 0)// index = 3 + { + string start = address.Substring(1, index - 1); + string bit = address.Substring(index + 1); + dv.Start = int.Parse(start);//10 + dv.Bit = byte.Parse(bit, NumberStyles.HexNumber);//4 + } + else //index = -1 用户输入了R104 就认为是R104.0 + dv.Start = int.Parse(address.Substring(1)); + switch (address.Substring(0, 1)) + { + case "X": + dv.Area = Panasonic.Xarea; + break; + case "Y": + dv.Area = Panasonic.Yarea; + break; + case "R": + dv.Area = Panasonic.Rarea; + break; + case "T": + dv.Area = Panasonic.Tarea; + break; + case "C": + dv.Area = Panasonic.Carea; + break; + } + } + return dv; + } + #region 实现了四个命令 其余没有做 + /// + /// 按字单位读取触点状态 从0-999 + /// 如果是读取R100.4 和R101.5就是CreateRCCCmd(100, 2,Panasonic.Rarea,out respBeginStr) + /// 然后从读来的16位数中获取需要的位 + /// + /// + private string CreateRCCCmd(int start, ushort size, int areaType, out string respBeginStr) + { + string area = string.Empty; + switch (areaType) + { + case Panasonic.Xarea: + area = "X"; + break; + case Panasonic.Yarea: + area = "Y"; + break; + case Panasonic.Rarea: + area = "R"; + break; + case Panasonic.Carea: + area = "C"; + break; + } + respBeginStr = "<" + IntTo2Hex(_devId) + "$" + "RC" + area; + string readCmd = "<" + IntTo2Hex(_devId) + "#" + Panasonic.RCCCmd + area + IntTo4Bcd(start) + IntTo4Bcd(start + size - 1); + readCmd = readCmd + Utility.XorCheck(readCmd) + "\r"; + return readCmd; + } + /// + /// 创建写入单触点状态命令 + /// + /// 开始地址 + /// 位索引 + /// 触点类型:X,Y,R,C + /// 写入的值 + /// 响应的开始帧 + private string CreateWCSCmd(int start, int bit, int areaType, bool value, out string respBeginStr) + { + string area = string.Empty; + switch (areaType) + { + case Panasonic.Xarea: + area = "X"; + break; + case Panasonic.Yarea: + area = "Y"; + break; + case Panasonic.Rarea: + area = "R"; + break; + case Panasonic.Carea: + area = "C"; + break; + } + string bcdStart = IntTo3Bcd(start); + string hexBit = bit.ToString("X"); + string strData = area + bcdStart + hexBit + (value ? "1" : "0"); + respBeginStr = "<" + IntTo2Hex(_devId) + "$" + "WC"; + string readCmd = "<" + IntTo2Hex(_devId) + "#" + Panasonic.WCSCmd + strData; + readCmd = readCmd + Utility.XorCheck(readCmd) + "\r"; + return readCmd; + } + /// + /// 读取多个寄存器 只支持DT寄存器 + /// + /// 开始地址 + /// 寄存器个数 + /// 响应的开始帧 + /// + private string CreateRDCmd(int start, ushort size, out string respBeginStr) + { + respBeginStr = "<" + IntTo2Hex(_devId) + "$" + "RD"; + string readCmd = "<" + IntTo2Hex(_devId) + "#" + Panasonic.RDCmd + "D" + IntTo5Bcd(start) + IntTo5Bcd(start + size - 1); + readCmd = readCmd + Utility.XorCheck(readCmd) + "\r"; + return readCmd; + } + /// + /// 写入连续一块区域寄存器 + /// + /// 开始地址 + /// 寄存器个数(最小是1) + /// 写入的值 + /// 响应的开始帧 + /// + public string CreateWDCmd(int start, short[] values, out string respBeginStr) + { + string dataStr = string.Empty; + int size = values.Length; + for (int i = 0; i < size; i++) + { + UInt16 value = Utility.ReverseInt16(values[i]); + dataStr = dataStr + IntTo4Hex(value); + } + respBeginStr = "<" + IntTo2Hex(_devId) + "$" + Panasonic.WDCmd; + string readCmd = "<" + IntTo2Hex(_devId) + "#" + Panasonic.WDCmd + "D" + IntTo5Bcd(start) + IntTo5Bcd(start + size - 1) + dataStr; + readCmd = readCmd + Utility.XorCheck(readCmd) + "\r"; + return readCmd; + } + #endregion + /// + /// 读取多个字 松下只能读取16位数读取,不支持32位,浮点和字符串 + /// 如果是读取数据寄存器的值的话,读取的是4字符16位数 + /// 读来的数据比如是3124H 那么真实值应该是2431H 最大值为FFFFH + /// + /// 开始的地址 + /// 个数 + /// + public byte[] ReadBytes(DeviceAddress startAddress, ushort size) + { + string respBeginStr; + string cmd; + try + { + if (!isBool(startAddress.Area))//读寄存器的情况 + { + cmd = CreateRDCmd(startAddress.Start,size,out respBeginStr); + } + else //读触点的情况 + { + cmd = CreateRCCCmd(startAddress.Start, size, startAddress.Area, out respBeginStr); + } + return WriteSyncData(respBeginStr, cmd); + } + catch (Exception e) + { + OnClose?.Invoke(this, new ShutdownRequestEventArgs(e.Message)); + return null; + } + } + private static readonly object syncLock = new Object(); + private byte[] WriteSyncData(string respBeginStr, string cmd) + { + byte[] writeData = Encoding.Default.GetBytes(cmd); + string recv = string.Empty; + lock (syncLock)//这里加锁 防止一个刚写完还没全读来 另一就去写 + //但这就会造成锁死的情况 比如循环读的时候 触发去写 这时候必须要等读完才能去写 + { + _serialPort.Write(writeData, 0, writeData.Length); + try + { + recv = _serialPort.ReadLine().Trim(); + } + catch (Exception) + { + OnClose?.Invoke(this, new ShutdownRequestEventArgs("读取超时")); + return null; + } + } + if (recv.Substring(3, 1) == "!")//返回为错误代码 + { + string err = recv.Substring(4, 2); + OnClose?.Invoke(this, new ShutdownRequestEventArgs(daveStrerror(err))); + return null; + } + string needXorStr = recv.Substring(0, recv.Length - 2);//需要进行xor校验的字符串 + string recvCheck = recv.Substring(recv.Length - 2, 2);//接收的xor字符串 + string checkStr = Utility.XorCheck(needXorStr); + if (checkStr != recvCheck) + { + OnClose?.Invoke(this, new ShutdownRequestEventArgs("校验失败")); + return null; + } + else + { + if (recv.Substring(4, 1) == "W")//如果是写入命令 + { + return new byte[0]; + } + string dataStr = Utility.Pinchstring(recv, respBeginStr, checkStr); + return Utility.HexToBytes(dataStr); + } + } + string daveStrerror(string code) + { + switch (code) + { + case "20":return "未定义"; + case "21": return "远程单元无法被正确识别,或者发生了数据错误."; + case "22": return "用于远程单元的接收缓冲区已满."; + case "23": return "远程单元编号(01 至16)设置与本地单元重复."; + case "24": return "试图发送不符合传输格式的数据,或者某一帧数据溢出或发生了数据错误."; + case "25": return "传输系统硬件停止操作."; + case "26": return "远程单元的编号设置超出01 至63 的范围."; + case "27": return "接收方数据帧溢出. 试图在不同的模块之间发送不同帧长度的数据."; + case "28": return "远程单元不存在. (超时)"; + case "29": return "试图发送或接收处于关闭状态的缓冲区."; + case "30": return "持续处于传输禁止状态."; + case "40": return "在指令数据中发生传输错误."; + case "41": return "所发送的指令信息不符合传输格式."; + case "42": return "发送了一个未被支持的指令向未被支持的目标站发送了指令"; + case "43": return "在处于传输请求信息挂起时,发送了其他指令."; + case "50": return "设置了实际不存在的链接编号.."; + case "51": return "当向其他单元发出指令时,本地单元的传输缓冲区已满.."; + case "52": return "无法向其他单元传输"; + case "53": return "在接收到指令时,正在处理其他指令."; + case "60": return "在指令中包含有无法使用的代码,或者代码没有附带区域指定参数(X,Y,D,等以外.)."; + case "61": return "触点编号,区域编号,数据代码格式(BCD,hex,等)上溢出, 下溢出以及区域指定错误."; + case "62": return "过多记录数据在未记录状态下的操作"; + case "63": return "当一条指令发出时,运行模式不能够对指令进行处理"; + case "65": return "在存储保护状态下执行写操作到程序区域或系统寄存器。"; + case "66": return "地址(程序地址、绝对地址等)数据编码形式(BCD、hex 等)、上溢、下溢或指定范围错误"; + case "67": return "要读的数据不存在。(读取没有写入注释寄存区的数据。)"; + default: return "no message defined!"; + } + } + public ItemData ReadBit(DeviceAddress address) + { + throw new NotImplementedException(); + } + public ItemData ReadByte(DeviceAddress address) + { + throw new NotImplementedException(); + } + public ItemData ReadFloat(DeviceAddress address) + { + throw new NotImplementedException(); + } + public ItemData ReadInt16(DeviceAddress address) + { + throw new NotImplementedException(); + } + /// + /// 读取一个32位数 返回如果是6300 则认为是0063H 高低位要反 而且是16进账 + /// + /// + public ItemData ReadInt32(DeviceAddress address) + { + throw new NotImplementedException(); + } + + public ItemData[] ReadMultiple(DeviceAddress[] addrsArr) + { + throw new NotImplementedException(); + } + + public ItemData ReadString(DeviceAddress address, ushort size) + { + throw new NotImplementedException(); + } + + public ItemData ReadValue(DeviceAddress address) + { + throw new NotImplementedException(); + } + + public bool RemoveGroup(IGroup group) + { + throw new NotImplementedException(); + } + + public int WriteBit(DeviceAddress address, bool bit) + { + string respBeginStr = string.Empty; + string cmd = CreateWCSCmd(address.Start,address.Bit,address.Area,bit,out respBeginStr); + WriteSyncData(respBeginStr, cmd); + return 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + throw new NotImplementedException(); + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + throw new NotImplementedException(); + } + + public int WriteFloat(DeviceAddress address, float value) + { + throw new NotImplementedException(); + } + + public int WriteInt16(DeviceAddress address, short value) + { + string respBeginStr = string.Empty; + string cmd = CreateWDCmd(address.Start, new short[1] {value}, out respBeginStr); + WriteSyncData(respBeginStr, cmd); + return 0; + } + + public int WriteInt32(DeviceAddress address, int value) + { + throw new NotImplementedException(); + } + + public int WriteMultiple(DeviceAddress[] addrArr, object[] buffer) + { + throw new NotImplementedException(); + } + + public int WriteString(DeviceAddress address, string str) + { + throw new NotImplementedException(); + } + + public int WriteValue(DeviceAddress address, object value) + { + throw new NotImplementedException(); + } + + #region 类中类中工具方法 + /// + /// 将byte类型转换成两位16进制字符串 + /// + private static string IntTo2Hex(int num) + { + return num.ToString("X2").PadLeft(2, '0');//15 -> 0F + } + /// + /// 将byte类型转换成4位16进制字符串 + /// + private static string IntTo4Hex(int num) + { + return num.ToString("X2").PadLeft(4, '0');//15 -> 000F + } + /// + /// 将Int类型转换成3位BCD字符串 + /// + private string IntTo3Bcd(int num) + { + return num.ToString().PadLeft(3, '0'); //9->009 + } + /// + /// 将Int类型转换成4位BCD字符串 + /// + private string IntTo4Bcd(int num) + { + return num.ToString().PadLeft(4, '0'); //9->0009 + } + /// + /// 将Int类型转换成5位BCD字符串 + /// + private string IntTo5Bcd(int num) + { + return num.ToString().PadLeft(5, '0'); //9->00009 + } + #endregion + } + public sealed class PanasonicGroup : PLCGroup + { + public PanasonicGroup(short id, string name, int updateRate, bool active, IPLCDriver plcReader) + { + this._id = id; + this._name = name; + this._updateRate = updateRate; + this._isActive = active; + this._plcReader = plcReader; + this._server = _plcReader.Parent; + this._timer = new System.Timers.Timer(); + this._changedList = new List(); + this._cacheReader = new NetShortCacheReader(); + } + + protected override void Poll() + { + short[] cache = (short[])_cacheReader.Cache; + int offset = 0; + foreach (PDUArea area in _rangeList) + { + byte[] rcvBytes = _plcReader.ReadBytes(area.Start, (ushort)area.Len);//从PLC读取数据 + if (rcvBytes == null || rcvBytes.Length == 0) + { + continue; + } + else + { + int len = rcvBytes.Length / 2; + short[] prcv = new short[rcvBytes.Length / 2]; + for (int i = 0; i < prcv.Length; i++) + prcv[i] = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(rcvBytes, i * 2)); + int index = area.StartIndex;//index指向_items中的Tag元数据 + int count = index + area.Count; + while (index < count) + { + DeviceAddress addr = _items[index].Address; + int iShort = addr.CacheIndex; + int iShort1 = iShort - offset; + if (addr.VarType == DataType.BOOL) + { + short tt = prcv[iShort1]; + short tmp = IPAddress.HostToNetworkOrder((short)(tt ^ cache[iShort])); + DeviceAddress next = addr; + if (tmp != 0) + { + while (addr.Start == next.Start) + { + short ne = (short)(1 << next.Bit); + if ((tmp & (ne)) > 0) _changedList.Add(index); + if (++index < count) + next = _items[index].Address; + else + break; + } + } + else + { + while (addr.Start == next.Start && ++index < count) + { + next = _items[index].Address; + } + } + } + else + { + if (addr.DataSize <= 2) + { + if (prcv[iShort1] != cache[iShort]) _changedList.Add(index); + } + else + { + int size = addr.DataSize / 2; + for (int i = 0; i < size; i++) + { + if (prcv[iShort1 + i] != cache[iShort + i]) + { + _changedList.Add(index); + break; + } + } + } + index++; + } + } + for (int j = 0; j < len; j++) + { + cache[j + offset] = prcv[j]; + }//将PLC读取的数据写入到CacheReader中 + offset += len; + } + } + } + } + + public struct Panasonic + { + //用的是松下的协议 支持串口和以太网。 + /// + ///1. 读取单触点状态 + /// + public const string RCSCmd = "RCS"; + /// + /// 2. 写入单触点状态 + /// + public const string WCSCmd = "WCS"; + /// + ///3. 读取多触点状态 + /// + public const string RCPCmd = "RCP"; + /// + /// 4. 写入多触点状态 + /// + public const string WCPCmd = "WCP"; + /// + /// 5. 按字单位读取触点状态 + /// + public const string RCCCmd = "RCC"; + /// + /// 6. 按字单位写入触点状态 + /// + public const string WCCCmd = "WCC"; + /// + /// 7.读取数据寄存器值 不支持索引 + /// + public const string RDCmd = "RD"; + /// + /// 8. 写入数据寄存器值 + /// + public const string WDCmd = "WD"; + + public const byte Xarea = 0;//外部输入 + public const byte Yarea = 1;//外部输出 + public const byte Rarea = 2;//内部继电器 + public const byte Tarea = 3;//定时器 + public const byte Carea = 4;//计数器 不支持 + public const byte Larea = 5;//链接继电器 不支持 + + public const byte DTarea = 6;//数据寄存器 DT + public const byte LDarea = 7;//链接寄存器 LD 不支持 + public const byte FLarea = 8;//文件寄存器 FL 不支持 + public const byte SVarea = 9;//目标值 SV + public const byte EVarea = 10;//经过值 EV + public const byte IXarea = 11;//索引寄存器 IX + public const byte IYarea = 12;//索引寄存器 IY + public const byte WXarea = 13;//字单位外部输入 WX + public const byte WYarea = 14;//字单位外部输出 WY + public const byte WRarea = 15;//字单位内部继电器 WR + public const byte WLarea = 16;//字单位链接继电器 WL 不支持 + + } +} diff --git a/SCADA/Program/PanasonicDriver/Properties/AssemblyInfo.cs b/SCADA/Program/PanasonicDriver/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4a54870 --- /dev/null +++ b/SCADA/Program/PanasonicDriver/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PanasonicDriver")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("PanasonicDriver")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0d1c943d-594f-4e07-aa39-90cec4e37c8e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SCADA/dll/PanasonicDriver.dll b/SCADA/dll/PanasonicDriver.dll new file mode 100644 index 0000000000000000000000000000000000000000..730dfad4c463c964a0bd1b727d880008b8f9585b GIT binary patch literal 17408 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 literal 0 HcmV?d00001