diff --git a/Modbus.Net/CrossLampControl.WebApi/Web.config b/Modbus.Net/CrossLampControl.WebApi/Web.config
index a16f83e..b35f853 100644
--- a/Modbus.Net/CrossLampControl.WebApi/Web.config
+++ b/Modbus.Net/CrossLampControl.WebApi/Web.config
@@ -1,4 +1,4 @@
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
\ No newline at end of file
diff --git a/Modbus.Net/ModBus.Net/AddressCombiner.cs b/Modbus.Net/ModBus.Net/AddressCombiner.cs
index e920b6d..77df7da 100644
--- a/Modbus.Net/ModBus.Net/AddressCombiner.cs
+++ b/Modbus.Net/ModBus.Net/AddressCombiner.cs
@@ -36,32 +36,61 @@ namespace ModBus.Net
int preNum = -1;
Type preType = null;
int getCount = 0;
- foreach (var address in groupedAddress.OrderBy(address=>address.Address))
+ foreach (var address in groupedAddress.OrderBy(address => address.Address))
{
if (initNum < 0)
{
initNum = address.Address;
- getCount = (int)BigEndianValueHelper.Instance.ByteLength[address.DataType.FullName];
+ getCount = (int) BigEndianValueHelper.Instance.ByteLength[address.DataType.FullName];
}
else
{
if (address.Address > preNum + BigEndianValueHelper.Instance.ByteLength[preType.FullName])
{
- ans.Add(new CommunicationUnit(){Area = area, Address = initNum, GetCount = getCount, DataType = typeof(byte)});
+ ans.Add(new CommunicationUnit()
+ {
+ Area = area,
+ Address = initNum,
+ GetCount = getCount,
+ DataType = typeof (byte)
+ });
initNum = address.Address;
- getCount = (int)BigEndianValueHelper.Instance.ByteLength[address.DataType.FullName];
+ getCount = (int) BigEndianValueHelper.Instance.ByteLength[address.DataType.FullName];
}
else
{
- getCount += (int)BigEndianValueHelper.Instance.ByteLength[address.DataType.FullName];
+ getCount += (int) BigEndianValueHelper.Instance.ByteLength[address.DataType.FullName];
}
}
preNum = address.Address;
preType = address.DataType;
}
- ans.Add(new CommunicationUnit() { Area = area, Address = initNum, GetCount = getCount, DataType = typeof(byte) });
+ ans.Add(new CommunicationUnit()
+ {
+ Area = area,
+ Address = initNum,
+ GetCount = getCount,
+ DataType = typeof (byte)
+ });
}
return ans;
}
}
+
+ public class AddressCombinerSingle : AddressCombiner
+ {
+ public override IEnumerable Combine(IEnumerable addresses)
+ {
+ return
+ addresses.Select(
+ address =>
+ new CommunicationUnit()
+ {
+ Area = address.Area,
+ Address = address.Address,
+ DataType = address.DataType,
+ GetCount = 1
+ }).ToList();
+ }
+ }
}
diff --git a/Modbus.Net/ModBus.Net/BaseMachine.cs b/Modbus.Net/ModBus.Net/BaseMachine.cs
index 03539ed..b98c812 100644
--- a/Modbus.Net/ModBus.Net/BaseMachine.cs
+++ b/Modbus.Net/ModBus.Net/BaseMachine.cs
@@ -144,7 +144,11 @@ namespace ModBus.Net
BigEndianValueHelper.Instance.ByteLength[
communicateAddress.DataType.FullName]));
//如果没有数据,终止
- if (datas == null || datas.Length == 0) return null;
+ if (datas == null || datas.Length == 0 || datas.Length !=
+ (int)
+ Math.Ceiling(communicateAddress.GetCount *
+ BigEndianValueHelper.Instance.ByteLength[
+ communicateAddress.DataType.FullName])) return null;
int pos = 0;
//解码数据
while (pos < communicateAddress.GetCount)
diff --git a/Modbus.Net/ModBus.Net/ConfigurationManager.Designer.cs b/Modbus.Net/ModBus.Net/ConfigurationManager.Designer.cs
index 3e90557..55208f8 100644
--- a/Modbus.Net/ModBus.Net/ConfigurationManager.Designer.cs
+++ b/Modbus.Net/ModBus.Net/ConfigurationManager.Designer.cs
@@ -69,6 +69,15 @@ namespace ModBus.Net {
}
}
+ ///
+ /// 查找类似 opcda://localhost/FBoxOpcServer 的本地化字符串。
+ ///
+ internal static string FBoxOpcDaHost {
+ get {
+ return ResourceManager.GetString("FBoxOpcDaHost", resourceCulture);
+ }
+ }
+
///
/// 查找类似 192.168.1.1 的本地化字符串。
///
@@ -96,6 +105,15 @@ namespace ModBus.Net {
}
}
+ ///
+ /// 查找类似 opcda://localhost/... 的本地化字符串。
+ ///
+ internal static string OpcDaHost {
+ get {
+ return ResourceManager.GetString("OpcDaHost", resourceCulture);
+ }
+ }
+
///
/// 查找类似 102 的本地化字符串。
///
diff --git a/Modbus.Net/ModBus.Net/ConfigurationManager.resx b/Modbus.Net/ModBus.Net/ConfigurationManager.resx
index 720ced4..ea86858 100644
--- a/Modbus.Net/ModBus.Net/ConfigurationManager.resx
+++ b/Modbus.Net/ModBus.Net/ConfigurationManager.resx
@@ -120,6 +120,9 @@
COM1
+
+ opcda://localhost/FBoxOpcServer
+
192.168.1.1
@@ -129,6 +132,9 @@
502
+
+ opcda://localhost/...
+
102
diff --git a/Modbus.Net/ModBus.Net/FBox/FBoxConnector.cs b/Modbus.Net/ModBus.Net/FBox/FBoxConnector.cs
new file mode 100644
index 0000000..fc54a85
--- /dev/null
+++ b/Modbus.Net/ModBus.Net/FBox/FBoxConnector.cs
@@ -0,0 +1,621 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNet.SignalR.Client;
+using Newtonsoft.Json;
+using Thinktecture.IdentityModel.Client;
+
+namespace ModBus.Net.FBox
+{
+ public struct SignalRSigninMsg
+ {
+ public string ClientId { get; set; }
+ public string ClientSecret { get; set; }
+ public string UserId { get; set; }
+ public string Password { get; set; }
+ public string SigninAdditionalValues { get; set; }
+ public SignalRServer SignalRServer { get; set; }
+ }
+
+ public class FBoxConnector : BaseConnector
+ {
+ private OAuth2Client _oauth2;
+ private string _refreshToken;
+
+ private HttpClient _httpClient;
+ private HttpClient _httpClient2;
+ private HubConnection _hubConnection;
+ private SignalRSigninMsg _msg;
+ protected SignalRSigninMsg Msg => _msg;
+ private DMonGroup _dataGroup;
+ private int _state;
+ private string _groupUid;
+ private string _boxUid;
+ private string _boxNo;
+ private int _boxSessionId;
+ private int _connectionState;
+ private Dictionary _data;
+ private Dictionary _dataType;
+
+ private DateTime _timeStamp = DateTime.MinValue;
+
+ public override string ConnectionToken { get; }
+
+ private AsyncLock _lock = new AsyncLock();
+
+ private Timer _timer;
+
+ private string MachineId => ConnectionToken.Split(',')[0];
+
+ private string LocalSequence => ConnectionToken.Split(',')[1];
+
+ private bool _connected;
+ public override bool IsConnected => _connected;
+
+ private bool Connected
+ {
+ get { return _connected; }
+ set
+ {
+ if (value == false)
+ {
+ Disconnect();
+ }
+ _connected = value;
+
+ }
+ }
+
+ private Constants _constants;
+ private Constants Constants => _constants ?? (_constants = new Constants());
+
+ public FBoxConnector(string machineId, string localSequence, SignalRSigninMsg msg)
+ {
+ Constants.SignalRServer = msg.SignalRServer;
+ System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
+ ConnectionToken = machineId + "," + localSequence;
+ _msg = msg;
+ _data = new Dictionary();
+ _dataType = new Dictionary();
+ }
+
+ private async void ChangeToken(object sender)
+ {
+ try
+ {
+
+ var tokenResponse = await _oauth2.RequestRefreshTokenAsync(_refreshToken);
+ _refreshToken = tokenResponse.RefreshToken;
+ _httpClient.SetBearerToken(tokenResponse.AccessToken);
+
+ _httpClient2.SetBearerToken(tokenResponse.AccessToken);
+ _hubConnection.Stop();
+ _hubConnection.Headers["Authorization"] = "Bearer " + tokenResponse.AccessToken;
+ await _hubConnection.Start();
+ await
+ _httpClient2.PostAsync(
+ "dmon/group/" + _dataGroup.Uid + "/start", null);
+
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Retoken failed." + e.Message);
+ }
+ Console.WriteLine("Retoken success.");
+ }
+
+ public override bool Connect()
+ {
+ return AsyncHelper.RunSync(ConnectAsync);
+ }
+
+ public override async Task ConnectAsync()
+ {
+ try
+ {
+
+ _oauth2 = new OAuth2Client(
+ new Uri(Constants.TokenEndpoint),
+ Msg.ClientId,
+ Msg.ClientSecret
+ );
+ var tokenResponse = await _oauth2.RequestResourceOwnerPasswordAsync
+ (
+ Msg.UserId,
+ Msg.Password,
+ Msg.SigninAdditionalValues
+ );
+ if (tokenResponse != null)
+ {
+ _refreshToken = tokenResponse.RefreshToken;
+ await CallService(Msg, tokenResponse.AccessToken);
+ }
+
+ await
+ _httpClient2.PostAsync(
+ "dmon/group/" + _dataGroup.Uid + "/start", null);
+ Connected = true;
+ _timer = new Timer(ChangeToken, null, 3600*1000*4, 3600*1000*4);
+ Console.WriteLine("SignalR Connected success");
+ return true;
+
+ }
+ catch (Exception e)
+ {
+ _oauth2 = null;
+ Console.WriteLine("SignalR Connected failed");
+ return false;
+ }
+ }
+
+ private async Task CallService(SignalRSigninMsg msg, string token)
+ {
+
+ var guid = Guid.NewGuid().ToString();
+
+ var baseAddress = Constants.AspNetWebApiSampleApi;
+
+ _httpClient = new HttpClient
+ {
+ BaseAddress = new Uri(baseAddress)
+ };
+
+ _httpClient.SetBearerToken(token);
+
+ //var response = await _httpClient.GetStringAsync("device/spec");
+
+ //List deviceSpecs = JsonConvert.DeserializeObject>(response);
+ //deviceSpecs = deviceSpecs.OrderBy(p => p.Id).ToList();
+
+
+ var response = await _httpClient.GetStringAsync("boxgroup");
+
+ List boxGroups = JsonConvert.DeserializeObject>(response);
+
+ foreach (var boxGroup in boxGroups)
+ {
+ var boxes = boxGroup.BoxRegs;
+ foreach (var box in boxes)
+ {
+ var sessionId = box.Box.CurrentSessionId;
+ var baseUrl = box.Box.CommServer.ApiBaseUrl;
+ var signalrUrl = box.Box.CommServer.SignalRUrl;
+ var boxUid = box.Box.Uid;
+ var boxNo = box.Box.BoxNo;
+ var connectionState = box.Box.ConnectionState;
+
+
+ if (boxNo != MachineId) continue;
+
+ _httpClient2 = new HttpClient
+ {
+ BaseAddress = new Uri(baseUrl)
+ };
+ _httpClient2.SetBearerToken(token);
+ _httpClient2.DefaultRequestHeaders.Add("X-FBox-ClientId", guid);
+
+ response = await _httpClient2.GetStringAsync("box/" + box.Box.Uid + "/dmon/def/grouped");
+
+
+ List dataGroups = JsonConvert.DeserializeObject>(response);
+ foreach (var dataGroup in dataGroups)
+ {
+ if (dataGroup.Name == LocalSequence)
+ {
+ _dataGroup = dataGroup;
+ break;
+ }
+ }
+
+ _boxSessionId = sessionId;
+ _boxNo = boxNo;
+ _connectionState = connectionState;
+
+ _hubConnection = new HubConnection(signalrUrl);
+ _hubConnection.Headers.Add("Authorization", "Bearer " + token);
+ _hubConnection.Headers.Add("X-FBox-ClientId", guid);
+ _hubConnection.Headers.Add("X-FBox-Session", sessionId.ToString());
+
+ IHubProxy dataHubProxy = _hubConnection.CreateHubProxy("clientHub");
+ dataHubProxy.On>("dMonUpdateValue",
+ (boxSessionId, values) =>
+ {
+
+//#if DEBUG
+ //Console.WriteLine($"Box session {boxSessionId} return at {DateTime.Now}");
+//#endif
+
+ _timeStamp = DateTime.Now;
+
+ foreach (var value in values)
+ {
+ if (value.Status != 0)
+ {
+ lock (_data)
+ {
+ var dMonEntry =
+ _dataGroup.DMonEntries.FirstOrDefault(
+ p => p.Uid == value.Id);
+ if (dMonEntry != null)
+ {
+ if (_data.ContainsKey(dMonEntry.Desc))
+ {
+ _data.Remove(dMonEntry.Desc);
+ }
+ }
+
+ }
+ return;
+ }
+ lock (_data)
+ {
+ if (_dataGroup.DMonEntries.Any(p => p.Uid == value.Id))
+ {
+ if (_data == null)
+ {
+ _data = new Dictionary();
+ }
+
+ var dMonEntry = _dataGroup.DMonEntries.FirstOrDefault(
+ p => p.Uid == value.Id);
+
+ if (value.Value.HasValue && dMonEntry != null)
+ {
+ if (_data.ContainsKey(dMonEntry.Desc))
+ {
+ _data[dMonEntry.Desc] = value.Value.Value;
+ }
+ else
+ {
+ _data.Add(dMonEntry.Desc, value.Value.Value);
+ }
+ }
+ }
+ }
+ }
+ });
+
+ dataHubProxy.On("boxConnectionStateChanged",
+ async (newConnectionToken, getBoxUid, oldStatus, newStatus) =>
+ {
+ //#if DEBUG
+ //Console.WriteLine(
+ //$"Box uid {getBoxUid} change state at {DateTime.Now} new connectionToken {newConnectionToken} newStatus {newStatus}");
+ //#endif
+ sessionId = newConnectionToken;
+ _boxSessionId = sessionId;
+
+ _connectionState = newStatus;
+
+ _httpClient2.DefaultRequestHeaders.Remove("X-FBox-Session");
+ _httpClient2.DefaultRequestHeaders.Add("X-FBox-Session",
+ sessionId.ToString());
+ _hubConnection.Headers["X-FBox-Session"] = sessionId.ToString();
+
+ if (_hubConnection.State == ConnectionState.Disconnected)
+ {
+ await _hubConnection.Start();
+ }
+
+ if (newStatus == 1)
+ {
+ if (Connected)
+ {
+ await
+ _httpClient2.PostAsync(
+ "dmon/group/" + _dataGroup.Uid + "/start", null);
+ }
+ }
+
+ else
+ {
+ lock (_data)
+ {
+ _data.Clear();
+ }
+ Connected = false;
+ }
+
+ });
+
+ _hubConnection.Error += async ex =>
+ {
+ Console.WriteLine(@"SignalR error: {0}", ex.Message);
+ await ConnectRecovery(_hubConnection);
+ };
+
+ _hubConnection.Closed += async () =>
+ {
+ await ConnectRecovery(_hubConnection);
+ };
+
+ ServicePointManager.DefaultConnectionLimit = 10;
+
+ if (_dataGroup == null) return;
+
+ var groupUid = _dataGroup.Uid;
+ var groupName = _dataGroup.Name;
+
+ if (groupName != "(Default)" && groupName != "默认组" && _connectionState == 1)
+ {
+ _groupUid = groupUid;
+ }
+ if (groupName != "(Default)" && groupName != "默认组" && _connectionState == 1)
+ {
+ _boxUid = boxUid;
+ }
+
+ _dataType = new Dictionary();
+
+ foreach (var dMonEntry in _dataGroup.DMonEntries)
+ {
+ Type type;
+ switch (dMonEntry.DataType)
+ {
+ //位
+ case 0:
+ {
+ type = typeof (bool);
+ break;
+ }
+ //16位无符号
+ case 1:
+ {
+ type = typeof (ushort);
+ break;
+ }
+ //16位有符号
+ case 2:
+ {
+ type = typeof (short);
+ break;
+ }
+ //32位无符号
+ case 11:
+ {
+ type = typeof (uint);
+ break;
+ }
+ //32位有符号
+ case 12:
+ {
+ type = typeof (int);
+ break;
+ }
+ //16位BCD
+ case 3:
+ {
+ type = typeof (short);
+ break;
+ }
+ //32位BCD
+ case 13:
+ {
+ type = typeof (int);
+ break;
+ }
+ //浮点数
+ case 16:
+ {
+ type = typeof (float);
+ break;
+ }
+ //16位16进制
+ case 4:
+ {
+ type = typeof (short);
+ break;
+ }
+ //32位16进制
+ case 14:
+ {
+ type = typeof (int);
+ break;
+ }
+ //16位2进制
+ case 5:
+ {
+ type = typeof (short);
+ break;
+ }
+ //32位2进制
+ case 15:
+ {
+ type = typeof (int);
+ break;
+ }
+ default:
+ {
+ type = typeof (short);
+ break;
+ }
+ }
+
+ if (!_dataType.ContainsKey(dMonEntry.Desc))
+ {
+ _dataType.Add(dMonEntry.Desc, type);
+ }
+ else
+ {
+ _dataType[dMonEntry.Desc] = type;
+ }
+ }
+
+
+ await _hubConnection.Start();
+ await dataHubProxy.Invoke("updateClientId", guid);
+ }
+ }
+ }
+
+ private async Task ConnectRecovery(HubConnection hubConnection)
+ {
+
+
+ try
+ {
+ if (hubConnection.State != ConnectionState.Connected)
+ {
+ try
+ {
+ hubConnection.Stop();
+ }
+ catch
+ {
+ // ignored
+ }
+
+ await hubConnection.Start();
+ if (Connected)
+ {
+ await
+ _httpClient2.PostAsync(
+ "dmon/group/" + _dataGroup.Uid + "/start", null);
+ }
+ }
+
+ }
+ catch
+ {
+ lock (_data)
+ {
+ _data.Clear();
+ }
+ Connected = false;
+ }
+
+
+ }
+
+
+ public override bool Disconnect()
+ {
+ return AsyncHelper.RunSync(DisconnectAsync);
+ }
+
+ public async Task DisconnectAsync()
+ {
+ try
+ {
+
+ await
+ _httpClient2.PostAsync(
+ "dmon/group/" + _groupUid + "/stop",
+ null);
+ _connected = false;
+ _timer.Dispose();
+ _timer = null;
+ Console.WriteLine("SignalR Disconnect success");
+ return true;
+
+ }
+ catch
+ {
+ Console.WriteLine("SignalR Disconnect failed");
+ return false;
+ }
+ }
+
+ public override bool SendMsgWithoutReturn(byte[] message)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Task SendMsgWithoutReturnAsync(byte[] message)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override byte[] SendMsg(byte[] message)
+ {
+ if (_httpClient == null)
+ {
+ Connected = false;
+ return null;
+ }
+
+ if (_hubConnection.State == ConnectionState.Disconnected)
+ {
+ _hubConnection.Start();
+ }
+
+ var formater = new AddressFormaterFBox();
+ var translator = new AddressTranslatorFBox();
+
+ byte[] ans;
+
+ if (_connectionState != 1)
+ {
+ Connected = false;
+ Console.WriteLine($"Return Value Rejected with connectionToken {ConnectionToken}");
+ return null;
+ }
+
+ if (_timeStamp == DateTime.MinValue)
+ {
+ return Encoding.ASCII.GetBytes("NoData");
+ }
+
+ if (DateTime.Now - _timeStamp > TimeSpan.FromMinutes(1))
+ {
+ Connected = false;
+ return null;
+ }
+
+ Dictionary machineDataValue;
+ lock (_data)
+ {
+ machineDataValue = _data.ToDictionary(pair => pair.Key, pair => pair.Value);
+ }
+ var machineDataType = _dataType;
+ if (machineDataType == null || machineDataType.Count == 0)
+ {
+ Connected = false;
+ Console.WriteLine($"Return Value Rejected with connectionToken {ConnectionToken}");
+ return null;
+ }
+
+ if (machineDataValue == null || machineDataValue.Count == 0)
+ {
+ return Encoding.ASCII.GetBytes("NoData");
+ }
+
+ int pos = 0;
+ int area = BigEndianValueHelper.Instance.GetInt(message, ref pos);
+ int address = BigEndianValueHelper.Instance.GetInt(message, ref pos);
+ //short count = BigEndianValueHelper.Instance.GetShort(message, ref pos);
+ object[] dataAns = new object[1];
+ try
+ {
+ dataAns[0] =
+ Convert.ChangeType(
+ machineDataValue[formater.FormatAddress(translator.GetAreaName(area), address)],
+ machineDataType[formater.FormatAddress(translator.GetAreaName(area), address)]);
+ }
+ catch (Exception e)
+ {
+ return Encoding.ASCII.GetBytes("NoData");
+ //dataAns[0] =
+ //Convert.ChangeType(
+ //0,
+ //machineDataType[formater.FormatAddress(translator.GetAreaName(area), address)]);
+ }
+ finally
+ {
+ ans = BigEndianValueHelper.Instance.ObjectArrayToByteArray(dataAns);
+ }
+
+ return ans;
+ }
+
+ public override Task SendMsgAsync(byte[] message)
+ {
+ return Task.Factory.StartNew(() => SendMsg(message));
+ }
+ }
+}
diff --git a/Modbus.Net/ModBus.Net/FBox/FBoxProtocalLinker.cs b/Modbus.Net/ModBus.Net/FBox/FBoxProtocalLinker.cs
new file mode 100644
index 0000000..c486dbf
--- /dev/null
+++ b/Modbus.Net/ModBus.Net/FBox/FBoxProtocalLinker.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ModBus.Net.FBox
+{
+ public class FBoxProtocalLinker : ProtocalLinker
+ {
+ protected FBoxProtocalLinker(string machineId, string localSequence, SignalRSigninMsg msg)
+ {
+ _baseConnector = new FBoxConnector(machineId, localSequence, msg);
+ }
+
+ public override bool CheckRight(byte[] content)
+ {
+ if (content != null && content.Length == 6 && Encoding.ASCII.GetString(content) == "NoData")
+ {
+ return false;
+ }
+ return base.CheckRight(content);
+ }
+ }
+}
diff --git a/Modbus.Net/ModBus.Net/FBox/FBoxSignalRProtocalLinker.cs b/Modbus.Net/ModBus.Net/FBox/FBoxSignalRProtocalLinker.cs
index 9b041c1..ff29eb4 100644
--- a/Modbus.Net/ModBus.Net/FBox/FBoxSignalRProtocalLinker.cs
+++ b/Modbus.Net/ModBus.Net/FBox/FBoxSignalRProtocalLinker.cs
@@ -6,15 +6,10 @@ using System.Threading.Tasks;
namespace ModBus.Net.FBox
{
- public class FBoxSignalRProtocalLinker : SignalRProtocalLinker
+ public class FBoxSignalRProtocalLinker : FBoxProtocalLinker
{
public FBoxSignalRProtocalLinker(string machineId, string localSequence, SignalRSigninMsg msg) : base(machineId, localSequence, msg)
{
}
-
- public override bool CheckRight(byte[] content)
- {
- return true;
- }
}
}
diff --git a/Modbus.Net/ModBus.Net/FBox/SignalRConnector.cs b/Modbus.Net/ModBus.Net/FBox/SignalRConnector.cs
deleted file mode 100644
index 1f45b6c..0000000
--- a/Modbus.Net/ModBus.Net/FBox/SignalRConnector.cs
+++ /dev/null
@@ -1,747 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNet.SignalR.Client;
-using Newtonsoft.Json;
-using Thinktecture.IdentityModel.Client;
-
-namespace ModBus.Net.FBox
-{
- public struct SignalRSigninMsg
- {
- public string ClientId { get; set; }
- public string ClientSecret { get; set; }
- public string UserId { get; set; }
- public string Password { get; set; }
- public string SigninAdditionalValues { get; set; }
- public SignalRServer SignalRServer { get; set; }
- }
-
- public class SignalRConnector : BaseConnector
- {
- private static Dictionary _oauth2;
- private static Dictionary _refreshToken;
-
- private static Dictionary _httpClient;
- private static Dictionary _httpClient2;
- private static Dictionary _hubConnections;
- private static Dictionary _boxUidMsg;
- private static Dictionary _boxUidSessionId;
- private static Dictionary> _boxUidDataGroups;
- private static Dictionary _connectionTokenState;
- private static Dictionary _groupNameUid;
- private static Dictionary _groupNameBoxUid;
- private static Dictionary> _machineData;
- private static Dictionary> _machineDataType;
- private static Dictionary _boxUidBoxNo;
- private static HashSet _connectedDataGroupUid;
-
- public override string ConnectionToken { get; }
-
- private Timer _timer;
-
- private string MachineId => ConnectionToken.Split(',')[0];
-
- private string LocalSequence => ConnectionToken.Split(',')[1];
-
- private static readonly AsyncLock _lock = new AsyncLock();
-
- private bool _connected;
- public override bool IsConnected => _connected;
- private SignalRSigninMsg Msg { get; set;}
-
- private Constants _constants;
- private Constants Constants => _constants ?? (_constants = new Constants());
-
- public SignalRConnector(string machineId, string localSequence, SignalRSigninMsg msg)
- {
- Constants.SignalRServer = msg.SignalRServer;
- System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
- ConnectionToken = machineId + "," + localSequence;
- if (_oauth2 == null)
- {
- _httpClient = new Dictionary();
- _oauth2 = new Dictionary();
- _refreshToken = new Dictionary();
- _boxUidMsg = new Dictionary();
- _hubConnections = new Dictionary();
- _httpClient2 = new Dictionary();
- _machineData = new Dictionary>();
- _machineDataType = new Dictionary>();
- _boxUidSessionId = new Dictionary();
- _connectionTokenState = new Dictionary();
- _groupNameUid = new Dictionary();
- _groupNameBoxUid = new Dictionary();
- _boxUidDataGroups = new Dictionary>();
- _boxUidBoxNo = new Dictionary();
- _connectedDataGroupUid = new HashSet();
- _timer = new Timer(ChangeToken, null, 3600 * 1000 * 4, 3600 * 1000 * 4);
- }
- Msg = msg;
- }
-
- private async void ChangeToken(object sender)
- {
- try
- {
- using (await _lock.LockAsync())
- {
- var tokenResponse = await _oauth2[Msg].RequestRefreshTokenAsync(_refreshToken[Msg]);
- _refreshToken[Msg] = tokenResponse.RefreshToken;
- _httpClient[Msg].SetBearerToken(tokenResponse.AccessToken);
-
- foreach (var boxUidMsg in _boxUidMsg)
- {
- if (boxUidMsg.Value.Equals(Msg))
- {
- if (_httpClient2.ContainsKey(boxUidMsg.Key) && _hubConnections.ContainsKey(boxUidMsg.Key))
- _httpClient2[boxUidMsg.Key].SetBearerToken(tokenResponse.AccessToken);
- _hubConnections[boxUidMsg.Key].Stop();
- _hubConnections[boxUidMsg.Key].Headers["Authorization"] = "Bearer " +
- tokenResponse.AccessToken;
- await _hubConnections[boxUidMsg.Key].Start();
- var localDataGroups = _boxUidDataGroups[boxUidMsg.Key];
- foreach (var localDataGroup in localDataGroups)
- {
- if (_connectedDataGroupUid.Contains(localDataGroup.Uid))
- {
- await
- _httpClient2[boxUidMsg.Key].PostAsync(
- "dmon/group/" + localDataGroup.Uid + "/start", null);
- }
- }
- }
- }
- }
- }
- catch (Exception e)
- {
- Console.WriteLine("Retoken failed." + e.Message);
- }
- Console.WriteLine("Retoken success.");
- }
-
- public override bool Connect()
- {
- return AsyncHelper.RunSync(ConnectAsync);
- }
-
- public override async Task ConnectAsync()
- {
- try
- {
- using (await _lock.LockAsync())
- {
- if (!_oauth2.ContainsKey(Msg))
- {
- _oauth2.Add(Msg, new OAuth2Client(
- new Uri(Constants.TokenEndpoint),
- Msg.ClientId,
- Msg.ClientSecret
- ));
- var tokenResponse = await _oauth2[Msg].RequestResourceOwnerPasswordAsync
- (
- Msg.UserId,
- Msg.Password,
- Msg.SigninAdditionalValues
- );
- if (tokenResponse != null)
- {
- _refreshToken.Add(Msg, tokenResponse.RefreshToken);
- AsyncHelper.RunSync(()=> CallService(Msg, tokenResponse.AccessToken));
- }
- }
-
-
- if (_groupNameBoxUid.ContainsKey(ConnectionToken) && _hubConnections.ContainsKey(_groupNameBoxUid[ConnectionToken]) &&
- _httpClient2.ContainsKey(_groupNameBoxUid[ConnectionToken]) &&
- _groupNameUid.ContainsKey(ConnectionToken))
- {
- await
- _httpClient2[_groupNameBoxUid[ConnectionToken]].PostAsync(
- "dmon/group/" + _groupNameUid[ConnectionToken] + "/start",
- null);
- _connectedDataGroupUid.Add(_groupNameUid[ConnectionToken]);
- _connected = true;
- Console.WriteLine("SignalR Connected success");
- return true;
- }
- else
- {
- _connected = false;
- Console.WriteLine("SignalR Connected failed");
- return false;
- }
- }
- }
- catch (Exception e)
- {
- if (_oauth2.ContainsKey(Msg))
- {
- _oauth2.Remove(Msg);
- }
- Console.WriteLine("SignalR Connected failed");
- return false;
- }
- }
-
- private async Task CallService(SignalRSigninMsg msg, string token)
- {
-
- var guid = Guid.NewGuid().ToString();
-
- var baseAddress = Constants.AspNetWebApiSampleApi;
-
- if (!_httpClient.ContainsKey(msg))
- {
- _httpClient.Add(msg, new HttpClient
- {
- BaseAddress = new Uri(baseAddress)
- });
- }
-
- _httpClient[msg].SetBearerToken(token);
-
- //var response = await _httpClient.GetStringAsync("device/spec");
-
- //List deviceSpecs = JsonConvert.DeserializeObject>(response);
- //deviceSpecs = deviceSpecs.OrderBy(p => p.Id).ToList();
-
-
- var response = await _httpClient[msg].GetStringAsync("boxgroup");
-
- List boxGroups = JsonConvert.DeserializeObject>(response);
-
- foreach (var boxGroup in boxGroups)
- {
- var boxes = boxGroup.BoxRegs;
- foreach (var box in boxes)
- {
- var sessionId = box.Box.CurrentSessionId;
- var baseUrl = box.Box.CommServer.ApiBaseUrl;
- var signalrUrl = box.Box.CommServer.SignalRUrl;
- var boxUid = box.Box.Uid;
- var boxNo = box.Box.BoxNo;
-
- //var currentStat = box.Box.ConnectionState;
-
- var client2 = new HttpClient
- {
- BaseAddress = new Uri(baseUrl)
- };
- client2.SetBearerToken(token);
- client2.DefaultRequestHeaders.Add("X-FBox-ClientId", guid);
-
- response = await client2.GetStringAsync("box/" + box.Box.Uid + "/dmon/def/grouped");
-
- if (_boxUidDataGroups.ContainsKey(boxUid))
- {
- break;
- }
-
- List dataGroups = JsonConvert.DeserializeObject>(response);
- _boxUidDataGroups.Add(boxUid, dataGroups);
- lock (_boxUidSessionId)
- {
- _boxUidSessionId.Add(boxUid, sessionId);
- }
- _boxUidBoxNo.Add(boxUid, boxNo);
- _boxUidMsg.Add(boxUid, Msg);
-
- var hubConnection = new HubConnection(signalrUrl);
- _hubConnections.Add(boxUid, hubConnection);
- hubConnection.Headers.Add("Authorization", "Bearer " + token);
- hubConnection.Headers.Add("X-FBox-ClientId", guid);
- hubConnection.Headers.Add("X-FBox-Session", sessionId.ToString());
-
- IHubProxy dataHubProxy = hubConnection.CreateHubProxy("clientHub");
- dataHubProxy.On>("dMonUpdateValue",
- (boxSessionId, values) =>
- {
- lock (_boxUidSessionId)
- {
- if (_boxUidSessionId.ContainsValue(boxSessionId))
- {
- Console.WriteLine($"Box session {boxSessionId} return at {DateTime.Now}");
- var localBoxUid =
- _boxUidSessionId.FirstOrDefault(p => p.Value == boxSessionId).Key;
- var localBoxNo = _boxUidBoxNo[localBoxUid];
-
- foreach (var value in values)
- {
- if (value.Status != 0)
- {
- lock (_machineData)
- {
- if (_boxUidDataGroups.ContainsKey(localBoxUid))
- {
- foreach (var dataGroupInner in _boxUidDataGroups[localBoxUid])
- {
- var dMonEntry =
- dataGroupInner.DMonEntries.FirstOrDefault(
- p => p.Uid == value.Id);
- if (dMonEntry != null &&
- _machineData.ContainsKey(localBoxNo + "," +
- dataGroupInner.Name))
- {
- if (_machineData[localBoxNo + "," + dataGroupInner.Name]
- .ContainsKey(dMonEntry.Desc))
- {
- _machineData[localBoxNo + "," + dataGroupInner.Name]
- .Remove(dMonEntry.Desc);
- }
- }
- }
- }
- }
- return;
- }
- lock (_machineData)
- {
- if (_boxUidDataGroups.ContainsKey(localBoxUid))
- {
- foreach (var dataGroupInner in _boxUidDataGroups[localBoxUid])
- {
- if (dataGroupInner.DMonEntries.Any(p => p.Uid == value.Id))
- {
- if (
- !_machineData.ContainsKey(localBoxNo + "," +
- dataGroupInner.Name))
- {
- _machineData.Add(localBoxNo + "," + dataGroupInner.Name,
- new Dictionary());
- }
- if (_machineData[localBoxNo + "," + dataGroupInner.Name] ==
- null)
- {
- _machineData[localBoxNo + "," + dataGroupInner.Name] =
- new Dictionary();
- }
-
- var dMonEntry =
- dataGroupInner.DMonEntries.FirstOrDefault(
- p => p.Uid == value.Id);
-
- if (value.Value.HasValue && dMonEntry != null)
- {
- if (
- _machineData[localBoxNo + "," + dataGroupInner.Name]
- .ContainsKey(
- dMonEntry.Desc))
- {
- _machineData[localBoxNo + "," + dataGroupInner.Name]
- [dMonEntry.Desc] =
- value.Value.Value;
- }
- else
- {
- _machineData[localBoxNo + "," + dataGroupInner.Name]
- .Add(dMonEntry.Desc,
- value.Value.Value);
- }
- }
- break;
- }
- }
- }
- }
- }
- }
- }
- });
-
- dataHubProxy.On("boxConnectionStateChanged",
- async (newConnectionToken, getBoxUid, oldStatus, newStatus) =>
- {
- using (await _lock.LockAsync())
- {
- if (_httpClient2.ContainsKey(getBoxUid))
- {
- Console.WriteLine(
- $"Box uid {getBoxUid} change state at {DateTime.Now} new connectionToken {newConnectionToken} newStatus {newStatus}");
- sessionId = newConnectionToken;
- lock (_boxUidSessionId)
- {
- _boxUidSessionId[getBoxUid] = sessionId;
- }
-
- var localBoxNo = _boxUidBoxNo[getBoxUid];
- var localDataGroups = _boxUidDataGroups[getBoxUid];
- lock (_connectionTokenState)
- {
- foreach (var localDataGroup in localDataGroups)
- {
- if (
- !_connectionTokenState.ContainsKey(localBoxNo + "," +
- localDataGroup.Name))
- {
- _connectionTokenState.Add(localBoxNo + "," + localDataGroup.Name,
- newStatus);
- }
- else
- {
- _connectionTokenState[localBoxNo + "," + localDataGroup.Name] =
- newStatus;
- }
- }
- }
- _httpClient2[getBoxUid].DefaultRequestHeaders.Remove("X-FBox-Session");
- _httpClient2[getBoxUid].DefaultRequestHeaders.Add("X-FBox-Session",
- sessionId.ToString());
- _hubConnections[getBoxUid].Headers["X-FBox-Session"] = sessionId.ToString();
-
- if (_hubConnections[getBoxUid].State == ConnectionState.Disconnected)
- {
- await _hubConnections[getBoxUid].Start();
- }
-
- if (newStatus == 1)
- {
- foreach (var localDataGroup in localDataGroups)
- {
- if (_connectedDataGroupUid.Contains(localDataGroup.Uid))
- {
- await
- _httpClient2[getBoxUid].PostAsync(
- "dmon/group/" + localDataGroup.Uid + "/start", null);
- }
- }
- }
- }
- }
- });
-
- hubConnection.Error += async ex =>
- {
- Console.WriteLine(@"SignalR error: {0}", ex.Message);
- await ConnectRecovery(hubConnection);
- };
-
- hubConnection.Closed += async () =>
- {
- await ConnectRecovery(hubConnection);
- };
-
- ServicePointManager.DefaultConnectionLimit = 10;
-
- foreach (var dataGroup in dataGroups)
- {
- if (dataGroup == null) return;
-
- var groupUid = dataGroup.Uid;
- var groupName = dataGroup.Name;
-
- if (groupName != "(Default)" && groupName != "默认组" &&
- !_connectionTokenState.ContainsKey(boxNo + "," + groupName))
- {
- _connectionTokenState.Add(boxNo + "," + groupName, 1);
- }
- if (groupName != "(Default)" && groupName != "默认组" &&
- !_groupNameUid.ContainsKey(boxNo + "," + groupName))
- {
- _groupNameUid.Add(boxNo + "," + groupName, groupUid);
- }
- if (groupName != "(Default)" && groupName != "默认组" &&
- !_groupNameBoxUid.ContainsKey(boxNo + "," + groupName))
- {
- _groupNameBoxUid.Add(boxNo + "," + groupName, boxUid);
- }
- if (groupName != "(Default)" && groupName != "默认组" && !_httpClient2.ContainsKey(boxUid))
- {
- _httpClient2.Add(boxUid, client2);
- }
-
- if (!_machineDataType.ContainsKey(boxNo + "," + groupName))
- {
- _machineDataType.Add(boxNo + "," + groupName, new Dictionary());
- }
- foreach (var dMonEntry in dataGroup.DMonEntries)
- {
- Type type;
- switch (dMonEntry.DataType)
- {
- //位
- case 0:
- {
- type = typeof (bool);
- break;
- }
- //16位无符号
- case 1:
- {
- type = typeof (ushort);
- break;
- }
- //16位有符号
- case 2:
- {
- type = typeof (short);
- break;
- }
- //32位无符号
- case 11:
- {
- type = typeof (uint);
- break;
- }
- //32位有符号
- case 12:
- {
- type = typeof (int);
- break;
- }
- //16位BCD
- case 3:
- {
- type = typeof (short);
- break;
- }
- //32位BCD
- case 13:
- {
- type = typeof (int);
- break;
- }
- //浮点数
- case 16:
- {
- type = typeof (float);
- break;
- }
- //16位16进制
- case 4:
- {
- type = typeof (short);
- break;
- }
- //32位16进制
- case 14:
- {
- type = typeof (int);
- break;
- }
- //16位2进制
- case 5:
- {
- type = typeof (short);
- break;
- }
- //32位2进制
- case 15:
- {
- type = typeof (int);
- break;
- }
- default:
- {
- type = typeof (short);
- break;
- }
- }
-
- if (!_machineDataType[boxNo + "," + groupName].ContainsKey(dMonEntry.Desc))
- {
- _machineDataType[boxNo + "," + groupName].Add(dMonEntry.Desc, type);
- }
- else
- {
- _machineDataType[boxNo + "," + groupName][dMonEntry.Desc] = type;
- }
- }
- }
-
- await hubConnection.Start();
- await dataHubProxy.Invoke("updateClientId", guid);
- }
-
- }
- }
-
- private async Task ConnectRecovery(HubConnection hubConnection)
- {
- using (await _lock.LockAsync())
- {
- string getBoxUid;
- lock (_boxUidSessionId)
- {
- getBoxUid =
- _boxUidSessionId.FirstOrDefault(
- p => p.Value == int.Parse(hubConnection.Headers["X-FBox-Session"])).Key;
- }
- try
- {
- if (hubConnection.State != ConnectionState.Connected)
- {
- try
- {
- hubConnection.Stop();
- }
- catch
- {
- // ignored
- }
-
- await hubConnection.Start();
- var localDataGroups = _boxUidDataGroups[getBoxUid];
- foreach (var localDataGroup in localDataGroups)
- {
- if (_connectedDataGroupUid.Contains(localDataGroup.Uid))
- {
- await
- _httpClient2[getBoxUid].PostAsync(
- "dmon/group/" + localDataGroup.Uid + "/start", null);
- }
- }
- }
- }
- catch
- {
- if (_boxUidBoxNo.ContainsKey(getBoxUid))
- {
- var localBoxNo = _boxUidBoxNo[getBoxUid];
- lock (_machineData)
- {
- foreach (var machineDataUnit in _machineData)
- {
- if (machineDataUnit.Key.Contains(localBoxNo))
- {
- _machineData.Remove(machineDataUnit.Key);
- }
- }
- }
- }
- var localDataGroups = _boxUidDataGroups[getBoxUid];
- foreach (var localDataGroup in localDataGroups)
- {
- _connectedDataGroupUid.RemoveWhere(p => p == localDataGroup.Uid);
- }
- _connected = false;
- }
- }
- }
-
- public override bool Disconnect()
- {
- return AsyncHelper.RunSync(DisconnectAsync);
- }
-
- public async Task DisconnectAsync()
- {
- try
- {
- using (await _lock.LockAsync())
- {
- if (_groupNameBoxUid.ContainsKey(ConnectionToken) && _hubConnections.ContainsKey(_groupNameBoxUid[ConnectionToken]) &&
- _httpClient2.ContainsKey(_groupNameBoxUid[ConnectionToken]) &&
- _groupNameUid.ContainsKey(ConnectionToken))
- {
- await
- _httpClient2[_groupNameBoxUid[ConnectionToken]].PostAsync(
- "dmon/group/" + _groupNameUid[ConnectionToken] + "/stop",
- null);
- _connectedDataGroupUid.RemoveWhere(p => p == _groupNameUid[ConnectionToken]);
- _connected = false;
- Console.WriteLine("SignalR Disconnect success");
- return true;
- }
- else
- {
- Console.WriteLine("SignalR Disconnect failed");
- return false;
- }
- }
- }
- catch
- {
- Console.WriteLine("SignalR Disconnect failed");
- return false;
- }
- }
-
- public override bool SendMsgWithoutReturn(byte[] message)
- {
- throw new NotImplementedException();
- }
-
- public override Task SendMsgWithoutReturnAsync(byte[] message)
- {
- throw new NotImplementedException();
- }
-
- public override byte[] SendMsg(byte[] message)
- {
- if (!_httpClient.ContainsKey(Msg))
- {
- _connected = false;
- return null;
- }
-
- var formater = new AddressFormaterFBox();
- var translator = new AddressTranslatorFBox();
-
- byte[] ans;
-
- lock (_connectionTokenState)
- {
- if (_connectionTokenState.ContainsKey(ConnectionToken) && _connectionTokenState[ConnectionToken] != 1)
- {
- _connected = false;
- Console.WriteLine($"Return Value Rejected with connectionToken {ConnectionToken}");
- return null;
- }
- }
-
- lock (_machineData)
- {
- if (!_machineData.ContainsKey(ConnectionToken) || !_machineDataType.ContainsKey(ConnectionToken))
- {
- _connected = false;
- Console.WriteLine($"Return Value Rejected with connectionToken {ConnectionToken}");
- return null;
- }
- var machineDataValue = _machineData[ConnectionToken];
- var machineDataType = _machineDataType[ConnectionToken];
- if (machineDataValue != null && machineDataType.Count == 0)
- {
- _connected = false;
- Console.WriteLine($"Return Value Rejected with connectionToken {ConnectionToken}");
- return null;
- }
- int pos = 0;
- int area = BigEndianValueHelper.Instance.GetInt(message, ref pos);
- int address = BigEndianValueHelper.Instance.GetInt(message, ref pos);
- //short count = BigEndianValueHelper.Instance.GetShort(message, ref pos);
- object[] dataAns = new object[1];
- try
- {
- dataAns[0] =
- Convert.ChangeType(
- machineDataValue[formater.FormatAddress(translator.GetAreaName(area), address)],
- machineDataType[formater.FormatAddress(translator.GetAreaName(area), address)]);
- }
- catch (Exception)
- {
- dataAns[0] =
- Convert.ChangeType(
- 0,
- machineDataType[formater.FormatAddress(translator.GetAreaName(area), address)]);
- }
- finally
- {
- ans = BigEndianValueHelper.Instance.ObjectArrayToByteArray(dataAns);
- }
- }
-
- return ans;
- }
-
- public override Task SendMsgAsync(byte[] message)
- {
- return Task.Factory.StartNew(() => SendMsg(message));
- }
- }
-}
diff --git a/Modbus.Net/ModBus.Net/FBox/SignalRProtocalLinker.cs b/Modbus.Net/ModBus.Net/FBox/SignalRProtocalLinker.cs
deleted file mode 100644
index 888bb9b..0000000
--- a/Modbus.Net/ModBus.Net/FBox/SignalRProtocalLinker.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace ModBus.Net.FBox
-{
- public class SignalRProtocalLinker : ProtocalLinker
- {
- protected SignalRProtocalLinker(string machineId, string localSequence, SignalRSigninMsg msg)
- {
- _baseConnector = new SignalRConnector(machineId, localSequence, msg);
- }
- }
-}
diff --git a/Modbus.Net/ModBus.Net/ModBus.Net.csproj b/Modbus.Net/ModBus.Net/ModBus.Net.csproj
index a48910a..ffb5bb6 100644
--- a/Modbus.Net/ModBus.Net/ModBus.Net.csproj
+++ b/Modbus.Net/ModBus.Net/ModBus.Net.csproj
@@ -47,6 +47,30 @@
..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll
True
+
+ False
+ ..\packages_local\Opc.Ua.Client.dll
+
+
+ False
+ ..\packages_local\Opc.Ua.Configuration.dll
+
+
+ False
+ ..\packages_local\Opc.Ua.Core.dll
+
+
+ False
+ ..\packages_local\OpcComRcw.dll
+
+
+ False
+ ..\packages_local\OpcNetApi.dll
+
+
+ False
+ ..\packages_local\OpcNetApi.Com.dll
+
@@ -97,14 +121,23 @@
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -131,6 +164,12 @@
+
+
+ {4f43b6f0-0c32-4c34-978e-9b8b5b0b6e80}
+ h-opc
+
+
+
\ No newline at end of file
diff --git a/Modbus.Net/h-opc/packages.config b/Modbus.Net/h-opc/packages.config
new file mode 100644
index 0000000..34147bc
--- /dev/null
+++ b/Modbus.Net/h-opc/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 12d244f..ff33d84 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,8 @@ Caution: I really want to implement the COM communication system, but nowaday Us
Caution2: In the current version, you can't get bit or set bit in this library. Please get a byte, change the bit value in the byte, and set to PLC. I will fix this bug in future.
+Reference: "h-opc" is linked by [https://github.com/hylasoft-usa/h-opc](https://github.com/hylasoft-usa/h-opc)
+
Table of Content:
* [Features](#features)
* [Usage](#usage)
@@ -17,6 +19,7 @@ Table of Content:
* A open platform that you can easy to extend a industrial communication protocal.
* Modbus Tcp protocal.
* Siemens Tcp protocal (acturally it is the same as Profinet)
+* OPC DA protocal.
* All communications can be asyncronized.
* A task manager that you can easily manage multiple connections.
* .net framework 4.6 and Visual Studio 2015 support.