387 lines
15 KiB
C#
387 lines
15 KiB
C#
![]() |
using JDYD.IEC104;
|
|||
|
using JDYD.IEC104.DatagramFormatters;
|
|||
|
using System.Net;
|
|||
|
using System.Net.Sockets;
|
|||
|
using System.Threading.Tasks.Dataflow;
|
|||
|
|
|||
|
namespace Yunda.ISAS.DataMonitoringServer.DataAnalysis
|
|||
|
{
|
|||
|
// 定义一个委托
|
|||
|
public delegate void NotifyCallAllCompleteDelegate(string message);
|
|||
|
public class IEC104Client
|
|||
|
{
|
|||
|
Node _node = new Node();
|
|||
|
bool _isMaster = false;
|
|||
|
string _remoteAddr = "192.168.81.35";
|
|||
|
int _remotePort = 2432;
|
|||
|
int _localPort = 2432;
|
|||
|
ActionBlock<IECData> _handleDataAction;
|
|||
|
public event NotifyCallAllCompleteDelegate NotifyCallAllCompleteEvent;
|
|||
|
public IEC104Client(string host, int port,ActionBlock<IECData> handleDataAction)
|
|||
|
{
|
|||
|
_remoteAddr = host;
|
|||
|
_remotePort = port;
|
|||
|
_handleDataAction = handleDataAction;
|
|||
|
_node.NewDatagram += Node_NewDatagram;
|
|||
|
_node.DatagramSending += Node_DatagramSending;
|
|||
|
_node.ConnectionLost += Node_ConnectionLost;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
private void Node_ConnectionLost(Node sender)
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData("连接已断开"));
|
|||
|
if (toDisconnect)
|
|||
|
{
|
|||
|
Connect();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void Node_DatagramSending(APDU d, Node sender)
|
|||
|
{
|
|||
|
DispalyDatagram(d, false);
|
|||
|
}
|
|||
|
//LinkedList<DatagramInfo> DatagramToShow = new LinkedList<DatagramInfo>();
|
|||
|
object locker = new object();
|
|||
|
void DispalyDatagram(APDU apdu, bool rev = true)
|
|||
|
{
|
|||
|
if (apdu == null)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
try
|
|||
|
{
|
|||
|
var t = apdu.TransferTime;
|
|||
|
_handleDataAction.Post(new IECData(string.Format("{0}-{1}-{2} {3:d2}:{4:d2}:{5:d2}.{6:d3} {7}",
|
|||
|
t.Year, t.Month, t.Day, t.Hour, t.Minute, t.Second, t.Millisecond, rev ? "接收" : "发送")));
|
|||
|
switch (apdu.Format)
|
|||
|
{
|
|||
|
case DatagramFormat.NumberedSupervisory:
|
|||
|
_handleDataAction.Post(new IECData("S格式报文 " + "接收序号 " + apdu.RecevingNumber.ToString()));
|
|||
|
|
|||
|
break;
|
|||
|
case DatagramFormat.UnnumberedControl:
|
|||
|
_handleDataAction.Post(new IECData("U格式报文 " + apdu.ControlFunction.ToString()));
|
|||
|
break;
|
|||
|
case DatagramFormat.InformationTransmit:
|
|||
|
|
|||
|
string content = $"I格式报文:发送序号:{apdu.SendingNumber} 接收序号:{apdu.RecevingNumber} 报文类型: {apdu.Formatter?.Description}";
|
|||
|
_handleDataAction.Post(new IECData(content));
|
|||
|
content = $"I格式报文:服务地址:{apdu.ASDU.Address} 传送原因:{apdu.ASDU.Cause} P/N:{apdu.ASDU.PN} TEST:{apdu.ASDU.Test}";
|
|||
|
_handleDataAction.Post(new IECData(content));
|
|||
|
foreach (var message in apdu.ASDU.Messages)
|
|||
|
{
|
|||
|
IECData iECDataFloat = null;
|
|||
|
IECData iECDataInt = null;
|
|||
|
content = $"I格式报文:数据类型:{message.Type}";
|
|||
|
_handleDataAction.Post(new IECData(content));
|
|||
|
|
|||
|
if (message.Type != ElementType.Empty)
|
|||
|
{
|
|||
|
switch (message.Type)
|
|||
|
{
|
|||
|
case ElementType.NVA:
|
|||
|
break;
|
|||
|
case ElementType.R:
|
|||
|
iECDataFloat = new IECData((ushort)message.Address, message.R, apdu.ASDU.Cause, IECDataType.Telemetering);
|
|||
|
break;
|
|||
|
case ElementType.SVA:
|
|||
|
break;
|
|||
|
case ElementType.SCD:
|
|||
|
iECDataInt = new IECData((ushort)message.Address, (byte)(message.SCD + 1), apdu.ASDU.Cause,IECDataType.Telesignal);
|
|||
|
break;
|
|||
|
case ElementType.DCD:
|
|||
|
iECDataInt = new IECData((ushort)message.Address, (byte)(message.SCD), apdu.ASDU.Cause, IECDataType.Telesignal);
|
|||
|
break;
|
|||
|
case ElementType.DIQ:
|
|||
|
iECDataInt = new IECData((ushort)message.Address, (byte)message.DIQ, apdu.ASDU.Cause, IECDataType.Telesignal);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (message.Extra != null)
|
|||
|
{
|
|||
|
if (apdu.Formatter != null && apdu.Formatter.GetType() == typeof(C_IC_NA_1))
|
|||
|
{
|
|||
|
content = $"I格式报文:附加信息:总召唤";
|
|||
|
_handleDataAction.Post(new IECData(content));
|
|||
|
if (apdu.ASDU.Cause == 10)
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData() { DataType = IECDataType.CompleteAll });
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
DateTime time =default;
|
|||
|
if (message.TimeStamp != null && message.TimeStamp.Length == 7)
|
|||
|
{
|
|||
|
var timeStr = string.Format("{0:d2}:{1:d2}:{2:d2}.{3:d3}", message.Hour, message.Minute, message.Second, message.Milisecond);
|
|||
|
if (!DateTime.TryParse(timeStr, out time))
|
|||
|
{
|
|||
|
time = DateTime.Now;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
time = DateTime.Now;
|
|||
|
}
|
|||
|
if (iECDataFloat != null)
|
|||
|
{
|
|||
|
iECDataFloat.Time = time;
|
|||
|
_handleDataAction.Post(iECDataFloat);
|
|||
|
}
|
|||
|
if (iECDataInt != null)
|
|||
|
{
|
|||
|
iECDataInt.Time = time;
|
|||
|
_handleDataAction.Post(iECDataInt);
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData(ex));
|
|||
|
}
|
|||
|
GC.Collect();
|
|||
|
}
|
|||
|
|
|||
|
private void Node_NewDatagram(APDU d, Node sender)
|
|||
|
{
|
|||
|
DispalyDatagram(d, true);
|
|||
|
}
|
|||
|
Socket listenSocket;
|
|||
|
Task _connectTask;
|
|||
|
CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
|||
|
public bool _isConnected = false;
|
|||
|
public bool IsCallAllData=false;
|
|||
|
/// <summary>
|
|||
|
/// 连接
|
|||
|
/// </summary>
|
|||
|
public void Connect()
|
|||
|
{
|
|||
|
while (!_cancellationTokenSource.TryReset())
|
|||
|
{
|
|||
|
Task.Delay(100).Wait();
|
|||
|
}
|
|||
|
_isConnected = false;
|
|||
|
toDisconnect = true;
|
|||
|
_connectTask = Task.Factory.StartNew(async () =>
|
|||
|
{
|
|||
|
if (_node.Socket != null)
|
|||
|
return;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData("开始连接..." ));
|
|||
|
if (listenSocket != null)
|
|||
|
{
|
|||
|
listenSocket.Dispose();
|
|||
|
listenSocket = null;
|
|||
|
}
|
|||
|
IPAddress ipAddress;
|
|||
|
Socket s =null;
|
|||
|
if (IPAddress.TryParse(_remoteAddr, out ipAddress))
|
|||
|
{
|
|||
|
if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData("这是一个IPv4地址:" + _remoteAddr));
|
|||
|
s = new Socket( SocketType.Stream, ProtocolType.Tcp);
|
|||
|
}
|
|||
|
else if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData("这是一个IPv6地址:"+ _remoteAddr));
|
|||
|
|
|||
|
s = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
if (s == null)
|
|||
|
{
|
|||
|
throw new Exception("ip地址错误");
|
|||
|
}
|
|||
|
// 创建Socket
|
|||
|
//Socket clientSocket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
|
|||
|
|
|||
|
_handleDataAction.Post(new IECData("正在尝试连接" + _remoteAddr + ",目标端口" + _remotePort));
|
|||
|
await s.ConnectAsync(_remoteAddr, _remotePort, _cancellationTokenSource.Token);
|
|||
|
_node.BindSocket(s, true);
|
|||
|
var ip = _node.Socket.RemoteEndPoint as IPEndPoint;
|
|||
|
if (ip != null)
|
|||
|
{
|
|||
|
var addr = ip.Address;
|
|||
|
if (addr.IsIPv4MappedToIPv6)
|
|||
|
addr = addr.MapToIPv4();
|
|||
|
_handleDataAction.Post(new IECData("新连接已建立,对方位于" + addr.ToString() + ":" + ip.Port));
|
|||
|
}
|
|||
|
_node.StartReceive();
|
|||
|
Task.Delay(1000).Wait();
|
|||
|
//CallAll();
|
|||
|
_isConnected = true;
|
|||
|
_handleDataAction.Post(new IECData("连接成功"));
|
|||
|
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData(new Exception("连接失败,对方地址" + _remoteAddr + ",目标端口" + _remotePort)));
|
|||
|
if (toDisconnect)
|
|||
|
{
|
|||
|
Task.Delay(5000).Wait();
|
|||
|
_handleDataAction.Post(new IECData("正在尝试重连" + _remoteAddr + ",目标端口" + _remotePort));
|
|||
|
Connect();
|
|||
|
}
|
|||
|
if (_isConnected)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
}, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
APDU metf_tosend;
|
|||
|
/// <summary>
|
|||
|
/// 发送遥控
|
|||
|
/// </summary>
|
|||
|
/// <param name="value"></param>
|
|||
|
/// <param name="addr"></param>
|
|||
|
public void SendValue(byte value,int addr,bool isDoublepPoint )
|
|||
|
{
|
|||
|
if (metf_tosend == null)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if (isDoublepPoint)
|
|||
|
{
|
|||
|
var f = (C_DC_NA_1)_node.FormatterManager.GetInstance(typeof(C_DC_NA_1));
|
|||
|
if (metf_tosend == null)
|
|||
|
metf_tosend = f.CreateAPDU(1);
|
|||
|
var apdu = f.Create((ushort)addr, 1, value);
|
|||
|
_node.SendAPDU(apdu);
|
|||
|
metf_tosend = null;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
var f = (C_SC_NA_1)_node.FormatterManager.GetInstance(typeof(C_SC_NA_1));
|
|||
|
if (metf_tosend == null)
|
|||
|
metf_tosend = f.CreateAPDU(1);
|
|||
|
var apdu = f.Create((ushort)addr, 1, value);
|
|||
|
_node.SendAPDU(apdu);
|
|||
|
metf_tosend = null;
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData(ex));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
bool toDisconnect = false;
|
|||
|
public void Close()
|
|||
|
{
|
|||
|
toDisconnect = false;
|
|||
|
_node.CloseConnection();
|
|||
|
_cancellationTokenSource.Cancel();
|
|||
|
}
|
|||
|
public void CallSingle(int addr)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
var f = (C_RD_NA_1)_node.FormatterManager.GetInstance(typeof(C_RD_NA_1));
|
|||
|
var apdu = f.Create(1, (uint)addr);
|
|||
|
_node.SendAPDU(apdu);
|
|||
|
}
|
|||
|
catch (Exception ex) {
|
|||
|
_handleDataAction.Post(new IECData(ex));
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void CallAll()
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
var f = (C_IC_NA_1)_node.FormatterManager.GetInstance(typeof(C_IC_NA_1));
|
|||
|
var apdu = f.Create(1, 0);
|
|||
|
_node.SendAPDU(apdu);
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
_handleDataAction.Post(new IECData(ex));
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 数据体
|
|||
|
/// </summary>
|
|||
|
public class IECData
|
|||
|
{
|
|||
|
public ushort InfoAddr { get; set; }
|
|||
|
public byte IValue { get; set; }
|
|||
|
public float FValue { get; set; }
|
|||
|
public ushort Cause { get; set; } = 20;
|
|||
|
|
|||
|
public IECDataType DataType { get; set; }
|
|||
|
public DateTime Time { get; set; }
|
|||
|
|
|||
|
public string Msg { get; set; }
|
|||
|
public Exception Ex { get; set; }
|
|||
|
public IECData()
|
|||
|
{
|
|||
|
}
|
|||
|
public IECData(string msg)
|
|||
|
{
|
|||
|
Msg = msg;
|
|||
|
DataType = IECDataType.Msg;
|
|||
|
}
|
|||
|
public IECData(Exception ex)
|
|||
|
{
|
|||
|
Ex = ex;
|
|||
|
DataType = IECDataType.Error;
|
|||
|
}
|
|||
|
|
|||
|
public IECData(ushort infoAddr, byte iValue, ushort cause, IECDataType dataType = IECDataType.Telesignal)
|
|||
|
{
|
|||
|
InfoAddr = infoAddr;
|
|||
|
IValue = iValue;
|
|||
|
DataType = dataType;
|
|||
|
Cause = cause;
|
|||
|
|
|||
|
}
|
|||
|
public IECData(ushort infoAddr, float fValue, ushort cause, IECDataType dataType = IECDataType.Telemetering)
|
|||
|
{
|
|||
|
InfoAddr = infoAddr;
|
|||
|
FValue = fValue;
|
|||
|
DataType = dataType;
|
|||
|
Cause = cause;
|
|||
|
|
|||
|
}
|
|||
|
public IECData(ushort infoAddr, byte iValue , IECDataType dataType, ushort cause)
|
|||
|
{
|
|||
|
InfoAddr = infoAddr;
|
|||
|
IValue = iValue;
|
|||
|
DataType = dataType;
|
|||
|
Cause = cause;
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 数据类型
|
|||
|
/// </summary>
|
|||
|
public enum IECDataType
|
|||
|
{
|
|||
|
Telesignal = 0,
|
|||
|
Telemetering = 1,
|
|||
|
TeleCommand = 2,
|
|||
|
Error = 3,
|
|||
|
Msg = 4,
|
|||
|
CompleteAll =5,//完成总召
|
|||
|
}
|
|||
|
|
|||
|
}
|