2016-01-28 update 1
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
For more information on how to configure your ASP.NET application, please visit
|
||||
http://go.microsoft.com/fwlink/?LinkId=301879
|
||||
@@ -6,12 +6,12 @@
|
||||
<configuration>
|
||||
<configSections>
|
||||
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
|
||||
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
|
||||
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
|
||||
</configSections>
|
||||
<connectionStrings>
|
||||
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-CrossLampControl.WebApi-20140912112502.mdf;Initial Catalog=aspnet-CrossLampControl.WebApi-20140912112502;Integrated Security=True" providerName="System.Data.SqlClient"/>
|
||||
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-CrossLampControl.WebApi-20140912112502.mdf;Initial Catalog=aspnet-CrossLampControl.WebApi-20140912112502;Integrated Security=True" providerName="System.Data.SqlClient" />
|
||||
</connectionStrings>
|
||||
<appSettings/>
|
||||
<appSettings />
|
||||
<!--
|
||||
有关 web.config 更改的说明,请参见 http://go.microsoft.com/fwlink/?LinkId=235367。
|
||||
|
||||
@@ -21,61 +21,61 @@
|
||||
</system.Web>
|
||||
-->
|
||||
<system.web>
|
||||
<authentication mode="None"/>
|
||||
<compilation debug="true" targetFramework="4.6"/>
|
||||
<httpRuntime targetFramework="4.5"/>
|
||||
<authentication mode="None" />
|
||||
<compilation debug="true" targetFramework="4.6" />
|
||||
<httpRuntime targetFramework="4.5" />
|
||||
</system.web>
|
||||
<system.webServer>
|
||||
<modules>
|
||||
<remove name="FormsAuthentication"/>
|
||||
<remove name="FormsAuthentication" />
|
||||
</modules>
|
||||
<handlers>
|
||||
<remove name="ExtensionlessUrlHandler-Integrated-4.0"/>
|
||||
<remove name="OPTIONSVerbHandler"/>
|
||||
<remove name="TRACEVerbHandler"/>
|
||||
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
|
||||
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
|
||||
<remove name="OPTIONSVerbHandler" />
|
||||
<remove name="TRACEVerbHandler" />
|
||||
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0"/>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
<assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
|
||||
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="1.0.0.0-5.2.0.0" newVersion="5.2.0.0"/>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="1.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0"/>
|
||||
<assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0"/>
|
||||
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234"/>
|
||||
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<entityFramework>
|
||||
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
|
||||
<parameters>
|
||||
<parameter value="mssqllocaldb"/>
|
||||
<parameter value="mssqllocaldb" />
|
||||
</parameters>
|
||||
</defaultConnectionFactory>
|
||||
<providers>
|
||||
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"/>
|
||||
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
|
||||
</providers>
|
||||
</entityFramework>
|
||||
</configuration>
|
||||
@@ -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<CommunicationUnit> Combine(IEnumerable<AddressUnit> addresses)
|
||||
{
|
||||
return
|
||||
addresses.Select(
|
||||
address =>
|
||||
new CommunicationUnit()
|
||||
{
|
||||
Area = address.Area,
|
||||
Address = address.Address,
|
||||
DataType = address.DataType,
|
||||
GetCount = 1
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -69,6 +69,15 @@ namespace ModBus.Net {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 opcda://localhost/FBoxOpcServer 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string FBoxOpcDaHost {
|
||||
get {
|
||||
return ResourceManager.GetString("FBoxOpcDaHost", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 192.168.1.1 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -96,6 +105,15 @@ namespace ModBus.Net {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 opcda://localhost/... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string OpcDaHost {
|
||||
get {
|
||||
return ResourceManager.GetString("OpcDaHost", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 102 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="COM" xml:space="preserve">
|
||||
<value>COM1</value>
|
||||
</data>
|
||||
<data name="FBoxOpcDaHost" xml:space="preserve">
|
||||
<value>opcda://localhost/FBoxOpcServer</value>
|
||||
</data>
|
||||
<data name="IP" xml:space="preserve">
|
||||
<value>192.168.1.1</value>
|
||||
</data>
|
||||
@@ -129,6 +132,9 @@
|
||||
<data name="ModbusPort" xml:space="preserve">
|
||||
<value>502</value>
|
||||
</data>
|
||||
<data name="OpcDaHost" xml:space="preserve">
|
||||
<value>opcda://localhost/...</value>
|
||||
</data>
|
||||
<data name="SiemensPort" xml:space="preserve">
|
||||
<value>102</value>
|
||||
</data>
|
||||
|
||||
621
Modbus.Net/ModBus.Net/FBox/FBoxConnector.cs
Normal file
621
Modbus.Net/ModBus.Net/FBox/FBoxConnector.cs
Normal file
@@ -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<string, double> _data;
|
||||
private Dictionary<string, Type> _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<string, double>();
|
||||
_dataType = new Dictionary<string, Type>();
|
||||
}
|
||||
|
||||
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<bool> 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<DeviceSpecSource> deviceSpecs = JsonConvert.DeserializeObject<List<DeviceSpecSource>>(response);
|
||||
//deviceSpecs = deviceSpecs.OrderBy(p => p.Id).ToList();
|
||||
|
||||
|
||||
var response = await _httpClient.GetStringAsync("boxgroup");
|
||||
|
||||
List<BoxGroup> boxGroups = JsonConvert.DeserializeObject<List<BoxGroup>>(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<DMonGroup> dataGroups = JsonConvert.DeserializeObject<List<DMonGroup>>(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<int, List<GetValue>>("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<string, double>();
|
||||
}
|
||||
|
||||
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<int, string, int, int>("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<string, Type>();
|
||||
|
||||
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<bool> 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<bool> 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<string, double> 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<byte[]> SendMsgAsync(byte[] message)
|
||||
{
|
||||
return Task.Factory.StartNew(() => SendMsg(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Modbus.Net/ModBus.Net/FBox/FBoxProtocalLinker.cs
Normal file
25
Modbus.Net/ModBus.Net/FBox/FBoxProtocalLinker.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SignalRSigninMsg, OAuth2Client> _oauth2;
|
||||
private static Dictionary<SignalRSigninMsg, string> _refreshToken;
|
||||
|
||||
private static Dictionary<SignalRSigninMsg, HttpClient> _httpClient;
|
||||
private static Dictionary<string, HttpClient> _httpClient2;
|
||||
private static Dictionary<string, HubConnection> _hubConnections;
|
||||
private static Dictionary<string, SignalRSigninMsg> _boxUidMsg;
|
||||
private static Dictionary<string, int> _boxUidSessionId;
|
||||
private static Dictionary<string, List<DMonGroup>> _boxUidDataGroups;
|
||||
private static Dictionary<string, int> _connectionTokenState;
|
||||
private static Dictionary<string, string> _groupNameUid;
|
||||
private static Dictionary<string, string> _groupNameBoxUid;
|
||||
private static Dictionary<string, Dictionary<string, double>> _machineData;
|
||||
private static Dictionary<string, Dictionary<string, Type>> _machineDataType;
|
||||
private static Dictionary<string, string> _boxUidBoxNo;
|
||||
private static HashSet<string> _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<SignalRSigninMsg, HttpClient>();
|
||||
_oauth2 = new Dictionary<SignalRSigninMsg, OAuth2Client>();
|
||||
_refreshToken = new Dictionary<SignalRSigninMsg, string>();
|
||||
_boxUidMsg = new Dictionary<string, SignalRSigninMsg>();
|
||||
_hubConnections = new Dictionary<string, HubConnection>();
|
||||
_httpClient2 = new Dictionary<string, HttpClient>();
|
||||
_machineData = new Dictionary<string, Dictionary<string, double>>();
|
||||
_machineDataType = new Dictionary<string, Dictionary<string, Type>>();
|
||||
_boxUidSessionId = new Dictionary<string, int>();
|
||||
_connectionTokenState = new Dictionary<string, int>();
|
||||
_groupNameUid = new Dictionary<string, string>();
|
||||
_groupNameBoxUid = new Dictionary<string, string>();
|
||||
_boxUidDataGroups = new Dictionary<string, List<DMonGroup>>();
|
||||
_boxUidBoxNo = new Dictionary<string, string>();
|
||||
_connectedDataGroupUid = new HashSet<string>();
|
||||
_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<bool> 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<DeviceSpecSource> deviceSpecs = JsonConvert.DeserializeObject<List<DeviceSpecSource>>(response);
|
||||
//deviceSpecs = deviceSpecs.OrderBy(p => p.Id).ToList();
|
||||
|
||||
|
||||
var response = await _httpClient[msg].GetStringAsync("boxgroup");
|
||||
|
||||
List<BoxGroup> boxGroups = JsonConvert.DeserializeObject<List<BoxGroup>>(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<DMonGroup> dataGroups = JsonConvert.DeserializeObject<List<DMonGroup>>(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<int, List<GetValue>>("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<string, double>());
|
||||
}
|
||||
if (_machineData[localBoxNo + "," + dataGroupInner.Name] ==
|
||||
null)
|
||||
{
|
||||
_machineData[localBoxNo + "," + dataGroupInner.Name] =
|
||||
new Dictionary<string, double>();
|
||||
}
|
||||
|
||||
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<int, string, int, int>("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<string, Type>());
|
||||
}
|
||||
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<bool> 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<bool> 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<byte[]> SendMsgAsync(byte[] message)
|
||||
{
|
||||
return Task.Factory.StartNew(() => SendMsg(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,30 @@
|
||||
<HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Opc.Ua.Client, Version=1.2.334.4, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages_local\Opc.Ua.Client.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Opc.Ua.Configuration, Version=1.2.334.4, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages_local\Opc.Ua.Configuration.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Opc.Ua.Core, Version=1.2.334.4, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages_local\Opc.Ua.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpcComRcw, Version=2.0.105.1, Culture=neutral, PublicKeyToken=9a40e993cbface53, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages_local\OpcComRcw.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpcNetApi, Version=2.1.105.1, Culture=neutral, PublicKeyToken=9a40e993cbface53, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages_local\OpcNetApi.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpcNetApi.Com, Version=2.1.105.1, Culture=neutral, PublicKeyToken=9a40e993cbface53, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages_local\OpcNetApi.Com.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
@@ -97,14 +121,23 @@
|
||||
<Compile Include="Modbus\ModbusRtuProtocal.cs" />
|
||||
<Compile Include="Modbus\ModbusRtuProtocalLinker.cs" />
|
||||
<Compile Include="Modbus\ModbusTcpProtocalLinker.cs" />
|
||||
<Compile Include="OPC\AddressFormaterOpc.cs" />
|
||||
<Compile Include="OPC\DaClientExtend.cs" />
|
||||
<Compile Include="OPC\FBox\FBoxOpcDaManchine.cs" />
|
||||
<Compile Include="OPC\OpcDaConnector.cs" />
|
||||
<Compile Include="OPC\OpcDaMachine.cs" />
|
||||
<Compile Include="OPC\OpcDaProtocal.cs" />
|
||||
<Compile Include="OPC\OpcDaProtocalLinker.cs" />
|
||||
<Compile Include="OPC\OpcDaUtility.cs" />
|
||||
<Compile Include="OPC\OpcProtocal.cs" />
|
||||
<Compile Include="ProtocalLinker.cs" />
|
||||
<Compile Include="ProtocalLinkerBytesExtend.cs" />
|
||||
<Compile Include="ProtocalUnit.cs" />
|
||||
<Compile Include="Modbus\ModbusProtocal.cs" />
|
||||
<Compile Include="Modbus\ModbusTcpProtocal.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="FBox\SignalRConnector.cs" />
|
||||
<Compile Include="FBox\SignalRProtocalLinker.cs" />
|
||||
<Compile Include="FBox\FBoxConnector.cs" />
|
||||
<Compile Include="FBox\FBoxProtocalLinker.cs" />
|
||||
<Compile Include="Siemens\AddressFormaterSiemens.cs" />
|
||||
<Compile Include="Siemens\AddressTranslatorSiemens.cs" />
|
||||
<Compile Include="Siemens\SiemensMachine.cs" />
|
||||
@@ -131,6 +164,12 @@
|
||||
<None Include="LICENSE.md" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\h-opc\h-opc.csproj">
|
||||
<Project>{4f43b6f0-0c32-4c34-978e-9b8b5b0b6e80}</Project>
|
||||
<Name>h-opc</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
34
Modbus.Net/ModBus.Net/OPC/AddressFormaterOpc.cs
Normal file
34
Modbus.Net/ModBus.Net/OPC/AddressFormaterOpc.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModBus.Net.OPC
|
||||
{
|
||||
public class AddressFormaterOpc : AddressFormater
|
||||
{
|
||||
public BaseMachine Machine { get; set; }
|
||||
|
||||
protected Func<BaseMachine, AddressUnit, string[]> TagGeter { get; set; }
|
||||
|
||||
public AddressFormaterOpc(Func<BaseMachine, AddressUnit, string[]> tagGeter)
|
||||
{
|
||||
TagGeter = tagGeter;
|
||||
}
|
||||
|
||||
public override string FormatAddress(string area, int address)
|
||||
{
|
||||
var findAddress = Machine?.GetAddresses.FirstOrDefault(p => p.Area == area && p.Address == address);
|
||||
if (findAddress == null) return null;
|
||||
var strings = TagGeter(Machine, findAddress);
|
||||
var ans = "";
|
||||
for (int i = 0; i < strings.Length; i++)
|
||||
{
|
||||
ans += strings[i].Trim().Replace(" ", "") + ".";
|
||||
}
|
||||
ans = ans.Substring(0, ans.Length - 1);
|
||||
return ans;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Modbus.Net/ModBus.Net/OPC/DaClientExtend.cs
Normal file
50
Modbus.Net/ModBus.Net/OPC/DaClientExtend.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Opc.Da;
|
||||
|
||||
namespace Hylasoft.Opc.Da
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Read value full result
|
||||
/// </summary>
|
||||
public class OpcValueResult
|
||||
{
|
||||
public object Value { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public bool QualityGood { get; set; }
|
||||
}
|
||||
|
||||
public class MyDaClient : DaClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
|
||||
public OpcValueResult Read(string tag)
|
||||
{
|
||||
var item = new Item { ItemName = tag };
|
||||
var result = _server.Read(new[] { item })[0];
|
||||
CheckResult(result, tag);
|
||||
return new OpcValueResult()
|
||||
{
|
||||
Value = result.Value,
|
||||
Timestamp = result.Timestamp,
|
||||
QualityGood = result.Quality == Quality.Good
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag asynchronusly
|
||||
/// </summary>
|
||||
public Task<OpcValueResult> ReadAsync(string tag)
|
||||
{
|
||||
return Task.Run(() => Read(tag));
|
||||
}
|
||||
|
||||
public MyDaClient(Uri serverUrl) : base(serverUrl)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Modbus.Net/ModBus.Net/OPC/FBox/FBoxOpcDaManchine.cs
Normal file
37
Modbus.Net/ModBus.Net/OPC/FBox/FBoxOpcDaManchine.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModBus.Net.OPC.FBox
|
||||
{
|
||||
public class FBoxOpcDaMachine : OpcDaMachine
|
||||
{
|
||||
public string LocalSequence { get; set; }
|
||||
|
||||
public string LinkerName { get; set; }
|
||||
|
||||
public FBoxOpcDaMachine(string localSequence, string linkerName,
|
||||
IEnumerable<AddressUnit> getAddresses, bool keepConnect) : base(ConfigurationManager.FBoxOpcDaHost, getAddresses, keepConnect)
|
||||
{
|
||||
LocalSequence = localSequence;
|
||||
LinkerName = linkerName;
|
||||
AddressFormater =
|
||||
new AddressFormaterOpc(
|
||||
((machine, unit) =>
|
||||
new string[]
|
||||
{
|
||||
"他人分享", ((FBoxOpcDaMachine) machine).LinkerName, ((FBoxOpcDaMachine) machine).LocalSequence,
|
||||
unit.Name
|
||||
}));
|
||||
((AddressFormaterOpc)AddressFormater).Machine = this;
|
||||
}
|
||||
|
||||
public FBoxOpcDaMachine(string localSequence, string linkerName,
|
||||
IEnumerable<AddressUnit> getAddresses)
|
||||
: this(localSequence, linkerName, getAddresses, false)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Modbus.Net/ModBus.Net/OPC/OpcDaConnector.cs
Normal file
123
Modbus.Net/ModBus.Net/OPC/OpcDaConnector.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Eventing.Reader;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Hylasoft.Opc.Common;
|
||||
using Hylasoft.Opc.Da;
|
||||
|
||||
namespace ModBus.Net.OPC
|
||||
{
|
||||
public class OpcDaConnector : BaseConnector
|
||||
{
|
||||
public override string ConnectionToken { get; }
|
||||
|
||||
protected bool _connect;
|
||||
public override bool IsConnected => _connect;
|
||||
|
||||
protected static Dictionary<string, OpcDaConnector> _instances = new Dictionary<string, OpcDaConnector>();
|
||||
protected MyDaClient _daClient;
|
||||
|
||||
protected OpcDaConnector(string host)
|
||||
{
|
||||
ConnectionToken = host;
|
||||
}
|
||||
|
||||
public static OpcDaConnector Instance(string host)
|
||||
{
|
||||
if (!_instances.ContainsKey(host))
|
||||
{
|
||||
var connector = new OpcDaConnector(host);
|
||||
_instances.Add(host, connector);
|
||||
}
|
||||
return _instances[host];
|
||||
}
|
||||
|
||||
public override bool Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
_daClient = new MyDaClient(new Uri(ConnectionToken));
|
||||
_daClient.Connect();
|
||||
_connect = true;
|
||||
AddInfo("client connected.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddInfo("client connected exception: " + ex.Message);
|
||||
AddInfo("connect failed.");
|
||||
_connect = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<bool> ConnectAsync()
|
||||
{
|
||||
return Task.FromResult(Connect());
|
||||
}
|
||||
|
||||
public override bool Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
_daClient?.Dispose();
|
||||
_daClient = null;
|
||||
_connect = false;
|
||||
AddInfo("client disconnected successfully.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddInfo("client disconnected exception: " + ex.Message);
|
||||
_connect = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool SendMsgWithoutReturn(byte[] message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<bool> SendMsgWithoutReturnAsync(byte[] message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] SendMsg(byte[] message)
|
||||
{
|
||||
return AsyncHelper.RunSync(() => SendMsgAsync(message));
|
||||
}
|
||||
|
||||
public override async Task<byte[]> SendMsgAsync(byte[] message)
|
||||
{
|
||||
try
|
||||
{
|
||||
string tag = Encoding.UTF8.GetString(message);
|
||||
var result = await _daClient.ReadAsync(tag);
|
||||
if (result.QualityGood)
|
||||
{
|
||||
return BigEndianValueHelper.Instance.GetBytes(result.Value, result.Value.GetType());
|
||||
}
|
||||
else
|
||||
{
|
||||
return Encoding.ASCII.GetBytes("NoData");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//AddInfo("opc client exception:" + e);
|
||||
return Encoding.ASCII.GetBytes("NoData");
|
||||
//return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void AddInfo(string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Modbus.Net/ModBus.Net/OPC/OpcDaMachine.cs
Normal file
23
Modbus.Net/ModBus.Net/OPC/OpcDaMachine.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModBus.Net.OPC
|
||||
{
|
||||
public class OpcDaMachine : BaseMachine
|
||||
{
|
||||
public OpcDaMachine(string connectionString, IEnumerable<AddressUnit> getAddresses, bool keepConnect)
|
||||
: base(getAddresses, keepConnect)
|
||||
{
|
||||
BaseUtility = new OpcDaUtility(connectionString);
|
||||
AddressCombiner = new AddressCombinerSingle();
|
||||
}
|
||||
|
||||
public OpcDaMachine(string connectionString, IEnumerable<AddressUnit> getAddresses)
|
||||
: this(connectionString, getAddresses, false)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Modbus.Net/ModBus.Net/OPC/OpcDaProtocal.cs
Normal file
34
Modbus.Net/ModBus.Net/OPC/OpcDaProtocal.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModBus.Net.OPC
|
||||
{
|
||||
public class OpcDaProtocal : OpcProtocal
|
||||
{
|
||||
private int _connectTryCount;
|
||||
|
||||
private readonly string _host;
|
||||
|
||||
public OpcDaProtocal(string host)
|
||||
{
|
||||
_host = host;
|
||||
}
|
||||
|
||||
public override bool Connect()
|
||||
{
|
||||
return AsyncHelper.RunSync(ConnectAsync);
|
||||
}
|
||||
|
||||
public override async Task<bool> ConnectAsync()
|
||||
{
|
||||
_connectTryCount++;
|
||||
ProtocalLinker = new OpcDaProtocalLinker(_host);
|
||||
if (!await ProtocalLinker.ConnectAsync()) return false;
|
||||
_connectTryCount = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Modbus.Net/ModBus.Net/OPC/OpcDaProtocalLinker.cs
Normal file
30
Modbus.Net/ModBus.Net/OPC/OpcDaProtocalLinker.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModBus.Net.OPC
|
||||
{
|
||||
public class OpcDaProtocalLinker : ProtocalLinker
|
||||
{
|
||||
public OpcDaProtocalLinker() : this(ConfigurationManager.OpcDaHost)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OpcDaProtocalLinker(string host)
|
||||
{
|
||||
_baseConnector = OpcDaConnector.Instance(host);
|
||||
}
|
||||
|
||||
public override bool CheckRight(byte[] content)
|
||||
{
|
||||
if (content != null && content.Length == 6 && Encoding.ASCII.GetString(content) == "NoData")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CheckRight(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Modbus.Net/ModBus.Net/OPC/OpcDaUtility.cs
Normal file
43
Modbus.Net/ModBus.Net/OPC/OpcDaUtility.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModBus.Net.OPC
|
||||
{
|
||||
public class OpcDaUtility : BaseUtility
|
||||
{
|
||||
public OpcDaUtility(string connectionString)
|
||||
{
|
||||
ConnectionString = connectionString;
|
||||
AddressTranslator = null;
|
||||
Wrapper = new OpcDaProtocal(ConnectionString);
|
||||
}
|
||||
|
||||
public override void SetConnectionType(int connectionType)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<byte[]> GetDatasAsync(byte belongAddress, byte masterAddress, string startAddress, int getByteCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
var readRequestOpcInputStruct = new ReadRequestOpcInputStruct(startAddress, getByteCount);
|
||||
var readRequestOpcOutputStruct =
|
||||
await
|
||||
Wrapper.SendReceiveAsync(Wrapper[typeof(ReadRequestOpcProtocal)], readRequestOpcInputStruct) as ReadRequestOpcOutputStruct;
|
||||
return readRequestOpcOutputStruct?.GetValue;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<bool> SetDatasAsync(byte belongAddress, byte masterAddress, string startAddress, object[] setContents)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Modbus.Net/ModBus.Net/OPC/OpcProtocal.cs
Normal file
123
Modbus.Net/ModBus.Net/OPC/OpcProtocal.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModBus.Net.OPC
|
||||
{
|
||||
public abstract class OpcProtocal : BaseProtocal
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class ReadRequestOpcInputStruct : InputStruct
|
||||
{
|
||||
public ReadRequestOpcInputStruct(string tag, int getCount)
|
||||
{
|
||||
Tag = tag;
|
||||
GetCount = getCount;
|
||||
}
|
||||
|
||||
public string Tag { get; private set; }
|
||||
public int GetCount { get; private set; }
|
||||
}
|
||||
|
||||
public class ReadRequestOpcOutputStruct : OutputStruct
|
||||
{
|
||||
public ReadRequestOpcOutputStruct(byte[] value)
|
||||
{
|
||||
GetValue = value;
|
||||
}
|
||||
|
||||
public byte[] GetValue { get; private set; }
|
||||
}
|
||||
|
||||
public class ReadRequestOpcProtocal : SpecialProtocalUnit
|
||||
{
|
||||
public override byte[] Format(InputStruct message)
|
||||
{
|
||||
var r_message = (ReadRequestOpcInputStruct) message;
|
||||
return Format(Encoding.UTF8.GetBytes(r_message.Tag));
|
||||
}
|
||||
|
||||
public override OutputStruct Unformat(byte[] messageBytes, ref int pos)
|
||||
{
|
||||
return new ReadRequestOpcOutputStruct(messageBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/*public class WriteRequestSiemensInputStruct : InputStruct
|
||||
{
|
||||
public WriteRequestSiemensInputStruct(ushort pduRef, string startAddress, object[] writeValue, AddressTranslator addressTranslator)
|
||||
{
|
||||
PduRef = pduRef;
|
||||
var address = addressTranslator.AddressTranslate(startAddress, true);
|
||||
Offset = address.Key;
|
||||
int area = address.Value;
|
||||
Area = (byte)(area % 256);
|
||||
DbBlock = Area == 0x84 ? (ushort)(area / 256) : (ushort)0;
|
||||
WriteValue = writeValue;
|
||||
}
|
||||
|
||||
public ushort PduRef { get; private set; }
|
||||
public ushort DbBlock { get; private set; }
|
||||
public byte Area { get; private set; }
|
||||
public int Offset { get; private set; }
|
||||
public object[] WriteValue { get; private set; }
|
||||
}
|
||||
|
||||
public class WriteRequestSiemensOutputStruct : OutputStruct
|
||||
{
|
||||
public WriteRequestSiemensOutputStruct(ushort pduRef, SiemensAccessResult accessResult)
|
||||
{
|
||||
PduRef = pduRef;
|
||||
AccessResult = accessResult;
|
||||
}
|
||||
|
||||
public ushort PduRef { get; private set; }
|
||||
public SiemensAccessResult AccessResult { get; private set; }
|
||||
|
||||
}
|
||||
|
||||
public class WriteRequestSiemensProtocal : ProtocalUnit
|
||||
{
|
||||
public override byte[] Format(InputStruct message)
|
||||
{
|
||||
var r_message = (WriteRequestSiemensInputStruct)message;
|
||||
byte[] valueBytes = BigEndianValueHelper.Instance.ObjectArrayToByteArray(r_message.WriteValue);
|
||||
const byte protoId = 0x32;
|
||||
const byte rosctr = 0x01;
|
||||
const ushort redId = 0x0000;
|
||||
ushort pduRef = r_message.PduRef;
|
||||
const ushort parLg = 14; // 参数字节数(2+12的倍数),目前仅为14
|
||||
ushort datLg = (ushort)(4 + valueBytes.Length); // 数据字节数
|
||||
const byte serviceId = 0x05;
|
||||
const byte numberOfVariables = 1;
|
||||
const byte variableSpec = 0x12;
|
||||
const byte vAddrLg = 0x0A;
|
||||
const byte syntaxId = 0x10;
|
||||
const byte typeR = (byte)SiemensTypeCode.Byte;
|
||||
ushort numberOfElements = (ushort)valueBytes.Length;
|
||||
ushort dbBlock = r_message.DbBlock;
|
||||
byte area = r_message.Area;
|
||||
int offsetBit = r_message.Offset * 8;
|
||||
byte[] offsetBitBytes = BigEndianValueHelper.Instance.GetBytes(offsetBit);
|
||||
const byte reserved = 0x00;
|
||||
const byte type = (byte)SiemensDataType.OtherAccess;
|
||||
ushort numberOfWriteBits = (ushort)(valueBytes.Length * 8);
|
||||
return Format(new byte[7], protoId, rosctr, redId, pduRef, parLg, datLg, serviceId, numberOfVariables
|
||||
, variableSpec, vAddrLg, syntaxId, typeR, numberOfElements, dbBlock, area,
|
||||
offsetBitBytes.Skip(1).ToArray(), reserved, type, numberOfWriteBits, valueBytes);
|
||||
}
|
||||
|
||||
public override OutputStruct Unformat(byte[] messageBytes, ref int pos)
|
||||
{
|
||||
pos = 4;
|
||||
ushort pduRef = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
|
||||
pos = 14;
|
||||
byte accessResult = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
|
||||
return new WriteRequestSiemensOutputStruct(pduRef, (SiemensAccessResult)accessResult);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -35,5 +35,5 @@ using System.Runtime.InteropServices;
|
||||
// 方法是按如下所示使用“*”:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyVersion("0.4.0.0119")]
|
||||
[assembly: AssemblyFileVersion("0.4.0.0119")]
|
||||
[assembly: AssemblyVersion("0.4.5.0128")]
|
||||
[assembly: AssemblyFileVersion("0.4.5.0128")]
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModBus.Net
|
||||
|
||||
@@ -136,6 +136,78 @@ namespace ModBus.Net
|
||||
return BitConverter.GetBytes(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将object数字转换为byte数组
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Byte[] GetBytes(object value, Type type)
|
||||
{
|
||||
ValueHelper Instance;
|
||||
if (this is BigEndianValueHelper)
|
||||
{
|
||||
Instance = BigEndianValueHelper.Instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance = ValueHelper.Instance;
|
||||
}
|
||||
|
||||
switch (type.FullName)
|
||||
{
|
||||
case "System.Int16":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((short) value);
|
||||
return bytes;
|
||||
}
|
||||
case "System.Int32":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((int) value);
|
||||
return bytes;
|
||||
}
|
||||
case "System.Int64":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((long) value);
|
||||
return bytes;
|
||||
}
|
||||
case "System.UInt16":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((ushort) value);
|
||||
return bytes;
|
||||
}
|
||||
case "System.UInt32":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((uint) value);
|
||||
return bytes;
|
||||
}
|
||||
case "System.UInt64":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((ulong) value);
|
||||
return bytes;
|
||||
}
|
||||
case "System.Single":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((float) value);
|
||||
return bytes;
|
||||
}
|
||||
case "System.Double":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((double) value);
|
||||
return bytes;
|
||||
}
|
||||
case "System.Byte":
|
||||
{
|
||||
byte[] bytes = Instance.GetBytes((byte) value);
|
||||
return bytes;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new NotImplementedException("没有实现除整数以外的其它转换方式");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将byte数组中相应的位置转换为对应类型的数字
|
||||
/// </summary>
|
||||
|
||||
11
Modbus.Net/ModBus.Net/app.config
Normal file
11
Modbus.Net/ModBus.Net/app.config
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -18,6 +18,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
..\README.md = ..\README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "h-opc", "h-opc\h-opc.csproj", "{4F43B6F0-0C32-4C34-978E-9B8B5B0B6E80}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -44,6 +46,10 @@ Global
|
||||
{02CE1787-291A-47CB-A7F7-929DA1D920A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{02CE1787-291A-47CB-A7F7-929DA1D920A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{02CE1787-291A-47CB-A7F7-929DA1D920A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4F43B6F0-0C32-4C34-978E-9B8B5B0B6E80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4F43B6F0-0C32-4C34-978E-9B8B5B0B6E80}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4F43B6F0-0C32-4C34-978E-9B8B5B0B6E80}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4F43B6F0-0C32-4C34-978E-9B8B5B0B6E80}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
|
||||
27
Modbus.Net/h-opc/Common/ClientExtensions.cs
Normal file
27
Modbus.Net/h-opc/Common/ClientExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Useful extension methods for OPC Clients
|
||||
/// </summary>
|
||||
public static class ClientExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a tag from the OPC. If for whatever reason the read fails (Tag doesn't exist, server not available) returns a default value
|
||||
/// </summary>
|
||||
/// <param name="client">the opc client to use for the read</param>
|
||||
/// <param name="tag">The fully qualified identifier of the tag</param>
|
||||
/// <param name="defaultValue">the default value to read if the read fails</param>
|
||||
/// <returns></returns>
|
||||
public static T ReadOrdefault<T>(this IClient<Node> client, string tag, T defaultValue = default(T))
|
||||
{
|
||||
try
|
||||
{
|
||||
return client.Read<T>(tag);
|
||||
}
|
||||
catch (OpcException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Modbus.Net/h-opc/Common/IClient.cs
Normal file
94
Modbus.Net/h-opc/Common/IClient.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Client interface to perform basic Opc tasks, like discovery, monitoring, reading/writing tags,
|
||||
/// </summary>
|
||||
public interface IClient<out TNode> : IDisposable
|
||||
where TNode : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Connect the client to the OPC Server
|
||||
/// </summary>
|
||||
void Connect();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the OPC Client
|
||||
/// </summary>
|
||||
OpcStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to read</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>The value retrieved from the OPC</returns>
|
||||
T Read<T>(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to write on</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="item"></param>
|
||||
void Write<T>(string tag, T item);
|
||||
|
||||
/// <summary>
|
||||
/// Monitor the specified tag for changes
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the type of tag to monitor</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` monitors the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="callback">the callback to execute when the value is changed.
|
||||
/// The first parameter is the new value of the node, the second is an `unsubscribe` function to unsubscribe the callback</param>
|
||||
void Monitor<T>(string tag, Action<T, Action> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>If there is a tag, it returns it, otherwise it throws an </returns>
|
||||
TNode FindNode(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root node of the server
|
||||
/// </summary>
|
||||
TNode RootNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the sub nodes of `bar` on the folder `foo`</param>
|
||||
/// <returns>The list of sub-nodes</returns>
|
||||
IEnumerable<TNode> ExploreFolder(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag asynchronusly
|
||||
/// </summary>
|
||||
Task<T> ReadAsync<T>(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag asynchronously
|
||||
/// </summary>
|
||||
Task WriteAsync<T>(string tag, T item);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server asynchronously
|
||||
/// </summary>
|
||||
Task<Node> FindNodeAsync(string tag);
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server asynchronously
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures",
|
||||
Justification = "Task")]
|
||||
Task<IEnumerable<Node>> ExploreFolderAsync(string tag);
|
||||
}
|
||||
}
|
||||
48
Modbus.Net/h-opc/Common/Node.cs
Normal file
48
Modbus.Net/h-opc/Common/Node.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class representing a node on the OPC server
|
||||
/// </summary>
|
||||
public abstract class Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the displayed name of the node
|
||||
/// </summary>
|
||||
public string Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dot-separated fully qualified tag of the node
|
||||
/// </summary>
|
||||
public string Tag { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent node. If the node is root, returns null
|
||||
/// </summary>
|
||||
public Node Parent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new node
|
||||
/// </summary>
|
||||
/// <param name="name">the name of the node</param>
|
||||
/// <param name="parent">The parent node</param>
|
||||
protected Node(string name, Node parent = null)
|
||||
{
|
||||
Name = name;
|
||||
Parent = parent;
|
||||
if (parent != null && !string.IsNullOrEmpty(parent.Tag))
|
||||
Tag = parent.Tag + '.' + name;
|
||||
else
|
||||
Tag = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides ToString()
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return Tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Modbus.Net/h-opc/Common/OpcException.cs
Normal file
70
Modbus.Net/h-opc/Common/OpcException.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Opc.Ua;
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies an exception occurred during OPC Communication
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class OpcException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
public OpcException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
public OpcException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an (optional) associated OPC UA StatusCode for the exception.
|
||||
/// </summary>
|
||||
public StatusCode? Status { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
public OpcException(string message, StatusCode status)
|
||||
: base(message)
|
||||
{
|
||||
Status = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
public OpcException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the OpcException class
|
||||
/// </summary>
|
||||
protected OpcException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the System.Runtime.Serialization.SerializationInfo with information about the exception.
|
||||
/// </summary>
|
||||
/// <param name="info">The System.Runtime.Serialization.SerializationInfo that holds the serialized object data about the exception being thrown.</param>
|
||||
/// <param name="context">The System.Runtime.Serialization.StreamingContext that contains contextual information about the source or destination.</param>
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
18
Modbus.Net/h-opc/Common/OpcStatus.cs
Normal file
18
Modbus.Net/h-opc/Common/OpcStatus.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Hylasoft.Opc.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the status of an OPC connector
|
||||
/// </summary>
|
||||
public enum OpcStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The client is not connected
|
||||
/// </summary>
|
||||
NotConnected,
|
||||
|
||||
/// <summary>
|
||||
/// The client is connected
|
||||
/// </summary>
|
||||
Connected
|
||||
}
|
||||
}
|
||||
207
Modbus.Net/h-opc/Da/DaClient.cs
Normal file
207
Modbus.Net/h-opc/Da/DaClient.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Hylasoft.Opc.Common;
|
||||
using Opc;
|
||||
using Factory = OpcCom.Factory;
|
||||
using OpcDa = Opc.Da;
|
||||
|
||||
namespace Hylasoft.Opc.Da
|
||||
{
|
||||
/// <summary>
|
||||
/// Client Implementation for DA
|
||||
/// </summary>
|
||||
public partial class DaClient : IClient<DaNode>
|
||||
{
|
||||
private readonly URL _url;
|
||||
protected OpcDa.Server _server;
|
||||
private long _sub;
|
||||
private readonly IDictionary<string, DaNode> _nodesCache = new Dictionary<string, DaNode>();
|
||||
|
||||
// default monitor interval in Milliseconds
|
||||
private const int DefaultMonitorInterval = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new Data Access Client
|
||||
/// </summary>
|
||||
/// <param name="serverUrl">the url of the server to connect to</param>
|
||||
public DaClient(Uri serverUrl)
|
||||
{
|
||||
_url = new URL(serverUrl.AbsolutePath)
|
||||
{
|
||||
Scheme = serverUrl.Scheme,
|
||||
HostName = serverUrl.Host
|
||||
};
|
||||
}
|
||||
|
||||
#region interface methods
|
||||
|
||||
/// <summary>
|
||||
/// Connect the client to the OPC Server
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
if (Status == OpcStatus.Connected)
|
||||
return;
|
||||
_server = new OpcDa.Server(new Factory(), _url);
|
||||
_server.Connect();
|
||||
var root = new DaNode(string.Empty, string.Empty);
|
||||
RootNode = root;
|
||||
AddNodeToCache(root);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the OPC Client
|
||||
/// </summary>
|
||||
public OpcStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_server == null || _server.GetStatus().ServerState != OpcDa.serverState.running)
|
||||
return OpcStatus.NotConnected;
|
||||
return OpcStatus.Connected;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to read</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>The value retrieved from the OPC</returns>
|
||||
public T Read<T>(string tag)
|
||||
{
|
||||
var item = new OpcDa.Item { ItemName = tag };
|
||||
var result = _server.Read(new[] { item })[0];
|
||||
CheckResult(result, tag);
|
||||
|
||||
return (T)result.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to write on</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="item"></param>
|
||||
public void Write<T>(string tag, T item)
|
||||
{
|
||||
var itmVal = new OpcDa.ItemValue
|
||||
{
|
||||
ItemName = tag,
|
||||
Value = item
|
||||
};
|
||||
var result = _server.Write(new[] { itmVal })[0];
|
||||
CheckResult(result, tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monitor the specified tag for changes
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the type of tag to monitor</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` monitors the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="callback">the callback to execute when the value is changed.
|
||||
/// The first parameter is the new value of the node, the second is an `unsubscribe` function to unsubscribe the callback</param>
|
||||
public void Monitor<T>(string tag, Action<T, Action> callback)
|
||||
{
|
||||
var subItem = new OpcDa.SubscriptionState
|
||||
{
|
||||
Name = (++_sub).ToString(CultureInfo.InvariantCulture),
|
||||
Active = true,
|
||||
UpdateRate = DefaultMonitorInterval
|
||||
};
|
||||
var sub = _server.CreateSubscription(subItem);
|
||||
|
||||
// I have to start a new thread here because unsubscribing
|
||||
// the subscription during a datachanged event causes a deadlock
|
||||
Action unsubscribe = () => new Thread(o =>
|
||||
_server.CancelSubscription(sub)).Start();
|
||||
|
||||
sub.DataChanged += (handle, requestHandle, values) =>
|
||||
callback((T)values[0].Value, unsubscribe);
|
||||
sub.AddItems(new[] { new OpcDa.Item { ItemName = tag } });
|
||||
sub.SetEnabled(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>If there is a tag, it returns it, otherwise it throws an </returns>
|
||||
public DaNode FindNode(string tag)
|
||||
{
|
||||
// if the tag already exists in cache, return it
|
||||
if (_nodesCache.ContainsKey(tag))
|
||||
return _nodesCache[tag];
|
||||
|
||||
// try to find the tag otherwise
|
||||
var item = new OpcDa.Item { ItemName = tag };
|
||||
var result = _server.Read(new[] { item })[0];
|
||||
CheckResult(result, tag);
|
||||
var node = new DaNode(item.ItemName, item.ItemName, RootNode);
|
||||
AddNodeToCache(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root node of the server
|
||||
/// </summary>
|
||||
public DaNode RootNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the sub nodes of `bar` on the folder `foo`</param>
|
||||
/// <returns>The list of sub-nodes</returns>
|
||||
public IEnumerable<DaNode> ExploreFolder(string tag)
|
||||
{
|
||||
var parent = FindNode(tag);
|
||||
OpcDa.BrowsePosition p;
|
||||
var nodes = _server.Browse(new ItemIdentifier(parent.Tag), new OpcDa.BrowseFilters(), out p)
|
||||
.Select(t => new DaNode(t.Name, t.ItemName, parent))
|
||||
.ToList();
|
||||
//add nodes to cache
|
||||
foreach (var node in nodes)
|
||||
AddNodeToCache(node);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_server != null)
|
||||
_server.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Adds a node to the cache using the tag as its key
|
||||
/// </summary>
|
||||
/// <param name="node">the node to add</param>
|
||||
protected void AddNodeToCache(DaNode node)
|
||||
{
|
||||
if (!_nodesCache.ContainsKey(node.Tag))
|
||||
_nodesCache.Add(node.Tag, node);
|
||||
}
|
||||
|
||||
protected static void CheckResult(IResult result, string tag)
|
||||
{
|
||||
if (result == null)
|
||||
throw new OpcException("The server replied with an empty response");
|
||||
if (result.ResultID.ToString() != "S_OK")
|
||||
throw new OpcException(string.Format("Invalid response from the server. (Response Status: {0}, Opc Tag: {1})", result.ResultID, tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Modbus.Net/h-opc/Da/DaClient_async.cs
Normal file
45
Modbus.Net/h-opc/Da/DaClient_async.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Hylasoft.Opc.Common;
|
||||
using OpcDa = Opc.Da;
|
||||
|
||||
namespace Hylasoft.Opc.Da
|
||||
{
|
||||
/// <summary>
|
||||
/// Client Implementation for DA
|
||||
/// </summary>
|
||||
public partial class DaClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Read a tag asynchronusly
|
||||
/// </summary>
|
||||
public async Task<T> ReadAsync<T>(string tag)
|
||||
{
|
||||
return await Task.Run(() => Read<T>(tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag asynchronously
|
||||
/// </summary>
|
||||
public async Task WriteAsync<T>(string tag, T item)
|
||||
{
|
||||
await Task.Run(() => Write(tag, item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server asynchronously
|
||||
/// </summary>
|
||||
public async Task<Node> FindNodeAsync(string tag)
|
||||
{
|
||||
return await Task.Run(() => FindNode(tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server asynchronously
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<Node>> ExploreFolderAsync(string tag)
|
||||
{
|
||||
return await Task.Run(() => ExploreFolder(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Modbus.Net/h-opc/Da/DaNode.cs
Normal file
22
Modbus.Net/h-opc/Da/DaNode.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Hylasoft.Opc.Common;
|
||||
|
||||
namespace Hylasoft.Opc.Da
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a node to be used specifically for OPC DA
|
||||
/// </summary>
|
||||
public class DaNode : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a DaNode class
|
||||
/// </summary>
|
||||
/// <param name="name">the name of the node</param>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="parent">The parent node</param>
|
||||
public DaNode(string name, string tag, Node parent = null)
|
||||
: base(name, parent)
|
||||
{
|
||||
Tag = tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Modbus.Net/h-opc/Properties/AssemblyInfo.cs
Normal file
35
Modbus.Net/h-opc/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
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("h-opc")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("hylasoft")]
|
||||
[assembly: AssemblyProduct("h-opc")]
|
||||
[assembly: AssemblyCopyright(" ")]
|
||||
[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("cec4db47-9316-4591-a7e7-35118fb1e375")]
|
||||
|
||||
// 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("0.4.0")]
|
||||
[assembly: AssemblyVersion("0.4.0")]
|
||||
[assembly: AssemblyFileVersion("0.4.0")]
|
||||
5
Modbus.Net/h-opc/Settings.StyleCop
Normal file
5
Modbus.Net/h-opc/Settings.StyleCop
Normal file
@@ -0,0 +1,5 @@
|
||||
<StyleCopSettings Version="105" >
|
||||
<GlobalSettings>
|
||||
<BooleanProperty Name="RulesEnabledByDefault">False</BooleanProperty>
|
||||
</GlobalSettings>
|
||||
</StyleCopSettings>
|
||||
100
Modbus.Net/h-opc/Ua/ClientUtils.cs
Normal file
100
Modbus.Net/h-opc/Ua/ClientUtils.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System.Linq;
|
||||
using Opc.Ua;
|
||||
using System;
|
||||
using Opc.Ua.Client;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// List of static utility methods
|
||||
/// </summary>
|
||||
internal static class ClientUtils
|
||||
{
|
||||
// TODO I didn't write these methods. I should rewrite it once I understand whtat it does, beacuse it looks crazy
|
||||
|
||||
public static EndpointDescription SelectEndpoint(Uri discoveryUrl, bool useSecurity)
|
||||
{
|
||||
var configuration = EndpointConfiguration.Create();
|
||||
configuration.OperationTimeout = 5000;
|
||||
EndpointDescription endpointDescription1 = null;
|
||||
using (var discoveryClient = DiscoveryClient.Create(discoveryUrl, configuration))
|
||||
{
|
||||
var endpoints = discoveryClient.GetEndpoints(null);
|
||||
foreach (var endpointDescription2 in endpoints.Where(endpointDescription2 => endpointDescription2.EndpointUrl.StartsWith(discoveryUrl.Scheme)))
|
||||
{
|
||||
if (useSecurity)
|
||||
{
|
||||
if (endpointDescription2.SecurityMode == MessageSecurityMode.None)
|
||||
continue;
|
||||
}
|
||||
else if (endpointDescription2.SecurityMode != MessageSecurityMode.None)
|
||||
continue;
|
||||
if (endpointDescription1 == null)
|
||||
endpointDescription1 = endpointDescription2;
|
||||
if (endpointDescription2.SecurityLevel > endpointDescription1.SecurityLevel)
|
||||
endpointDescription1 = endpointDescription2;
|
||||
}
|
||||
if (endpointDescription1 == null)
|
||||
{
|
||||
if (endpoints.Count > 0)
|
||||
endpointDescription1 = endpoints[0];
|
||||
}
|
||||
}
|
||||
var uri = Utils.ParseUri(endpointDescription1.EndpointUrl);
|
||||
if (uri != null && uri.Scheme == discoveryUrl.Scheme)
|
||||
endpointDescription1.EndpointUrl = new UriBuilder(uri)
|
||||
{
|
||||
Host = discoveryUrl.DnsSafeHost,
|
||||
Port = discoveryUrl.Port
|
||||
}.ToString();
|
||||
return endpointDescription1;
|
||||
}
|
||||
|
||||
public static ReferenceDescriptionCollection Browse(Session session, NodeId nodeId)
|
||||
{
|
||||
var desc = new BrowseDescription
|
||||
{
|
||||
NodeId = nodeId,
|
||||
BrowseDirection = BrowseDirection.Forward,
|
||||
IncludeSubtypes = true,
|
||||
NodeClassMask = 0U,
|
||||
ResultMask = 63U,
|
||||
};
|
||||
return Browse(session, desc, true);
|
||||
}
|
||||
|
||||
public static ReferenceDescriptionCollection Browse(Session session, BrowseDescription nodeToBrowse, bool throwOnError)
|
||||
{
|
||||
try
|
||||
{
|
||||
var descriptionCollection = new ReferenceDescriptionCollection();
|
||||
var nodesToBrowse = new BrowseDescriptionCollection { nodeToBrowse };
|
||||
BrowseResultCollection results;
|
||||
DiagnosticInfoCollection diagnosticInfos;
|
||||
session.Browse(null, null, 0U, nodesToBrowse, out results, out diagnosticInfos);
|
||||
ClientBase.ValidateResponse(results, nodesToBrowse);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse);
|
||||
while (!StatusCode.IsBad(results[0].StatusCode))
|
||||
{
|
||||
for (var index = 0; index < results[0].References.Count; ++index)
|
||||
descriptionCollection.Add(results[0].References[index]);
|
||||
if (results[0].References.Count == 0 || results[0].ContinuationPoint == null)
|
||||
return descriptionCollection;
|
||||
var continuationPoints = new ByteStringCollection();
|
||||
continuationPoints.Add(results[0].ContinuationPoint);
|
||||
session.BrowseNext(null, false, continuationPoints, out results, out diagnosticInfos);
|
||||
ClientBase.ValidateResponse(results, continuationPoints);
|
||||
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints);
|
||||
}
|
||||
throw new ServiceResultException(results[0].StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ServiceResultException(ex, 2147549184U);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
Modbus.Net/h-opc/Ua/NodeExtensions.cs
Normal file
24
Modbus.Net/h-opc/Ua/NodeExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Hylasoft.Opc.Common;
|
||||
using OpcF = Opc.Ua;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// Class with extension methods for OPC UA
|
||||
/// </summary>
|
||||
public static class NodeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an OPC Foundation node to an Hylasoft OPC UA Node
|
||||
/// </summary>
|
||||
/// <param name="node">The node to convert</param>
|
||||
/// <param name="parent">the parent node (optional)</param>
|
||||
/// <returns></returns>
|
||||
internal static UaNode ToHylaNode(this OpcF.ReferenceDescription node, Node parent = null)
|
||||
{
|
||||
var name = node.DisplayName.ToString();
|
||||
var nodeId = node.NodeId.ToString();
|
||||
return new UaNode(name, nodeId, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
558
Modbus.Net/h-opc/Ua/UaClient.cs
Normal file
558
Modbus.Net/h-opc/Ua/UaClient.cs
Normal file
@@ -0,0 +1,558 @@
|
||||
using Hylasoft.Opc.Common;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
using Opc.Ua.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// Client Implementation for UA
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling",
|
||||
Justification = "Doesn't make sense to split this class")]
|
||||
public class UaClient : IClient<UaNode>
|
||||
{
|
||||
private readonly UaClientOptions _options = new UaClientOptions();
|
||||
private readonly Uri _serverUrl;
|
||||
protected Session _session;
|
||||
|
||||
private readonly IDictionary<string, UaNode> _nodesCache = new Dictionary<string, UaNode>();
|
||||
private readonly IDictionary<string, IList<UaNode>> _folderCache = new Dictionary<string, IList<UaNode>>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a server object
|
||||
/// </summary>
|
||||
/// <param name="serverUrl">the url of the server to connect to</param>
|
||||
public UaClient(Uri serverUrl)
|
||||
{
|
||||
_serverUrl = serverUrl;
|
||||
Status = OpcStatus.NotConnected;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Options to configure the UA client session
|
||||
/// </summary>
|
||||
public UaClientOptions Options
|
||||
{
|
||||
get { return _options; }
|
||||
}
|
||||
|
||||
|
||||
private void PostInitializeSession()
|
||||
{
|
||||
var node = _session.NodeCache.Find(ObjectIds.ObjectsFolder);
|
||||
RootNode = new UaNode(string.Empty, node.NodeId.ToString());
|
||||
AddNodeToCache(RootNode);
|
||||
Status = OpcStatus.Connected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect the client to the OPC Server
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
if (Status == OpcStatus.Connected)
|
||||
return;
|
||||
_session = InitializeSession(_serverUrl);
|
||||
_session.KeepAlive += SessionKeepAlive;
|
||||
_session.SessionClosing += SessionClosing;
|
||||
PostInitializeSession();
|
||||
}
|
||||
|
||||
private void SessionKeepAlive(Session session, KeepAliveEventArgs e)
|
||||
{
|
||||
if (e.CurrentState != ServerState.Running)
|
||||
{
|
||||
if (Status == OpcStatus.Connected)
|
||||
{
|
||||
Status = OpcStatus.NotConnected;
|
||||
NotifyServerConnectionLost();
|
||||
}
|
||||
}
|
||||
else if (e.CurrentState == ServerState.Running)
|
||||
{
|
||||
if (Status == OpcStatus.NotConnected)
|
||||
{
|
||||
Status = OpcStatus.Connected;
|
||||
NotifyServerConnectionRestored();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SessionClosing(object sender, EventArgs e)
|
||||
{
|
||||
Status = OpcStatus.NotConnected;
|
||||
NotifyServerConnectionLost();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reconnect the OPC session
|
||||
/// </summary>
|
||||
public void ReConnect()
|
||||
{
|
||||
if (Status != OpcStatus.Connected)
|
||||
return;
|
||||
Status = OpcStatus.NotConnected;
|
||||
_session.Reconnect();
|
||||
Status = OpcStatus.Connected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new OPC session, based on the current session parameters.
|
||||
/// </summary>
|
||||
public void RecreateSession()
|
||||
{
|
||||
if (Status != OpcStatus.Connected)
|
||||
return;
|
||||
|
||||
Status = OpcStatus.NotConnected;
|
||||
_session = Session.Recreate(_session);
|
||||
PostInitializeSession();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the OPC Client
|
||||
/// </summary>
|
||||
public OpcStatus Status { get; private set; }
|
||||
|
||||
|
||||
private ReadValueIdCollection BuildReadValueIdCollection(string tag, uint attributeId)
|
||||
{
|
||||
var n = FindNode(tag, RootNode);
|
||||
var readValue = new ReadValueId
|
||||
{
|
||||
NodeId = n.NodeId,
|
||||
AttributeId = attributeId
|
||||
};
|
||||
return new ReadValueIdCollection { readValue };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to read</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>The value retrieved from the OPC</returns>
|
||||
public T Read<T>(string tag)
|
||||
{
|
||||
var nodesToRead = BuildReadValueIdCollection(tag, Attributes.Value);
|
||||
DataValueCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
_session.Read(
|
||||
requestHeader: null,
|
||||
maxAge: 0,
|
||||
timestampsToReturn: TimestampsToReturn.Neither,
|
||||
nodesToRead: nodesToRead,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
var val = results[0];
|
||||
|
||||
CheckReturnValue(val.StatusCode);
|
||||
return (T)val.Value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read a tag asynchronously
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to read</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` reads the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>The value retrieved from the OPC</returns>
|
||||
public Task<T> ReadAsync<T>(string tag)
|
||||
{
|
||||
var nodesToRead = BuildReadValueIdCollection(tag, Attributes.Value);
|
||||
|
||||
// Wrap the ReadAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
|
||||
var taskCompletionSource = new TaskCompletionSource<T>();
|
||||
_session.BeginRead(
|
||||
requestHeader: null,
|
||||
maxAge: 0,
|
||||
timestampsToReturn: TimestampsToReturn.Neither,
|
||||
nodesToRead: nodesToRead,
|
||||
callback: ar =>
|
||||
{
|
||||
DataValueCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
var response = _session.EndRead(
|
||||
result: ar,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
|
||||
try
|
||||
{
|
||||
CheckReturnValue(response.ServiceResult);
|
||||
CheckReturnValue(results[0].StatusCode);
|
||||
var val = results[0];
|
||||
taskCompletionSource.TrySetResult((T)val.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletionSource.TrySetException(ex);
|
||||
}
|
||||
},
|
||||
asyncState: null);
|
||||
|
||||
return taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
|
||||
private WriteValueCollection BuildWriteValueCollection(string tag, uint attributeId, object dataValue)
|
||||
{
|
||||
var n = FindNode(tag, RootNode);
|
||||
var writeValue = new WriteValue
|
||||
{
|
||||
NodeId = n.NodeId,
|
||||
AttributeId = attributeId,
|
||||
Value = { Value = dataValue }
|
||||
};
|
||||
return new WriteValueCollection { writeValue };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to write on</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="item">The value for the item to write</param>
|
||||
public void Write<T>(string tag, T item)
|
||||
{
|
||||
var nodesToWrite = BuildWriteValueCollection(tag, Attributes.Value, item);
|
||||
|
||||
StatusCodeCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
_session.Write(
|
||||
requestHeader: null,
|
||||
nodesToWrite: nodesToWrite,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
|
||||
CheckReturnValue(results[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a value on the specified opc tag asynchronously
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of tag to write on</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="item">The value for the item to write</param>
|
||||
public Task WriteAsync<T>(string tag, T item)
|
||||
{
|
||||
var nodesToWrite = BuildWriteValueCollection(tag, Attributes.Value, item);
|
||||
|
||||
// Wrap the WriteAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
|
||||
var taskCompletionSource = new TaskCompletionSource<T>();
|
||||
_session.BeginWrite(
|
||||
requestHeader: null,
|
||||
nodesToWrite: nodesToWrite,
|
||||
callback: ar =>
|
||||
{
|
||||
StatusCodeCollection results;
|
||||
DiagnosticInfoCollection diag;
|
||||
var response = _session.EndWrite(
|
||||
result: ar,
|
||||
results: out results,
|
||||
diagnosticInfos: out diag);
|
||||
try
|
||||
{
|
||||
CheckReturnValue(response.ServiceResult);
|
||||
CheckReturnValue(results[0]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletionSource.TrySetException(ex);
|
||||
}
|
||||
},
|
||||
asyncState: null);
|
||||
return taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Monitor the specified tag for changes
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the type of tag to monitor</typeparam>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` monitors the tag `bar` on the folder `foo`</param>
|
||||
/// <param name="callback">the callback to execute when the value is changed.
|
||||
/// The first parameter is the new value of the node, the second is an `unsubscribe` function to unsubscribe the callback</param>
|
||||
public void Monitor<T>(string tag, Action<T, Action> callback)
|
||||
{
|
||||
var node = FindNode(tag);
|
||||
|
||||
var sub = new Subscription
|
||||
{
|
||||
PublishingInterval = _options.DefaultMonitorInterval,
|
||||
PublishingEnabled = true,
|
||||
DisplayName = tag,
|
||||
Priority = byte.MaxValue
|
||||
};
|
||||
|
||||
var item = new MonitoredItem
|
||||
{
|
||||
StartNodeId = node.NodeId,
|
||||
AttributeId = Attributes.Value,
|
||||
DisplayName = tag,
|
||||
SamplingInterval = _options.DefaultMonitorInterval,
|
||||
};
|
||||
sub.AddItem(item);
|
||||
_session.AddSubscription(sub);
|
||||
sub.Create();
|
||||
sub.ApplyChanges();
|
||||
|
||||
item.Notification += (monitoredItem, args) =>
|
||||
{
|
||||
var p = (MonitoredItemNotification)args.NotificationValue;
|
||||
var t = p.Value.WrappedValue.Value;
|
||||
Action unsubscribe = () =>
|
||||
{
|
||||
sub.RemoveItems(sub.MonitoredItems);
|
||||
sub.Delete(true);
|
||||
_session.RemoveSubscription(sub);
|
||||
sub.Dispose();
|
||||
};
|
||||
callback((T)t, unsubscribe);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explore a folder on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the sub nodes of `bar` on the folder `foo`</param>
|
||||
/// <returns>The list of sub-nodes</returns>
|
||||
public IEnumerable<UaNode> ExploreFolder(string tag)
|
||||
{
|
||||
IList<UaNode> nodes;
|
||||
_folderCache.TryGetValue(tag, out nodes);
|
||||
if (nodes != null)
|
||||
return nodes;
|
||||
|
||||
var folder = FindNode(tag);
|
||||
nodes = ClientUtils.Browse(_session, folder.NodeId)
|
||||
.GroupBy(n => n.NodeId) //this is to select distinct
|
||||
.Select(n => n.First())
|
||||
.Where(n => n.NodeClass == NodeClass.Variable || n.NodeClass == NodeClass.Object)
|
||||
.Select(n => n.ToHylaNode(folder))
|
||||
.ToList();
|
||||
|
||||
//add nodes to cache
|
||||
_folderCache.Add(tag, nodes);
|
||||
foreach (var node in nodes)
|
||||
AddNodeToCache(node);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explores a folder asynchronously
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<Common.Node>> ExploreFolderAsync(string tag)
|
||||
{
|
||||
return await Task.Run(() => ExploreFolder(tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node on the Opc Server
|
||||
/// </summary>
|
||||
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name.
|
||||
/// E.g: the tag `foo.bar` finds the tag `bar` on the folder `foo`</param>
|
||||
/// <returns>If there is a tag, it returns it, otherwise it throws an </returns>
|
||||
public UaNode FindNode(string tag)
|
||||
{
|
||||
// if the tag already exists in cache, return it
|
||||
if (_nodesCache.ContainsKey(tag))
|
||||
return _nodesCache[tag];
|
||||
|
||||
// try to find the tag otherwise
|
||||
var found = FindNode(tag, RootNode);
|
||||
if (found != null)
|
||||
{
|
||||
AddNodeToCache(found);
|
||||
return found;
|
||||
}
|
||||
|
||||
// throws an exception if not found
|
||||
throw new OpcException(string.Format("The tag \"{0}\" doesn't exist on the Server", tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find node asynchronously
|
||||
/// </summary>
|
||||
public async Task<Common.Node> FindNodeAsync(string tag)
|
||||
{
|
||||
return await Task.Run(() => FindNode(tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root node of the server
|
||||
/// </summary>
|
||||
public UaNode RootNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_session != null)
|
||||
{
|
||||
_session.RemoveSubscriptions(_session.Subscriptions.ToList());
|
||||
_session.Close();
|
||||
_session.Dispose();
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void CheckReturnValue(StatusCode status)
|
||||
{
|
||||
if (!StatusCode.IsGood(status))
|
||||
throw new OpcException(string.Format("Invalid response from the server. (Response Status: {0})", status), status);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a node to the cache using the tag as its key
|
||||
/// </summary>
|
||||
/// <param name="node">the node to add</param>
|
||||
protected void AddNodeToCache(UaNode node)
|
||||
{
|
||||
if (!_nodesCache.ContainsKey(node.Tag))
|
||||
_nodesCache.Add(node.Tag, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crappy method to initialize the session. I don't know what many of these things do, sincerely.
|
||||
/// </summary>
|
||||
private Session InitializeSession(Uri url)
|
||||
{
|
||||
var certificateValidator = new CertificateValidator();
|
||||
certificateValidator.CertificateValidation += (sender, eventArgs) =>
|
||||
{
|
||||
if (ServiceResult.IsGood(eventArgs.Error))
|
||||
eventArgs.Accept = true;
|
||||
else if ((eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted) && _options.AutoAcceptUntrustedCertificates)
|
||||
eventArgs.Accept = true;
|
||||
else
|
||||
throw new OpcException(string.Format("Failed to validate certificate with error code {0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo), eventArgs.Error.StatusCode);
|
||||
};
|
||||
|
||||
// Build the application configuration
|
||||
var appInstance = new ApplicationInstance
|
||||
{
|
||||
ApplicationType = ApplicationType.Client,
|
||||
ConfigSectionName = _options.ConfigSectionName,
|
||||
ApplicationConfiguration = new ApplicationConfiguration
|
||||
{
|
||||
ApplicationUri = url.ToString(),
|
||||
ApplicationName = _options.ApplicationName,
|
||||
ApplicationType = ApplicationType.Client,
|
||||
CertificateValidator = certificateValidator,
|
||||
SecurityConfiguration = new SecurityConfiguration
|
||||
{
|
||||
AutoAcceptUntrustedCertificates = _options.AutoAcceptUntrustedCertificates
|
||||
},
|
||||
TransportQuotas = new TransportQuotas
|
||||
{
|
||||
OperationTimeout = 600000,
|
||||
MaxStringLength = 1048576,
|
||||
MaxByteStringLength = 1048576,
|
||||
MaxArrayLength = 65535,
|
||||
MaxMessageSize = 4194304,
|
||||
MaxBufferSize = 65535,
|
||||
ChannelLifetime = 300000,
|
||||
SecurityTokenLifetime = 3600000
|
||||
},
|
||||
ClientConfiguration = new ClientConfiguration
|
||||
{
|
||||
DefaultSessionTimeout = 60000,
|
||||
MinSubscriptionLifetime = 10000
|
||||
},
|
||||
DisableHiResClock = true
|
||||
}
|
||||
};
|
||||
|
||||
// Assign a application certificate (when specified)
|
||||
if (_options.ApplicationCertificate != null)
|
||||
appInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(_options.ApplicationCertificate);
|
||||
|
||||
// Find the endpoint to be used
|
||||
var endpoints = ClientUtils.SelectEndpoint(url, _options.UseMessageSecurity);
|
||||
|
||||
// Create the OPC session:
|
||||
var session = Session.Create(
|
||||
configuration: appInstance.ApplicationConfiguration,
|
||||
endpoint: new ConfiguredEndpoint(
|
||||
collection: null,
|
||||
description: endpoints,
|
||||
configuration: EndpointConfiguration.Create(applicationConfiguration: appInstance.ApplicationConfiguration)),
|
||||
updateBeforeConnect: false,
|
||||
checkDomain: false,
|
||||
sessionName: _options.SessionName,
|
||||
sessionTimeout: _options.SessionTimeout,
|
||||
identity: null,
|
||||
preferredLocales: new string[] { });
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a node starting from the specified node as the root folder
|
||||
/// </summary>
|
||||
/// <param name="tag">the tag to find</param>
|
||||
/// <param name="node">the root node</param>
|
||||
/// <returns></returns>
|
||||
private UaNode FindNode(string tag, UaNode node)
|
||||
{
|
||||
var folders = tag.Split('.');
|
||||
var head = folders.FirstOrDefault();
|
||||
UaNode found;
|
||||
try
|
||||
{
|
||||
var subNodes = ExploreFolder(node.Tag);
|
||||
found = subNodes.Single(n => n.Name == head);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new OpcException(string.Format("The tag \"{0}\" doesn't exist on folder \"{1}\"", head, node.Tag), ex);
|
||||
}
|
||||
|
||||
return folders.Length == 1
|
||||
? found // last node, return it
|
||||
: FindNode(string.Join(".", folders.Except(new[] { head })), found); // find sub nodes
|
||||
}
|
||||
|
||||
|
||||
private void NotifyServerConnectionLost()
|
||||
{
|
||||
if (ServerConnectionLost != null)
|
||||
ServerConnectionLost(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void NotifyServerConnectionRestored()
|
||||
{
|
||||
if (ServerConnectionRestored != null)
|
||||
ServerConnectionRestored(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised when the connection to the OPC server is lost.
|
||||
/// </summary>
|
||||
public event EventHandler ServerConnectionLost;
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised when the connection to the OPC server is restored.
|
||||
/// </summary>
|
||||
public event EventHandler ServerConnectionRestored;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
64
Modbus.Net/h-opc/Ua/UaClientOptions.cs
Normal file
64
Modbus.Net/h-opc/Ua/UaClientOptions.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// This class defines the configuration options for the setup of the UA client session
|
||||
/// </summary>
|
||||
public class UaClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the (optional) certificate for the applicaiton to connect to the server
|
||||
/// </summary>
|
||||
public X509Certificate2 ApplicationCertificate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the ApplicationName for the client application.
|
||||
/// </summary>
|
||||
public string ApplicationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should untrusted certificates be silently accepted by the client?
|
||||
/// </summary>
|
||||
public bool AutoAcceptUntrustedCertificates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the ConfigSectionName for the client configuration.
|
||||
/// </summary>
|
||||
public string ConfigSectionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// default monitor interval in Milliseconds.
|
||||
/// </summary>
|
||||
public int DefaultMonitorInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies a name to be associated with the created sessions.
|
||||
/// </summary>
|
||||
public string SessionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the timeout for the sessions.
|
||||
/// </summary>
|
||||
public uint SessionTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specify whether message exchange should be secured.
|
||||
/// </summary>
|
||||
public bool UseMessageSecurity { get; set; }
|
||||
|
||||
|
||||
internal UaClientOptions()
|
||||
{
|
||||
// Initialize default values:
|
||||
ApplicationName = "h-opc-client";
|
||||
AutoAcceptUntrustedCertificates = true;
|
||||
ConfigSectionName = "h-opc-client";
|
||||
DefaultMonitorInterval = 100;
|
||||
SessionName = "h-opc-client";
|
||||
SessionTimeout = 60000U;
|
||||
UseMessageSecurity = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Modbus.Net/h-opc/Ua/UaNode.cs
Normal file
29
Modbus.Net/h-opc/Ua/UaNode.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Hylasoft.Opc.Common;
|
||||
|
||||
namespace Hylasoft.Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a node to be used specifically for OPC UA
|
||||
/// </summary>
|
||||
public class UaNode : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// The UA Id of the node
|
||||
/// </summary>
|
||||
public string NodeId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a UaNode class
|
||||
/// </summary>
|
||||
/// <param name="name">the name of the node</param>
|
||||
/// <param name="nodeId">The UA Id of the node</param>
|
||||
/// <param name="parent">The parent node</param>
|
||||
internal UaNode(string name, string nodeId, Node parent = null)
|
||||
: base(name, parent)
|
||||
{
|
||||
NodeId = nodeId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
107
Modbus.Net/h-opc/h-opc.csproj
Normal file
107
Modbus.Net/h-opc/h-opc.csproj
Normal file
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{4F43B6F0-0C32-4C34-978E-9B8B5B0B6E80}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Hylasoft.Opc</RootNamespace>
|
||||
<AssemblyName>h-opc</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<StyleCopOverrideSettingsFile>Settings.StyleCop</StyleCopOverrideSettingsFile>
|
||||
<BuildToolsStyleCopVersion>4.7.44.0</BuildToolsStyleCopVersion>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<StyleCopEnabled>False</StyleCopEnabled>
|
||||
<DocumentationFile>bin\Debug\h-opc.XML</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;CODE_ANALYSIS</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<StyleCopEnabled>True</StyleCopEnabled>
|
||||
<StyleCopTreatErrorsAsWarnings>False</StyleCopTreatErrorsAsWarnings>
|
||||
<DocumentationFile>bin\Release\h-opc.XML</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Opc.Ua.Client">
|
||||
<HintPath>..\ext_packages\Opc.Ua.Client.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Opc.Ua.Configuration">
|
||||
<HintPath>..\ext_packages\Opc.Ua.Configuration.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Opc.Ua.Core">
|
||||
<HintPath>..\ext_packages\Opc.Ua.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpcComRcw">
|
||||
<HintPath>..\ext_packages\OpcComRcw.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpcNetApi">
|
||||
<HintPath>..\ext_packages\OpcNetApi.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpcNetApi.Com">
|
||||
<HintPath>..\ext_packages\OpcNetApi.Com.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Common\ClientExtensions.cs" />
|
||||
<Compile Include="Common\Node.cs" />
|
||||
<Compile Include="Common\IClient.cs" />
|
||||
<Compile Include="Common\OpcException.cs" />
|
||||
<Compile Include="Common\OpcStatus.cs" />
|
||||
<Compile Include="Da\DaClient_async.cs" />
|
||||
<Compile Include="Da\DaClient.cs" />
|
||||
<Compile Include="Da\DaNode.cs" />
|
||||
<Compile Include="Ua\ClientUtils.cs" />
|
||||
<Compile Include="Ua\NodeExtensions.cs" />
|
||||
<Compile Include="Ua\UaClientOptions.cs" />
|
||||
<Compile Include="Ua\UaNode.cs" />
|
||||
<Compile Include="Ua\UaClient.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="Settings.StyleCop">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="StyleCopAdditionalAddinPaths">
|
||||
<StyleCopAdditionalAddinPaths Include="..\packages\BuildTools.StyleCopPlus.4.7.49.4\tools">
|
||||
<Visible>false</Visible>
|
||||
</StyleCopAdditionalAddinPaths>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\BuildTools.StyleCop.4.7.49.0\tools\StyleCop.targets" Condition="Exists('..\packages\BuildTools.StyleCop.4.7.49.0\tools\StyleCop.targets')" />
|
||||
<Target Name="packages_BuildTools_StyleCop_4_7_49_0_tools_StyleCop_targets" Condition="$(StyleCopOutputFile)==''" BeforeTargets="BeforeBuild">
|
||||
<Error Text="BuildTools_StyleCop - the BuildTools_StyleCop package has not been restored.
If you are running this from an IDE, make sure NuGet Package Restore has been enabled, then reload the solution and re-run the build.
If you are running this from the command line, run the build again.
If this is a CI server, you may want to make sure NuGet Package Restore runs before your build with:
 msbuild solution.sln /t:restorepackages" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
5
Modbus.Net/h-opc/packages.config
Normal file
5
Modbus.Net/h-opc/packages.config
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="BuildTools.StyleCop" version="4.7.49.0" targetFramework="net45" />
|
||||
<package id="BuildTools.StyleCopPlus" version="4.7.49.4" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -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: <b>"h-opc"</b> 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.
|
||||
|
||||
Reference in New Issue
Block a user