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