Several function enchancement
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 南大奥拓NA200H专用AddressFormater
|
/// 南大奥拓NA200H专用AddressFormater
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressFormaterNA200H : AddressFormater
|
public class AddressFormaterNA200H : AddressFormater<int, int>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 格式化地址
|
/// 格式化地址
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
<LangVersion>10.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Modbus.NA200H</AssemblyName>
|
<AssemblyName>Modbus.Net.Modbus.NA200H</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Modbus.NA200H</RootNamespace>
|
<RootNamespace>Modbus.Net.Modbus.NA200H</RootNamespace>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
<LangVersion>10.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Modbus.SelfDefinedSample</AssemblyName>
|
<AssemblyName>Modbus.Net.Modbus.SelfDefinedSample</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Modbus.SelfDefinedSample</RootNamespace>
|
<RootNamespace>Modbus.Net.Modbus.SelfDefinedSample</RootNamespace>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Modbus标准AddressFormater
|
/// Modbus标准AddressFormater
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressFormaterModbus : AddressFormater
|
public class AddressFormaterModbus : AddressFormater<int, int>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 格式化地址
|
/// 格式化地址
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
<LangVersion>10.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Modbus</AssemblyName>
|
<AssemblyName>Modbus.Net.Modbus</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Modbus</RootNamespace>
|
<RootNamespace>Modbus.Net.Modbus</RootNamespace>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Modbus.Net.Modbus
|
|||||||
/// <param name="masterAddress">主站号</param>
|
/// <param name="masterAddress">主站号</param>
|
||||||
/// <param name="endian">端格式</param>
|
/// <param name="endian">端格式</param>
|
||||||
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString,
|
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString,
|
||||||
IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress,
|
IEnumerable<AddressUnit<TUnitKey, int ,int>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress,
|
||||||
Endian endian)
|
Endian endian)
|
||||||
: base(id, getAddresses, keepConnect, slaveAddress, masterAddress)
|
: base(id, getAddresses, keepConnect, slaveAddress, masterAddress)
|
||||||
{
|
{
|
||||||
@@ -42,7 +42,7 @@ namespace Modbus.Net.Modbus
|
|||||||
/// <param name="masterAddress">主站号</param>
|
/// <param name="masterAddress">主站号</param>
|
||||||
/// <param name="endian">端格式</param>
|
/// <param name="endian">端格式</param>
|
||||||
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString,
|
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString,
|
||||||
IEnumerable<AddressUnit<TUnitKey>> getAddresses, byte slaveAddress, byte masterAddress,
|
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, byte slaveAddress, byte masterAddress,
|
||||||
Endian endian)
|
Endian endian)
|
||||||
: this(id, connectionType, connectionString, getAddresses, true, slaveAddress, masterAddress, endian)
|
: this(id, connectionType, connectionString, getAddresses, true, slaveAddress, masterAddress, endian)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ namespace Modbus.Net.Opc
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opc地址编码器
|
/// Opc地址编码器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressFormaterOpc<TMachineKey, TUnitKey> : AddressFormater where TMachineKey : IEquatable<TMachineKey>
|
public class AddressFormaterOpc<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> : AddressFormater<TAddressKey, TSubAddressKey> where TMachineKey : IEquatable<TMachineKey>
|
||||||
where TUnitKey : IEquatable<TUnitKey>
|
where TUnitKey : IEquatable<TUnitKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 协议构造器
|
/// 协议构造器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tagGeter">如何通过BaseMachine和AddressUnit构造Opc的标签</param>
|
/// <param name="tagGeter">如何通过BaseMachine和AddressUnit构造Opc的标签</param>
|
||||||
/// <param name="machine">调用这个编码器的设备</param>
|
/// <param name="machine">调用这个编码器的设备</param>
|
||||||
public AddressFormaterOpc(Func<BaseMachine<TMachineKey, TUnitKey>, AddressUnit<TUnitKey>, string[]> tagGeter,
|
public AddressFormaterOpc(Func<BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey>, AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>, string> tagGeter,
|
||||||
BaseMachine<TMachineKey, TUnitKey> machine)
|
BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> machine)
|
||||||
{
|
{
|
||||||
Machine = machine;
|
Machine = machine;
|
||||||
TagGeter = tagGeter;
|
TagGeter = tagGeter;
|
||||||
@@ -24,13 +24,13 @@ namespace Modbus.Net.Opc
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备
|
/// 设备
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BaseMachine<TMachineKey, TUnitKey> Machine { get; set; }
|
public BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> Machine { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 标签构造器
|
/// 标签构造器
|
||||||
/// (设备,地址)->不具备分隔符的标签数组
|
/// (设备,地址)->不具备分隔符的标签数组
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Func<BaseMachine<TMachineKey, TUnitKey>, AddressUnit<TUnitKey>, string[]> TagGeter { get; set; }
|
protected Func<BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey>, AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>, string> TagGeter { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 编码地址
|
/// 编码地址
|
||||||
@@ -38,15 +38,12 @@ namespace Modbus.Net.Opc
|
|||||||
/// <param name="area">地址所在的数据区域</param>
|
/// <param name="area">地址所在的数据区域</param>
|
||||||
/// <param name="address">地址</param>
|
/// <param name="address">地址</param>
|
||||||
/// <returns>编码后的地址</returns>
|
/// <returns>编码后的地址</returns>
|
||||||
public override string FormatAddress(string area, int address)
|
public override string FormatAddress(string area, TAddressKey address)
|
||||||
{
|
{
|
||||||
var findAddress = Machine?.GetAddresses.FirstOrDefault(p => p.Area == area && p.Address == address);
|
var findAddress = Machine?.GetAddresses.FirstOrDefault(p => p.Area == area && p.Address.Equals(address));
|
||||||
if (findAddress == null) return null;
|
if (findAddress == null) return null;
|
||||||
var strings = TagGeter(Machine, findAddress);
|
var ans = TagGeter(Machine, findAddress);
|
||||||
var ans = "";
|
ans = ans.Trim().Replace(" ", "");
|
||||||
for (var i = 0; i < strings.Length; i++)
|
|
||||||
ans += strings[i].Trim().Replace(" ", "") + '\r';
|
|
||||||
ans = ans.Substring(0, ans.Length - 1);
|
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ namespace Modbus.Net.Opc
|
|||||||
/// <param name="address">地址</param>
|
/// <param name="address">地址</param>
|
||||||
/// <param name="subAddress">子地址(忽略)</param>
|
/// <param name="subAddress">子地址(忽略)</param>
|
||||||
/// <returns>编码后的地址</returns>
|
/// <returns>编码后的地址</returns>
|
||||||
public override string FormatAddress(string area, int address, int subAddress)
|
public override string FormatAddress(string area, TAddressKey address, TSubAddressKey subAddress)
|
||||||
{
|
{
|
||||||
return FormatAddress(area, address);
|
return FormatAddress(area, address);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ using Hylasoft.Opc.Da;
|
|||||||
using Hylasoft.Opc.Ua;
|
using Hylasoft.Opc.Ua;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using URL = Opc.URL;
|
|
||||||
|
|
||||||
namespace Modbus.Net.Opc
|
namespace Modbus.Net.Opc
|
||||||
{
|
{
|
||||||
@@ -73,18 +71,11 @@ namespace Modbus.Net.Opc
|
|||||||
public class MyDaClient : DaClient, IClientExtend
|
public class MyDaClient : DaClient, IClientExtend
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UaClient Extend
|
/// 构造函数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serverUrl">Url address of Opc UA server</param>
|
/// <param name="serverUrl">OpcDa服务端Url</param>
|
||||||
public MyDaClient(Uri serverUrl) : base(serverUrl)
|
public MyDaClient(Uri serverUrl) : base(serverUrl)
|
||||||
{
|
{
|
||||||
var url = new URL(serverUrl.OriginalString)
|
|
||||||
{
|
|
||||||
Scheme = serverUrl.Scheme,
|
|
||||||
HostName = serverUrl.Host
|
|
||||||
};
|
|
||||||
|
|
||||||
typeof(DaClient).GetField("_url", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(this, url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net462</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<LangVersion>10.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Opc</AssemblyName>
|
<AssemblyName>Modbus.Net.Opc</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Opc</RootNamespace>
|
<RootNamespace>Modbus.Net.Opc</RootNamespace>
|
||||||
@@ -23,22 +23,15 @@
|
|||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<Platforms>AnyCPU;x86</Platforms>
|
<Platforms>AnyCPU</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DocumentationFile>bin\Debug\Modbus.Net.Opc.xml</DocumentationFile>
|
<DocumentationFile>bin\Debug\Modbus.Net.Opc.xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
|
||||||
<DocumentationFile>bin\Debug\Modbus.Net.Opc.xml</DocumentationFile>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="H.Opc" Version="0.9.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\h-opc\h-opc\h-opc.csproj" />
|
||||||
<ProjectReference Include="..\Modbus.Net\Modbus.Net.csproj" />
|
<ProjectReference Include="..\Modbus.Net\Modbus.Net.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ namespace Modbus.Net.Opc
|
|||||||
{
|
{
|
||||||
var result = await Client.ReadAsync<object>(tag);
|
var result = await Client.ReadAsync<object>(tag);
|
||||||
object resultTrans;
|
object resultTrans;
|
||||||
if (result.Value.ToString() == "False")
|
if (result.Value?.ToString() == "False")
|
||||||
{
|
{
|
||||||
resultTrans = (byte)0;
|
resultTrans = (byte)0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Modbus.Net.Opc
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opc设备
|
/// Opc设备
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class OpcMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey> where TKey : IEquatable<TKey>
|
public class OpcMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey, string, string> where TKey : IEquatable<TKey>
|
||||||
where TUnitKey : IEquatable<TUnitKey>
|
where TUnitKey : IEquatable<TUnitKey>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -16,13 +16,12 @@ namespace Modbus.Net.Opc
|
|||||||
/// <param name="connectionType">连接类型</param>
|
/// <param name="connectionType">连接类型</param>
|
||||||
/// <param name="connectionString">连接地址</param>
|
/// <param name="connectionString">连接地址</param>
|
||||||
/// <param name="getAddresses">需要读写的地址</param>
|
/// <param name="getAddresses">需要读写的地址</param>
|
||||||
public OpcMachine(TKey id, OpcType connectionType, string connectionString, IEnumerable<AddressUnit<TUnitKey>> getAddresses)
|
/// <param name="tagSpliter">连接各个字段用的符号</param>
|
||||||
|
public OpcMachine(TKey id, OpcType connectionType, string connectionString, IEnumerable<AddressUnit<TUnitKey, string, string>> getAddresses, string tagSpliter)
|
||||||
: base(id, getAddresses, true)
|
: base(id, getAddresses, true)
|
||||||
{
|
{
|
||||||
BaseUtility = new OpcUtility(connectionType, connectionString);
|
BaseUtility = new OpcUtility(connectionType, connectionString);
|
||||||
AddressFormater = new AddressFormaterOpc<TKey, TUnitKey>((machine, unit) => { return new string[] { unit.Area }; }, this);
|
AddressFormater = new AddressFormaterOpc<TKey, TUnitKey, string, string>((machine, unit) => { var ans = unit.Area; if (unit.Address != null) ans += tagSpliter + unit.Address; if (unit.SubAddress != null) ans += tagSpliter + unit.SubAddress; return ans; }, this);
|
||||||
AddressCombiner = new AddressCombinerSingle<TUnitKey>();
|
|
||||||
AddressCombinerSet = new AddressCombinerSingle<TUnitKey>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,20 +8,6 @@ namespace Modbus.Net.Opc
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class OpcProtocolLinker : ProtocolLinker<OpcParamIn, OpcParamOut>
|
public abstract class OpcProtocolLinker : ProtocolLinker<OpcParamIn, OpcParamOut>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 发送并接收数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">发送协议的内容</param>
|
|
||||||
/// <returns>接收协议的内容</returns>
|
|
||||||
public override async Task<OpcParamOut> SendReceiveAsync(OpcParamIn content)
|
|
||||||
{
|
|
||||||
var extBytes = BytesExtend(content);
|
|
||||||
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes);
|
|
||||||
return receiveBytes == null
|
|
||||||
? null
|
|
||||||
: receiveBytes.Value.Length == 0 ? receiveBytes : BytesDecact(receiveBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
|
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -47,6 +33,7 @@ namespace Modbus.Net.Opc
|
|||||||
public override bool? CheckRight(OpcParamOut content)
|
public override bool? CheckRight(OpcParamOut content)
|
||||||
{
|
{
|
||||||
if (content == null || !content.Success) return false;
|
if (content == null || !content.Success) return false;
|
||||||
|
if (content.Success && content.Value == null) { content.Value = Encoding.ASCII.GetBytes("Success"); return true; }
|
||||||
if (content.Value.Length == 6 && Encoding.ASCII.GetString(content.Value) == "NoData")
|
if (content.Value.Length == 6 && Encoding.ASCII.GetString(content.Value) == "NoData")
|
||||||
return null;
|
return null;
|
||||||
return base.CheckRight(content);
|
return base.CheckRight(content);
|
||||||
|
|||||||
@@ -5,3 +5,8 @@
|
|||||||
OPC Implementation of Modbus.Net
|
OPC Implementation of Modbus.Net
|
||||||
|
|
||||||
Doc has been moved to wiki.
|
Doc has been moved to wiki.
|
||||||
|
|
||||||
|
## Caution
|
||||||
|
|
||||||
|
Do not use this module in commercial environment.<br>
|
||||||
|
Altered h-opc uses Technosoftware.DaAeHdaSolution that will not abey for non paid commercial use.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Siemens地址格式化(Modbus.Net专用格式)
|
/// Siemens地址格式化(Modbus.Net专用格式)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressFormaterSiemens : AddressFormater
|
public class AddressFormaterSiemens : AddressFormater<int, int>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 编码地址
|
/// 编码地址
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Siemens地址格式化(Siemens格式)
|
/// Siemens地址格式化(Siemens格式)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressFormaterSimenseStandard : AddressFormater
|
public class AddressFormaterSimenseStandard : AddressFormater<int, int>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 编码地址
|
/// 编码地址
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
<LangVersion>10.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net.Siemens</AssemblyName>
|
<AssemblyName>Modbus.Net.Siemens</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net.Siemens</RootNamespace>
|
<RootNamespace>Modbus.Net.Siemens</RootNamespace>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Modbus.Net.Siemens
|
|||||||
/// <param name="src">本机模块位,0到7,仅200使用,其它型号不要填写</param>
|
/// <param name="src">本机模块位,0到7,仅200使用,其它型号不要填写</param>
|
||||||
/// <param name="dst">PLC模块位,0到7,仅200使用,其它型号不要填写</param>
|
/// <param name="dst">PLC模块位,0到7,仅200使用,其它型号不要填写</param>
|
||||||
public SiemensMachine(TKey id, SiemensType connectionType, string connectionString, SiemensMachineModel model,
|
public SiemensMachine(TKey id, SiemensType connectionType, string connectionString, SiemensMachineModel model,
|
||||||
IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress, byte src = 1, byte dst = 0)
|
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress, byte src = 1, byte dst = 0)
|
||||||
: base(id, getAddresses, keepConnect, slaveAddress, masterAddress)
|
: base(id, getAddresses, keepConnect, slaveAddress, masterAddress)
|
||||||
{
|
{
|
||||||
BaseUtility = new SiemensUtility(connectionType, connectionString, model, slaveAddress, masterAddress, src, dst);
|
BaseUtility = new SiemensUtility(connectionType, connectionString, model, slaveAddress, masterAddress, src, dst);
|
||||||
@@ -45,7 +45,7 @@ namespace Modbus.Net.Siemens
|
|||||||
/// <param name="src">本机模块位,0到7,仅200使用,其它型号不要填写</param>
|
/// <param name="src">本机模块位,0到7,仅200使用,其它型号不要填写</param>
|
||||||
/// <param name="dst">PLC模块位,0到7,仅200使用,其它型号不要填写</param>
|
/// <param name="dst">PLC模块位,0到7,仅200使用,其它型号不要填写</param>
|
||||||
public SiemensMachine(TKey id, SiemensType connectionType, string connectionString, SiemensMachineModel model,
|
public SiemensMachine(TKey id, SiemensType connectionType, string connectionString, SiemensMachineModel model,
|
||||||
IEnumerable<AddressUnit<TUnitKey>> getAddresses, byte slaveAddress, byte masterAddress, byte src = 1, byte dst = 0)
|
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, byte slaveAddress, byte masterAddress, byte src = 1, byte dst = 0)
|
||||||
: this(id, connectionType, connectionString, model, getAddresses, true, slaveAddress, masterAddress, src, dst)
|
: this(id, connectionType, connectionString, model, getAddresses, true, slaveAddress, masterAddress, src, dst)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,16 +34,6 @@ namespace Modbus.Net.Siemens
|
|||||||
ProtocolLinker = new SiemensPpiProtocolLinker(_com, SlaveAddress);
|
ProtocolLinker = new SiemensPpiProtocolLinker(_com, SlaveAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送协议内容并接收,一般方法
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">写入的内容,使用对象数组描述</param>
|
|
||||||
/// <returns>从设备获取的字节流</returns>
|
|
||||||
public override PipeUnit SendReceive(params object[] content)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SendReceiveAsync(Endian, content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送协议内容并接收,一般方法
|
/// 发送协议内容并接收,一般方法
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using FastEnumUtility;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -19,7 +19,7 @@ namespace Modbus.Net.Siemens
|
|||||||
public SiemensPpiProtocolLinker(string com, int slaveAddress)
|
public SiemensPpiProtocolLinker(string com, int slaveAddress)
|
||||||
: base(com, slaveAddress, parity:
|
: base(com, slaveAddress, parity:
|
||||||
ConfigurationReader.GetValue("COM:Siemens", "Parity") != null
|
ConfigurationReader.GetValue("COM:Siemens", "Parity") != null
|
||||||
? FastEnum.Parse<Parity>(ConfigurationReader.GetValue("COM:Siemens", "Parity"))
|
? Enum.Parse<Parity>(ConfigurationReader.GetValue("COM:Siemens", "Parity"))
|
||||||
: null
|
: null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -78,16 +78,6 @@ namespace Modbus.Net.Siemens
|
|||||||
ProtocolLinker = new SiemensTcpProtocolLinker(_ip, _port);
|
ProtocolLinker = new SiemensTcpProtocolLinker(_ip, _port);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送数据并接收
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">发送的数据</param>
|
|
||||||
/// <returns>返回的数据</returns>
|
|
||||||
public override PipeUnit SendReceive(params object[] content)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SendReceiveAsync(Endian, content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送数据并接收
|
/// 发送数据并接收
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -100,17 +90,6 @@ namespace Modbus.Net.Siemens
|
|||||||
return await base.SendReceiveAsync(Endian, content);
|
return await base.SendReceiveAsync(Endian, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送数据并接收
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="unit">协议的核心</param>
|
|
||||||
/// <param name="content">协议的参数</param>
|
|
||||||
/// <returns>返回的数据</returns>
|
|
||||||
public override PipeUnit SendReceive(ProtocolUnit<byte[], byte[]> unit, IInputStruct content)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SendReceiveAsync(unit, content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送数据并接收
|
/// 发送数据并接收
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TripleAdd", "..\Samples\Tri
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Modbus.SelfDefinedSample", "Modbus.Net.Modbus.SelfDefinedSample\Modbus.Net.Modbus.SelfDefinedSample.csproj", "{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.Modbus.SelfDefinedSample", "Modbus.Net.Modbus.SelfDefinedSample\Modbus.Net.Modbus.SelfDefinedSample.csproj", "{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "h-opc", "..\h-opc\h-opc\h-opc.csproj", "{DC6425E4-1409-488D-A014-4DCC909CF542}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -84,6 +86,10 @@ Global
|
|||||||
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DC6425E4-1409-488D-A014-4DCC909CF542}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DC6425E4-1409-488D-A014-4DCC909CF542}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using FastEnumUtility;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -62,8 +61,19 @@ namespace Modbus.Net
|
|||||||
}
|
}
|
||||||
case "addressMap":
|
case "addressMap":
|
||||||
{
|
{
|
||||||
paramsSet.Add(AddressReader.ReadAddresses(dic["addressMap"]));
|
var machineTypeTemp = Assembly.Load("Modbus.Net." + dic["protocol"]).GetType("Modbus.Net." + dic["protocol"] + "." + dic["protocol"] + "Machine`2");
|
||||||
break;
|
var addressTypes = machineTypeTemp.GetProperty("GetAddresses").PropertyType.GenericTypeArguments[0].GenericTypeArguments;
|
||||||
|
if (addressTypes[1] == typeof(int) && addressTypes[2] == typeof(int))
|
||||||
|
{
|
||||||
|
paramsSet.Add(AddressReader<string, int, int>.ReadAddresses(dic["addressMap"]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (addressTypes[1] == typeof(string) && addressTypes[2] == typeof(string))
|
||||||
|
{
|
||||||
|
paramsSet.Add(AddressReader<string, string, string>.ReadAddresses(dic["addressMap"]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw new NotSupportedException("AddressUnit type not supported for AddressReader");
|
||||||
}
|
}
|
||||||
case "endian":
|
case "endian":
|
||||||
{
|
{
|
||||||
@@ -105,7 +115,7 @@ namespace Modbus.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddressReader
|
internal class AddressReader<TUnitKey, TAddressKey, TSubAddressKey> where TUnitKey : IEquatable<TUnitKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
{
|
{
|
||||||
private static readonly IConfigurationRoot configuration = new ConfigurationBuilder()
|
private static readonly IConfigurationRoot configuration = new ConfigurationBuilder()
|
||||||
.SetBasePath(Directory.GetCurrentDirectory())
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
@@ -114,14 +124,14 @@ namespace Modbus.Net
|
|||||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
|
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
public static IEnumerable<AddressUnit<string>> ReadAddresses(string addressMapName)
|
public static IEnumerable<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>> ReadAddresses(string addressMapName)
|
||||||
{
|
{
|
||||||
var ans = new List<AddressUnit<string>>();
|
var ans = new List<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>>();
|
||||||
var addressesRoot = configuration.GetSection("Modbus.Net").GetSection("AddressMap").GetSection(addressMapName).GetChildren();
|
var addressesRoot = configuration.GetSection("Modbus.Net").GetSection("AddressMap").GetSection(addressMapName).GetChildren();
|
||||||
foreach (var address in addressesRoot)
|
foreach (var address in addressesRoot)
|
||||||
{
|
{
|
||||||
|
|
||||||
var addressNew = address.Get<AddressUnit<string>>();
|
var addressNew = address.Get<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>>();
|
||||||
addressNew.DataType = "System." + address["DataType"] != null ? Type.GetType("System." + address["DataType"]) : throw new ArgumentNullException("DataType is null");
|
addressNew.DataType = "System." + address["DataType"] != null ? Type.GetType("System." + address["DataType"]) : throw new ArgumentNullException("DataType is null");
|
||||||
if (addressNew.DataType == null) throw new ArgumentNullException(string.Format("DataType define error {0} {1} {2}", addressMapName, addressNew.Id, address["DataType"]));
|
if (addressNew.DataType == null) throw new ArgumentNullException(string.Format("DataType define error {0} {1} {2}", addressMapName, addressNew.Id, address["DataType"]));
|
||||||
ans.Add(addressNew);
|
ans.Add(addressNew);
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Modbus.Net
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// AsyncHelper Class
|
|
||||||
/// </summary>
|
|
||||||
public static class AsyncHelper
|
|
||||||
{
|
|
||||||
private static readonly TaskFactory _myTaskFactory = new
|
|
||||||
TaskFactory(CancellationToken.None,
|
|
||||||
TaskCreationOptions.None,
|
|
||||||
TaskContinuationOptions.None,
|
|
||||||
TaskScheduler.Default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run async method syncronized
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TResult">Return type</typeparam>
|
|
||||||
/// <param name="func">Async method with return</param>
|
|
||||||
/// <returns>Return value</returns>
|
|
||||||
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
|
|
||||||
{
|
|
||||||
return _myTaskFactory
|
|
||||||
.StartNew(func)
|
|
||||||
.Unwrap()
|
|
||||||
.GetAwaiter()
|
|
||||||
.GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run async method syncronized.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="func">Async method</param>
|
|
||||||
public static void RunSync(Func<Task> func)
|
|
||||||
{
|
|
||||||
_myTaskFactory
|
|
||||||
.StartNew(func)
|
|
||||||
.Unwrap()
|
|
||||||
.GetAwaiter()
|
|
||||||
.GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Change async task to async task with cancellation token
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="task">Async task</param>
|
|
||||||
/// <param name="token">Cancellation Token</param>
|
|
||||||
/// <returns>Task with Cancellation token</returns>
|
|
||||||
public static Task WithCancellation(this Task task,
|
|
||||||
CancellationToken token)
|
|
||||||
{
|
|
||||||
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a CancellationToken to async task
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">type of a task</typeparam>
|
|
||||||
/// <param name="task">Task</param>
|
|
||||||
/// <param name="token">Cancellation token</param>
|
|
||||||
/// <returns>Task with cancellation token</returns>
|
|
||||||
public static Task<T> WithCancellation<T>(this Task<T> task,
|
|
||||||
CancellationToken token)
|
|
||||||
{
|
|
||||||
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -307,13 +307,27 @@ namespace Modbus.Net
|
|||||||
/// <returns>对应的ValueHelper实例</returns>
|
/// <returns>对应的ValueHelper实例</returns>
|
||||||
public static ValueHelper GetInstance(Endian endian)
|
public static ValueHelper GetInstance(Endian endian)
|
||||||
{
|
{
|
||||||
|
if (EndianInstanceCache.ContainsKey(endian.ToString()))
|
||||||
|
{
|
||||||
|
return EndianInstanceCache[endian.ToString()];
|
||||||
|
}
|
||||||
foreach (var assembly in AssemblyHelper.GetAllLibraryAssemblies())
|
foreach (var assembly in AssemblyHelper.GetAllLibraryAssemblies())
|
||||||
{
|
{
|
||||||
var valueHelper = assembly.GetType("Modbus.Net."+endian.ToString() + "ValueHelper")?.GetProperty("Instance")?.GetValue(null, null) as ValueHelper;
|
var valueHelper = assembly.GetType("Modbus.Net."+endian.ToString() + "ValueHelper")?.GetProperty("Instance")?.GetValue(null, null) as ValueHelper;
|
||||||
if (valueHelper != null) return valueHelper;
|
if (valueHelper != null)
|
||||||
|
{
|
||||||
|
EndianInstanceCache[endian.ToString()] = valueHelper;
|
||||||
|
return valueHelper;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
throw new NotImplementedException("ValueHelper " + endian.ToString() + " doesn't exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ValueHelper实例的缓存
|
||||||
|
/// </summary>
|
||||||
|
protected static Dictionary<string, ValueHelper> EndianInstanceCache = new Dictionary<string, ValueHelper>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -15,26 +15,12 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IMachineMethodDatas : IMachineMethod
|
public interface IMachineMethodDatas : IMachineMethod
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 读取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>从设备读取的数据</returns>
|
|
||||||
ReturnStruct<Dictionary<string, ReturnUnit<double>>> GetDatas(MachineDataType getDataType);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取数据
|
/// 读取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>从设备读取的数据</returns>
|
/// <returns>从设备读取的数据</returns>
|
||||||
Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType);
|
Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 写入数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="setDataType">写入类型</param>
|
|
||||||
/// <param name="values">需要写入的数据字典,当写入类型为Address时,键为需要写入的地址,当写入类型为CommunicationTag时,键为需要写入的单元的描述</param>
|
|
||||||
/// <returns>是否写入成功</returns>
|
|
||||||
ReturnStruct<bool> SetDatas(MachineDataType setDataType, Dictionary<string, double> values);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 写入数据
|
/// 写入数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -39,13 +39,6 @@ namespace Modbus.Net
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool Disconnect();
|
bool Disconnect();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送协议内容并接收,一般方法
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">写入的内容,使用对象数组描述</param>
|
|
||||||
/// <returns>从设备获取的字节流</returns>
|
|
||||||
TPipeUnit SendReceive(params object[] content);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送协议内容并接收,一般方法
|
/// 发送协议内容并接收,一般方法
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -53,14 +46,6 @@ namespace Modbus.Net
|
|||||||
/// <returns>从设备获取的字节流</returns>
|
/// <returns>从设备获取的字节流</returns>
|
||||||
Task<TPipeUnit> SendReceiveAsync(params object[] content);
|
Task<TPipeUnit> SendReceiveAsync(params object[] content);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="unit">协议的实例</param>
|
|
||||||
/// <param name="content">输入信息的结构化描述</param>
|
|
||||||
/// <returns>输出信息的结构化描述</returns>
|
|
||||||
TPipeUnit SendReceive(TProtocolUnit unit, IInputStruct content);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -69,16 +54,6 @@ namespace Modbus.Net
|
|||||||
/// <returns>输出信息的结构化描述</returns>
|
/// <returns>输出信息的结构化描述</returns>
|
||||||
Task<TPipeUnit> SendReceiveAsync(TProtocolUnit unit, IInputStruct content);
|
Task<TPipeUnit> SendReceiveAsync(TProtocolUnit unit, IInputStruct content);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="unit">协议的实例</param>
|
|
||||||
/// <param name="content">输入信息的结构化描述</param>
|
|
||||||
/// <returns>输出信息的结构化描述</returns>
|
|
||||||
/// <typeparam name="T">IOutputStruct的具体类型</typeparam>
|
|
||||||
T SendReceive<T>(
|
|
||||||
TProtocolUnit unit, IInputStruct content) where T : class, IOutputStruct;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -31,13 +31,6 @@ namespace Modbus.Net
|
|||||||
/// <returns>设备是否断开成功</returns>
|
/// <returns>设备是否断开成功</returns>
|
||||||
bool Disconnect();
|
bool Disconnect();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送并接收数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">发送协议的内容</param>
|
|
||||||
/// <returns>接收协议的内容</returns>
|
|
||||||
TParamOut SendReceive(TParamIn content);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送并接收数据
|
/// 发送并接收数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -45,13 +38,6 @@ namespace Modbus.Net
|
|||||||
/// <returns>接收协议的内容</returns>
|
/// <returns>接收协议的内容</returns>
|
||||||
Task<TParamOut> SendReceiveAsync(TParamIn content);
|
Task<TParamOut> SendReceiveAsync(TParamIn content);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">发送协议的内容</param>
|
|
||||||
/// <returns>接收协议的内容</returns>
|
|
||||||
TParamOut SendReceiveWithoutExtAndDec(TParamIn content);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
|
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -65,19 +51,5 @@ namespace Modbus.Net
|
|||||||
/// <param name="content">接收协议的内容</param>
|
/// <param name="content">接收协议的内容</param>
|
||||||
/// <returns>协议是否是正确的</returns>
|
/// <returns>协议是否是正确的</returns>
|
||||||
bool? CheckRight(TParamOut content);
|
bool? CheckRight(TParamOut content);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 协议内容扩展,发送时根据需要扩展
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">扩展前的基本协议内容</param>
|
|
||||||
/// <returns>扩展后的协议内容</returns>
|
|
||||||
TParamIn BytesExtend(TParamIn content);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 协议内容缩减,接收时根据需要缩减
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">缩减前的完整协议内容</param>
|
|
||||||
/// <returns>缩减后的协议内容</returns>
|
|
||||||
TParamOut BytesDecact(TParamOut content);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,14 +16,6 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IUtilityMethodDatas : IUtilityMethod
|
public interface IUtilityMethodDatas : IUtilityMethod
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 获取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="getByteCount">获取字节数个数</param>
|
|
||||||
/// <returns>接收到的byte数据</returns>
|
|
||||||
ReturnStruct<byte[]> GetDatas(string startAddress, int getByteCount);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -32,14 +24,6 @@ namespace Modbus.Net
|
|||||||
/// <returns>接收到的byte数据</returns>
|
/// <returns>接收到的byte数据</returns>
|
||||||
Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount);
|
Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="getTypeAndCount">获取类型和个数</param>
|
|
||||||
/// <returns>接收到的对应的类型和数据</returns>
|
|
||||||
ReturnStruct<object[]> GetDatas(string startAddress, KeyValuePair<Type, int> getTypeAndCount);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -48,15 +32,6 @@ namespace Modbus.Net
|
|||||||
/// <returns>接收到的对应的类型和数据</returns>
|
/// <returns>接收到的对应的类型和数据</returns>
|
||||||
Task<ReturnStruct<object[]>> GetDatasAsync(string startAddress, KeyValuePair<Type, int> getTypeAndCount);
|
Task<ReturnStruct<object[]>> GetDatasAsync(string startAddress, KeyValuePair<Type, int> getTypeAndCount);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">需要接收的类型</typeparam>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="getByteCount">获取字节数个数</param>
|
|
||||||
/// <returns>接收到的对应的类型和数据</returns>
|
|
||||||
ReturnStruct<T[]> GetDatas<T>(string startAddress, int getByteCount);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -66,14 +41,6 @@ namespace Modbus.Net
|
|||||||
/// <returns>接收到的对应的类型和数据</returns>
|
/// <returns>接收到的对应的类型和数据</returns>
|
||||||
Task<ReturnStruct<T[]>> GetDatasAsync<T>(string startAddress, int getByteCount);
|
Task<ReturnStruct<T[]>> GetDatasAsync<T>(string startAddress, int getByteCount);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="getTypeAndCountList">获取类型和个数的队列</param>
|
|
||||||
/// <returns>获取数据的对象数组,请强制转换成相应类型</returns>
|
|
||||||
ReturnStruct<object[]> GetDatas(string startAddress, IEnumerable<KeyValuePair<Type, int>> getTypeAndCountList);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -81,14 +48,6 @@ namespace Modbus.Net
|
|||||||
/// <param name="getTypeAndCountList">获取类型和个数的队列</param>
|
/// <param name="getTypeAndCountList">获取类型和个数的队列</param>
|
||||||
Task<ReturnStruct<object[]>> GetDatasAsync(string startAddress, IEnumerable<KeyValuePair<Type, int>> getTypeAndCountList);
|
Task<ReturnStruct<object[]>> GetDatasAsync(string startAddress, IEnumerable<KeyValuePair<Type, int>> getTypeAndCountList);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="setContents">设置数据</param>
|
|
||||||
/// <returns>是否设置成功</returns>
|
|
||||||
ReturnStruct<bool> SetDatas(string startAddress, object[] setContents);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置数据
|
/// 设置数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using FastEnumUtility;
|
using System;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
|
|
||||||
namespace Modbus.Net
|
namespace Modbus.Net
|
||||||
@@ -23,11 +23,11 @@ namespace Modbus.Net
|
|||||||
protected ComProtocolLinker(string com, int slaveAddress, BaudRate? baudRate = null, Parity? parity = null, StopBits? stopBits = null, DataBits? dataBits = null, Handshake? handshake = null,
|
protected ComProtocolLinker(string com, int slaveAddress, BaudRate? baudRate = null, Parity? parity = null, StopBits? stopBits = null, DataBits? dataBits = null, Handshake? handshake = null,
|
||||||
int? connectionTimeout = null, bool? isFullDuplex = null)
|
int? connectionTimeout = null, bool? isFullDuplex = null)
|
||||||
{
|
{
|
||||||
baudRate = FastEnum.Parse<BaudRate>(baudRate != null ? baudRate.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "BaudRate"));
|
baudRate = Enum.Parse<BaudRate>(baudRate != null ? baudRate.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "BaudRate"));
|
||||||
parity = FastEnum.Parse<Parity>(parity != null ? parity.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Parity"));
|
parity = Enum.Parse<Parity>(parity != null ? parity.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Parity"));
|
||||||
stopBits = FastEnum.Parse<StopBits>(stopBits != null ? stopBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "StopBits"));
|
stopBits = Enum.Parse<StopBits>(stopBits != null ? stopBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "StopBits"));
|
||||||
dataBits = FastEnum.Parse<DataBits>(dataBits != null ? dataBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "DataBits"));
|
dataBits = Enum.Parse<DataBits>(dataBits != null ? dataBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "DataBits"));
|
||||||
handshake = FastEnum.Parse<Handshake>(handshake != null ? handshake.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Handshake"));
|
handshake = Enum.Parse<Handshake>(handshake != null ? handshake.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Handshake"));
|
||||||
connectionTimeout = int.Parse(connectionTimeout != null ? connectionTimeout.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "ConnectionTimeout"));
|
connectionTimeout = int.Parse(connectionTimeout != null ? connectionTimeout.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "ConnectionTimeout"));
|
||||||
isFullDuplex = bool.Parse(isFullDuplex != null ? isFullDuplex.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "FullDuplex"));
|
isFullDuplex = bool.Parse(isFullDuplex != null ? isFullDuplex.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "FullDuplex"));
|
||||||
BaseConnector = new ComConnector(com + ":" + slaveAddress, baudRate.Value, parity.Value, stopBits.Value, dataBits.Value, handshake.Value, connectionTimeout.Value, isFullDuplex.Value);
|
BaseConnector = new ComConnector(com + ":" + slaveAddress, baudRate.Value, parity.Value, stopBits.Value, dataBits.Value, handshake.Value, connectionTimeout.Value, isFullDuplex.Value);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Modbus.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 基本的协议连接器
|
/// 基本的协议连接器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ProtocolLinker : ProtocolLinker<byte[], byte[]>
|
public abstract class ProtocolLinker : ProtocolLinker<byte[], byte[]>, IProtocolLinkerBytesExtend<byte[], byte[]>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送并接收数据
|
/// 发送并接收数据
|
||||||
@@ -41,7 +41,7 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="content">扩展前的基本协议内容</param>
|
/// <param name="content">扩展前的基本协议内容</param>
|
||||||
/// <returns>扩展后的协议内容</returns>
|
/// <returns>扩展后的协议内容</returns>
|
||||||
public override byte[] BytesExtend(byte[] content)
|
public virtual byte[] BytesExtend(byte[] content)
|
||||||
{
|
{
|
||||||
//自动查找相应的协议放缩类,命令规则为——当前的实际类名(注意是继承后的)+"BytesExtend"。
|
//自动查找相应的协议放缩类,命令规则为——当前的实际类名(注意是继承后的)+"BytesExtend"。
|
||||||
var bytesExtend =
|
var bytesExtend =
|
||||||
@@ -56,7 +56,7 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="content">缩减前的完整协议内容</param>
|
/// <param name="content">缩减前的完整协议内容</param>
|
||||||
/// <returns>缩减后的协议内容</returns>
|
/// <returns>缩减后的协议内容</returns>
|
||||||
public override byte[] BytesDecact(byte[] content)
|
public virtual byte[] BytesDecact(byte[] content)
|
||||||
{
|
{
|
||||||
//自动查找相应的协议放缩类,命令规则为——当前的实际类名(注意是继承后的)+"BytesExtend"。
|
//自动查找相应的协议放缩类,命令规则为——当前的实际类名(注意是继承后的)+"BytesExtend"。
|
||||||
var bytesExtend =
|
var bytesExtend =
|
||||||
@@ -122,16 +122,6 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsConnected => BaseConnector != null && BaseConnector.IsConnected;
|
public bool IsConnected => BaseConnector != null && BaseConnector.IsConnected;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送并接收数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">发送协议的内容</param>
|
|
||||||
/// <returns>接收协议的内容</returns>
|
|
||||||
public virtual TParamOut SendReceive(TParamIn content)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SendReceiveAsync(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送并接收数据
|
/// 发送并接收数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -139,19 +129,8 @@ namespace Modbus.Net
|
|||||||
/// <returns>接收协议的内容</returns>
|
/// <returns>接收协议的内容</returns>
|
||||||
public virtual async Task<TParamOut> SendReceiveAsync(TParamIn content)
|
public virtual async Task<TParamOut> SendReceiveAsync(TParamIn content)
|
||||||
{
|
{
|
||||||
var extBytes = BytesExtend(content);
|
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(content);
|
||||||
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes);
|
return receiveBytes;
|
||||||
return BytesDecact(receiveBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">发送协议的内容</param>
|
|
||||||
/// <returns>接收协议的内容</returns>
|
|
||||||
public virtual TParamOut SendReceiveWithoutExtAndDec(TParamIn content)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SendReceiveWithoutExtAndDecAsync(content));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -180,25 +159,5 @@ namespace Modbus.Net
|
|||||||
Disconnect();
|
Disconnect();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 协议内容扩展,发送时根据需要扩展
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">扩展前的基本协议内容</param>
|
|
||||||
/// <returns>扩展后的协议内容</returns>
|
|
||||||
public virtual TParamIn BytesExtend(TParamIn content)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 协议内容缩减,接收时根据需要缩减
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">缩减前的完整协议内容</param>
|
|
||||||
/// <returns>缩减后的协议内容</returns>
|
|
||||||
public virtual TParamOut BytesDecact(TParamOut content)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,20 +7,20 @@ namespace Modbus.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 地址组合器,组合后的每一组地址将只需一次向设备进行通讯
|
/// 地址组合器,组合后的每一组地址将只需一次向设备进行通讯
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class AddressCombiner<TKey> where TKey : IEquatable<TKey>
|
public abstract class AddressCombiner<TKey, TAddressKey, TSubAddressKey> where TKey : IEquatable<TKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 组合地址
|
/// 组合地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addresses">需要进行组合的地址</param>
|
/// <param name="addresses">需要进行组合的地址</param>
|
||||||
/// <returns>组合完成后与设备通讯的地址</returns>
|
/// <returns>组合完成后与设备通讯的地址</returns>
|
||||||
public abstract IEnumerable<CommunicationUnit<TKey>> Combine(IEnumerable<AddressUnit<TKey>> addresses);
|
public abstract IEnumerable<CommunicationUnit<TKey, TAddressKey, TSubAddressKey>> Combine(IEnumerable<AddressUnit<TKey, TAddressKey, TSubAddressKey>> addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 连续的地址将组合成一组,向设备进行通讯
|
/// 连续的地址将组合成一组,向设备进行通讯
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressCombinerContinus<TKey> : AddressCombiner<TKey> where TKey : IEquatable<TKey>
|
public class AddressCombinerContinus<TKey> : AddressCombiner<TKey, int, int> where TKey : IEquatable<TKey>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造函数
|
/// 构造函数
|
||||||
@@ -48,7 +48,7 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addresses">需要组合的地址</param>
|
/// <param name="addresses">需要组合的地址</param>
|
||||||
/// <returns>组合后的地址</returns>
|
/// <returns>组合后的地址</returns>
|
||||||
public override IEnumerable<CommunicationUnit<TKey>> Combine(IEnumerable<AddressUnit<TKey>> addresses)
|
public override IEnumerable<CommunicationUnit<TKey, int, int>> Combine(IEnumerable<AddressUnit<TKey, int, int>> addresses)
|
||||||
{
|
{
|
||||||
//按从小到大的顺序对地址进行排序
|
//按从小到大的顺序对地址进行排序
|
||||||
var groupedAddresses = from address in addresses
|
var groupedAddresses = from address in addresses
|
||||||
@@ -58,7 +58,7 @@ namespace Modbus.Net
|
|||||||
group address by address.Area
|
group address by address.Area
|
||||||
into grouped
|
into grouped
|
||||||
select grouped;
|
select grouped;
|
||||||
var ans = new List<CommunicationUnit<TKey>>();
|
var ans = new List<CommunicationUnit<TKey, int, int>>();
|
||||||
foreach (var groupedAddress in groupedAddresses)
|
foreach (var groupedAddress in groupedAddresses)
|
||||||
{
|
{
|
||||||
var area = groupedAddress.Key;
|
var area = groupedAddress.Key;
|
||||||
@@ -69,7 +69,7 @@ namespace Modbus.Net
|
|||||||
//上一个地址类型
|
//上一个地址类型
|
||||||
Type preType = null;
|
Type preType = null;
|
||||||
//记录一个地址组合当中的所有原始地址
|
//记录一个地址组合当中的所有原始地址
|
||||||
var originalAddresses = new List<AddressUnit<TKey>>();
|
var originalAddresses = new List<AddressUnit<TKey, int, int>>();
|
||||||
//对组合内地址从小到大进行排序
|
//对组合内地址从小到大进行排序
|
||||||
var orderedAddresses =
|
var orderedAddresses =
|
||||||
groupedAddress.OrderBy(
|
groupedAddress.OrderBy(
|
||||||
@@ -114,7 +114,7 @@ namespace Modbus.Net
|
|||||||
AddressTranslator.GetAreaByteLength(address.Area)))
|
AddressTranslator.GetAreaByteLength(address.Area)))
|
||||||
{
|
{
|
||||||
//上一个地址域压入返回结果,并把当前记录的结果清空。
|
//上一个地址域压入返回结果,并把当前记录的结果清空。
|
||||||
ans.Add(new CommunicationUnit<TKey>
|
ans.Add(new CommunicationUnit<TKey, int, int>
|
||||||
{
|
{
|
||||||
Area = area,
|
Area = area,
|
||||||
Address = (int)Math.Floor(initNum),
|
Address = (int)Math.Floor(initNum),
|
||||||
@@ -144,7 +144,7 @@ namespace Modbus.Net
|
|||||||
preType = address.DataType;
|
preType = address.DataType;
|
||||||
}
|
}
|
||||||
//最后一个地址域压入返回结果
|
//最后一个地址域压入返回结果
|
||||||
ans.Add(new CommunicationUnit<TKey>
|
ans.Add(new CommunicationUnit<TKey, int, int>
|
||||||
{
|
{
|
||||||
Area = area,
|
Area = area,
|
||||||
Address = (int)Math.Floor(initNum),
|
Address = (int)Math.Floor(initNum),
|
||||||
@@ -167,9 +167,9 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ans">拆分前的连续地址池</param>
|
/// <param name="ans">拆分前的连续地址池</param>
|
||||||
/// <returns>拆分后的连续地址池</returns>
|
/// <returns>拆分后的连续地址池</returns>
|
||||||
protected List<CommunicationUnit<TKey>> MaxExclude(List<CommunicationUnit<TKey>> ans)
|
protected List<CommunicationUnit<TKey, int, int>> MaxExclude(List<CommunicationUnit<TKey, int, int>> ans)
|
||||||
{
|
{
|
||||||
var newAns = new List<CommunicationUnit<TKey>>();
|
var newAns = new List<CommunicationUnit<TKey, int, int>>();
|
||||||
foreach (var communicationUnit in ans)
|
foreach (var communicationUnit in ans)
|
||||||
{
|
{
|
||||||
var oldByteCount = communicationUnit.GetCount *
|
var oldByteCount = communicationUnit.GetCount *
|
||||||
@@ -178,7 +178,7 @@ namespace Modbus.Net
|
|||||||
while (oldByteCount * ValueHelper.ByteLength[communicationUnit.DataType.FullName] >
|
while (oldByteCount * ValueHelper.ByteLength[communicationUnit.DataType.FullName] >
|
||||||
MaxLength)
|
MaxLength)
|
||||||
{
|
{
|
||||||
var newOriginalAddresses = new List<AddressUnit<TKey>>();
|
var newOriginalAddresses = new List<AddressUnit<TKey, int, int>>();
|
||||||
var newByteCount = 0.0;
|
var newByteCount = 0.0;
|
||||||
var newAddressUnitStart = oldOriginalAddresses.First();
|
var newAddressUnitStart = oldOriginalAddresses.First();
|
||||||
do
|
do
|
||||||
@@ -190,7 +190,7 @@ namespace Modbus.Net
|
|||||||
newOriginalAddresses.Add(currentAddressUnit);
|
newOriginalAddresses.Add(currentAddressUnit);
|
||||||
oldOriginalAddresses.RemoveAt(0);
|
oldOriginalAddresses.RemoveAt(0);
|
||||||
} while (newByteCount < MaxLength);
|
} while (newByteCount < MaxLength);
|
||||||
var newCommunicationUnit = new CommunicationUnit<TKey>
|
var newCommunicationUnit = new CommunicationUnit<TKey, int, int>
|
||||||
{
|
{
|
||||||
Area = newAddressUnitStart.Area,
|
Area = newAddressUnitStart.Area,
|
||||||
Address = newAddressUnitStart.Address,
|
Address = newAddressUnitStart.Address,
|
||||||
@@ -225,26 +225,26 @@ namespace Modbus.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 单个地址变为一组,每一个地址都进行一次查询
|
/// 单个地址变为一组,每一个地址都进行一次查询
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressCombinerSingle<TKey> : AddressCombiner<TKey> where TKey : IEquatable<TKey>
|
public class AddressCombinerSingle<TKey, TAddressKey, TSubAddressKey> : AddressCombiner<TKey, TAddressKey, TSubAddressKey> where TKey : IEquatable<TKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 组合地址
|
/// 组合地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addresses">需要组合的地址</param>
|
/// <param name="addresses">需要组合的地址</param>
|
||||||
/// <returns>组合后的地址</returns>
|
/// <returns>组合后的地址</returns>
|
||||||
public override IEnumerable<CommunicationUnit<TKey>> Combine(IEnumerable<AddressUnit<TKey>> addresses)
|
public override IEnumerable<CommunicationUnit<TKey, TAddressKey, TSubAddressKey>> Combine(IEnumerable<AddressUnit<TKey, TAddressKey, TSubAddressKey>> addresses)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
addresses.Select(
|
addresses.Select(
|
||||||
address =>
|
address =>
|
||||||
new CommunicationUnit<TKey>
|
new CommunicationUnit<TKey, TAddressKey, TSubAddressKey>
|
||||||
{
|
{
|
||||||
Area = address.Area,
|
Area = address.Area,
|
||||||
Address = address.Address,
|
Address = address.Address,
|
||||||
SubAddress = address.SubAddress,
|
SubAddress = address.SubAddress,
|
||||||
DataType = address.DataType,
|
DataType = address.DataType,
|
||||||
GetCount = 1,
|
GetCount = 1,
|
||||||
OriginalAddresses = new List<AddressUnit<TKey>> { address }
|
OriginalAddresses = new List<AddressUnit<TKey, TAddressKey, TSubAddressKey>> { address }
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +254,7 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class CommunicationUnitGap<TKey> where TKey : IEquatable<TKey>
|
internal class CommunicationUnitGap<TKey> where TKey : IEquatable<TKey>
|
||||||
{
|
{
|
||||||
public CommunicationUnit<TKey> EndUnit { get; set; }
|
public CommunicationUnit<TKey, int, int> EndUnit { get; set; }
|
||||||
public int GapNumber { get; set; }
|
public int GapNumber { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,11 +285,11 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addresses">需要组合的地址</param>
|
/// <param name="addresses">需要组合的地址</param>
|
||||||
/// <returns>组合后的地址</returns>
|
/// <returns>组合后的地址</returns>
|
||||||
public override IEnumerable<CommunicationUnit<TKey>> Combine(IEnumerable<AddressUnit<TKey>> addresses)
|
public override IEnumerable<CommunicationUnit<TKey, int, int>> Combine(IEnumerable<AddressUnit<TKey, int, int>> addresses)
|
||||||
{
|
{
|
||||||
var continusAddresses = base.Combine(addresses).ToList();
|
var continusAddresses = base.Combine(addresses).ToList();
|
||||||
var addressesGaps = new List<CommunicationUnitGap<TKey>>();
|
var addressesGaps = new List<CommunicationUnitGap<TKey>>();
|
||||||
CommunicationUnit<TKey> preCommunicationUnit = null;
|
CommunicationUnit<TKey, int, int> preCommunicationUnit = null;
|
||||||
foreach (var continusAddress in continusAddresses)
|
foreach (var continusAddress in continusAddresses)
|
||||||
{
|
{
|
||||||
if (preCommunicationUnit == null)
|
if (preCommunicationUnit == null)
|
||||||
@@ -335,7 +335,7 @@ namespace Modbus.Net
|
|||||||
continusAddresses.RemoveAt(index);
|
continusAddresses.RemoveAt(index);
|
||||||
continusAddresses.RemoveAt(index);
|
continusAddresses.RemoveAt(index);
|
||||||
//合并两个已有的地址段,变为一个新的地址段
|
//合并两个已有的地址段,变为一个新的地址段
|
||||||
var newAddress = new CommunicationUnit<TKey>
|
var newAddress = new CommunicationUnit<TKey, int, int>
|
||||||
{
|
{
|
||||||
Area = nowAddress.Area,
|
Area = nowAddress.Area,
|
||||||
Address = preAddress.Address,
|
Address = preAddress.Address,
|
||||||
@@ -382,9 +382,9 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addresses">需要组合的地址</param>
|
/// <param name="addresses">需要组合的地址</param>
|
||||||
/// <returns>组合后的地址</returns>
|
/// <returns>组合后的地址</returns>
|
||||||
public override IEnumerable<CommunicationUnit<TKey>> Combine(IEnumerable<AddressUnit<TKey>> addresses)
|
public override IEnumerable<CommunicationUnit<TKey, int, int>> Combine(IEnumerable<AddressUnit<TKey, int, int>> addresses)
|
||||||
{
|
{
|
||||||
var addressUnits = addresses as IList<AddressUnit<TKey>> ?? addresses.ToList();
|
var addressUnits = addresses as IList<AddressUnit<TKey, int, int>> ?? addresses.ToList();
|
||||||
var count = addressUnits.Sum(address => ValueHelper.ByteLength[address.DataType.FullName]);
|
var count = addressUnits.Sum(address => ValueHelper.ByteLength[address.DataType.FullName]);
|
||||||
return
|
return
|
||||||
new AddressCombinerNumericJump<TKey>((int)(count * Percentage / 100.0), MaxLength, AddressTranslator)
|
new AddressCombinerNumericJump<TKey>((int)(count * Percentage / 100.0), MaxLength, AddressTranslator)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
namespace Modbus.Net
|
using System;
|
||||||
|
|
||||||
|
namespace Modbus.Net
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 地址编码器
|
/// 地址编码器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class AddressFormater
|
public abstract class AddressFormater<TAddressKey, TSubAddressKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 编码地址
|
/// 编码地址
|
||||||
@@ -11,7 +13,7 @@
|
|||||||
/// <param name="area">地址所在的数据区域</param>
|
/// <param name="area">地址所在的数据区域</param>
|
||||||
/// <param name="address">地址</param>
|
/// <param name="address">地址</param>
|
||||||
/// <returns>编码后的地址</returns>
|
/// <returns>编码后的地址</returns>
|
||||||
public abstract string FormatAddress(string area, int address);
|
public abstract string FormatAddress(string area, TAddressKey address);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 编码地址
|
/// 编码地址
|
||||||
@@ -20,13 +22,13 @@
|
|||||||
/// <param name="address">地址</param>
|
/// <param name="address">地址</param>
|
||||||
/// <param name="subAddress">子地址</param>
|
/// <param name="subAddress">子地址</param>
|
||||||
/// <returns>编码后的地址</returns>
|
/// <returns>编码后的地址</returns>
|
||||||
public abstract string FormatAddress(string area, int address, int subAddress);
|
public abstract string FormatAddress(string area, TAddressKey address, TSubAddressKey subAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 基本的地址编码器
|
/// 基本的地址编码器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressFormaterBase : AddressFormater
|
public class AddressFormaterBase : AddressFormater<int, int>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 编码地址
|
/// 编码地址
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Quartz.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -11,37 +12,12 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TKey">设备的Id类型</typeparam>
|
/// <typeparam name="TKey">设备的Id类型</typeparam>
|
||||||
/// <typeparam name="TUnitKey">设备中使用的AddressUnit的Id类型</typeparam>
|
/// <typeparam name="TUnitKey">设备中使用的AddressUnit的Id类型</typeparam>
|
||||||
public abstract class BaseMachine<TKey, TUnitKey> : IMachine<TKey>
|
public abstract class BaseMachine<TKey, TUnitKey> : BaseMachine<TKey, TUnitKey, int, int>, IMachine<TKey>
|
||||||
where TKey : IEquatable<TKey>
|
where TKey : IEquatable<TKey>
|
||||||
where TUnitKey : IEquatable<TUnitKey>
|
where TUnitKey : IEquatable<TUnitKey>
|
||||||
{
|
{
|
||||||
private static readonly ILogger<BaseMachine<TKey, TUnitKey>> logger = LogProvider.CreateLogger<BaseMachine<TKey, TUnitKey>>();
|
private static readonly ILogger<BaseMachine<TKey, TUnitKey>> logger = LogProvider.CreateLogger<BaseMachine<TKey, TUnitKey>>();
|
||||||
|
|
||||||
private readonly int _maxErrorCount = 3;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">设备的ID号</param>
|
|
||||||
/// <param name="getAddresses">需要与设备通讯的地址</param>
|
|
||||||
protected BaseMachine(TKey id, IEnumerable<AddressUnit<TUnitKey>> getAddresses)
|
|
||||||
: this(id, getAddresses, false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">设备的ID号</param>
|
|
||||||
/// <param name="getAddresses">需要与设备通讯的地址</param>
|
|
||||||
/// <param name="keepConnect">是否保持连接</param>
|
|
||||||
protected BaseMachine(TKey id, IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
GetAddresses = getAddresses;
|
|
||||||
KeepConnect = keepConnect;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造器
|
/// 构造器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -50,70 +26,18 @@ namespace Modbus.Net
|
|||||||
/// <param name="keepConnect">是否保持连接</param>
|
/// <param name="keepConnect">是否保持连接</param>
|
||||||
/// <param name="slaveAddress">从站地址</param>
|
/// <param name="slaveAddress">从站地址</param>
|
||||||
/// <param name="masterAddress">主站地址</param>
|
/// <param name="masterAddress">主站地址</param>
|
||||||
protected BaseMachine(TKey id, IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect, byte slaveAddress,
|
protected BaseMachine(TKey id, IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, bool keepConnect, byte slaveAddress,
|
||||||
byte masterAddress) : this(id, getAddresses, keepConnect)
|
byte masterAddress) : base(id, getAddresses, keepConnect)
|
||||||
{
|
{
|
||||||
SlaveAddress = slaveAddress;
|
SlaveAddress = slaveAddress;
|
||||||
MasterAddress = masterAddress;
|
MasterAddress = masterAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private readonly int _maxErrorCount = 3;
|
||||||
|
|
||||||
private int ErrorCount { get; set; }
|
private int ErrorCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 地址编码器
|
|
||||||
/// </summary>
|
|
||||||
public AddressFormater AddressFormater { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取地址组合器
|
|
||||||
/// </summary>
|
|
||||||
public AddressCombiner<TUnitKey> AddressCombiner { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 写入地址组合器
|
|
||||||
/// </summary>
|
|
||||||
public AddressCombiner<TUnitKey> AddressCombinerSet { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 地址转换器
|
|
||||||
/// </summary>
|
|
||||||
public AddressTranslator AddressTranslator
|
|
||||||
{
|
|
||||||
get => BaseUtility.AddressTranslator;
|
|
||||||
set => BaseUtility.AddressTranslator = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 与设备实际通讯的连续地址
|
|
||||||
/// </summary>
|
|
||||||
protected IEnumerable<CommunicationUnit<TUnitKey>> CommunicateAddresses
|
|
||||||
=> GetAddresses != null ? AddressCombiner.Combine(GetAddresses) : null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 描述需要与设备通讯的地址
|
|
||||||
/// </summary>
|
|
||||||
private IEnumerable<AddressUnit<TUnitKey>> getAddresses;
|
|
||||||
|
|
||||||
private object getAddressesLock = new object();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 描述需要与设备通讯的地址
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<AddressUnit<TUnitKey>> GetAddresses
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return getAddresses;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
lock (getAddressesLock)
|
|
||||||
{
|
|
||||||
getAddresses = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从站号
|
/// 从站号
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -125,20 +49,26 @@ namespace Modbus.Net
|
|||||||
public byte MasterAddress { get; set; }
|
public byte MasterAddress { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取数据
|
/// 与设备实际通讯的连续地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>从设备读取的数据</returns>
|
protected IEnumerable<CommunicationUnit<TUnitKey, int, int>> CommunicateAddresses
|
||||||
public ReturnStruct<Dictionary<string, ReturnUnit<double>>> GetDatas(MachineDataType getDataType)
|
=> GetAddresses != null ? AddressCombiner.Combine(GetAddresses) : null;
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => GetDatasAsync(getDataType));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取地址组合器
|
||||||
|
/// </summary>
|
||||||
|
public AddressCombiner<TUnitKey, int, int> AddressCombiner { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写入地址组合器
|
||||||
|
/// </summary>
|
||||||
|
public AddressCombiner<TUnitKey, int, int> AddressCombinerSet { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取数据
|
/// 读取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>从设备读取的数据</returns>
|
/// <returns>从设备读取的数据</returns>
|
||||||
public async Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType)
|
public async override Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -316,18 +246,7 @@ namespace Modbus.Net
|
|||||||
/// <param name="setDataType">写入类型</param>
|
/// <param name="setDataType">写入类型</param>
|
||||||
/// <param name="values">需要写入的数据字典,当写入类型为Address时,键为需要写入的地址,当写入类型为CommunicationTag时,键为需要写入的单元的描述</param>
|
/// <param name="values">需要写入的数据字典,当写入类型为Address时,键为需要写入的地址,当写入类型为CommunicationTag时,键为需要写入的单元的描述</param>
|
||||||
/// <returns>是否写入成功</returns>
|
/// <returns>是否写入成功</returns>
|
||||||
public ReturnStruct<bool> SetDatas(MachineDataType setDataType, Dictionary<string, double> values)
|
public async override Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values)
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SetDatasAsync(setDataType, values));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 写入数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="setDataType">写入类型</param>
|
|
||||||
/// <param name="values">需要写入的数据字典,当写入类型为Address时,键为需要写入的地址,当写入类型为CommunicationTag时,键为需要写入的单元的描述</param>
|
|
||||||
/// <returns>是否写入成功</returns>
|
|
||||||
public async Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -342,12 +261,12 @@ namespace Modbus.Net
|
|||||||
ErrorCode = -1,
|
ErrorCode = -1,
|
||||||
ErrorMsg = "Connection Error"
|
ErrorMsg = "Connection Error"
|
||||||
};
|
};
|
||||||
var addresses = new List<AddressUnit<TUnitKey>>();
|
var addresses = new List<AddressUnit<TUnitKey, int, int>>();
|
||||||
//遍历每个要设置的值
|
//遍历每个要设置的值
|
||||||
foreach (var value in values)
|
foreach (var value in values)
|
||||||
{
|
{
|
||||||
//根据设置类型找到对应的地址描述
|
//根据设置类型找到对应的地址描述
|
||||||
AddressUnit<TUnitKey> address = null;
|
AddressUnit<TUnitKey, int, int> address = null;
|
||||||
switch (setDataType)
|
switch (setDataType)
|
||||||
{
|
{
|
||||||
case MachineDataType.Address:
|
case MachineDataType.Address:
|
||||||
@@ -550,6 +469,49 @@ namespace Modbus.Net
|
|||||||
ErrorMsg = ""
|
ErrorMsg = ""
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">设备的Id类型</typeparam>
|
||||||
|
/// <typeparam name="TUnitKey">设备中使用的AddressUnit的Id类型</typeparam>
|
||||||
|
/// <typeparam name="TAddressKey">设备中使用的AddressUnit的Address类型</typeparam>
|
||||||
|
/// <typeparam name="TSubAddressKey">设备中使用的AddressUnit的SubAddress类型</typeparam>
|
||||||
|
public abstract class BaseMachine<TKey, TUnitKey, TAddressKey, TSubAddressKey> : IMachine<TKey>
|
||||||
|
where TKey : IEquatable<TKey>
|
||||||
|
where TUnitKey : IEquatable<TUnitKey>
|
||||||
|
where TAddressKey : IEquatable<TAddressKey>
|
||||||
|
where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
|
{
|
||||||
|
private static readonly ILogger<BaseMachine<TKey, TUnitKey, TAddressKey, TSubAddressKey>> logger = LogProvider.CreateLogger<BaseMachine<TKey, TUnitKey, TAddressKey, TSubAddressKey>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">设备的ID号</param>
|
||||||
|
/// <param name="getAddresses">需要与设备通讯的地址</param>
|
||||||
|
protected BaseMachine(TKey id, IEnumerable<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>> getAddresses)
|
||||||
|
: this(id, getAddresses, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">设备的ID号</param>
|
||||||
|
/// <param name="getAddresses">需要与设备通讯的地址</param>
|
||||||
|
/// <param name="keepConnect">是否保持连接</param>
|
||||||
|
protected BaseMachine(TKey id, IEnumerable<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>> getAddresses, bool keepConnect)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
GetAddresses = getAddresses;
|
||||||
|
KeepConnect = keepConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly int _maxErrorCount = 3;
|
||||||
|
|
||||||
|
private int ErrorCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否处于连接状态
|
/// 是否处于连接状态
|
||||||
@@ -586,6 +548,383 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string ConnectionToken => BaseUtility.ConnectionToken;
|
public string ConnectionToken => BaseUtility.ConnectionToken;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述需要与设备通讯的地址
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>> getAddresses;
|
||||||
|
|
||||||
|
private object getAddressesLock = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述需要与设备通讯的地址
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>> GetAddresses
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return getAddresses;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (getAddressesLock)
|
||||||
|
{
|
||||||
|
getAddresses = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 地址编码器
|
||||||
|
/// </summary>
|
||||||
|
public AddressFormater<TAddressKey, TSubAddressKey> AddressFormater { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 地址转换器
|
||||||
|
/// </summary>
|
||||||
|
public AddressTranslator AddressTranslator
|
||||||
|
{
|
||||||
|
get => BaseUtility.AddressTranslator;
|
||||||
|
set => BaseUtility.AddressTranslator = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取数据
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>从设备读取的数据</returns>
|
||||||
|
public async virtual Task<ReturnStruct<Dictionary<string, ReturnUnit<double>>>> GetDatasAsync(MachineDataType getDataType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ans = new Dictionary<string, ReturnUnit<double>>();
|
||||||
|
//检测并连接设备
|
||||||
|
if (!BaseUtility.IsConnected)
|
||||||
|
await BaseUtility.ConnectAsync();
|
||||||
|
//如果无法连接,终止
|
||||||
|
if (!BaseUtility.IsConnected) return
|
||||||
|
new ReturnStruct<Dictionary<string, ReturnUnit<double>>>()
|
||||||
|
{
|
||||||
|
Datas = null,
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorCode = -1,
|
||||||
|
ErrorMsg = "Connection Error"
|
||||||
|
};
|
||||||
|
//遍历每一个实际向设备获取数据的连续地址
|
||||||
|
foreach (var address in GetAddresses)
|
||||||
|
{
|
||||||
|
//获取数据
|
||||||
|
var datas =
|
||||||
|
await
|
||||||
|
BaseUtility.GetUtilityMethods<IUtilityMethodDatas>().GetDatasAsync(
|
||||||
|
AddressFormater.FormatAddress(address.Area, address.Address,
|
||||||
|
address.SubAddress),
|
||||||
|
(int)
|
||||||
|
Math.Ceiling(ValueHelper.ByteLength[
|
||||||
|
address.DataType.FullName]));
|
||||||
|
|
||||||
|
|
||||||
|
//如果没有数据,终止
|
||||||
|
if (datas.IsSuccess == false || datas.Datas == null)
|
||||||
|
{
|
||||||
|
return new ReturnStruct<Dictionary<string, ReturnUnit<double>>>()
|
||||||
|
{
|
||||||
|
Datas = null,
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorCode = datas.ErrorCode,
|
||||||
|
ErrorMsg = datas.ErrorMsg
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (datas.Datas.Length != 0 && datas.Datas.Length <
|
||||||
|
(int)
|
||||||
|
Math.Ceiling(ValueHelper.ByteLength[
|
||||||
|
address.DataType.FullName]))
|
||||||
|
{
|
||||||
|
return new ReturnStruct<Dictionary<string, ReturnUnit<double>>>()
|
||||||
|
{
|
||||||
|
Datas = null,
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorCode = -2,
|
||||||
|
ErrorMsg = "Data length mismatch"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//字节坐标的主地址位置
|
||||||
|
var localMainPos = 0;
|
||||||
|
//字节坐标的子地址位置
|
||||||
|
var localSubPos = 0;
|
||||||
|
|
||||||
|
//根据类型选择返回结果的键是通讯标识还是地址
|
||||||
|
string key;
|
||||||
|
switch (getDataType)
|
||||||
|
{
|
||||||
|
case MachineDataType.CommunicationTag:
|
||||||
|
{
|
||||||
|
key = address.CommunicationTag;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.Address:
|
||||||
|
{
|
||||||
|
key = AddressFormater.FormatAddress(address.Area, address.Address, address.SubAddress);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.Name:
|
||||||
|
{
|
||||||
|
key = address.Name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.Id:
|
||||||
|
{
|
||||||
|
key = address.Id.ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
key = address.CommunicationTag;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//如果没有数据返回空
|
||||||
|
if (datas.Datas.Length == 0)
|
||||||
|
ans.Add(key, new ReturnUnit<double>
|
||||||
|
{
|
||||||
|
DeviceValue = null,
|
||||||
|
AddressUnit = address.MapAddressUnitTUnitKeyToAddressUnit(),
|
||||||
|
});
|
||||||
|
else
|
||||||
|
ans.Add(key,
|
||||||
|
new ReturnUnit<double>
|
||||||
|
{
|
||||||
|
DeviceValue =
|
||||||
|
Math.Round(Convert.ToDouble(
|
||||||
|
ValueHelper.GetInstance(BaseUtility.Endian)
|
||||||
|
.GetValue(datas.Datas, ref localMainPos, ref localSubPos,
|
||||||
|
address.DataType)) * address.Zoom, address.DecimalPos),
|
||||||
|
AddressUnit = address.MapAddressUnitTUnitKeyToAddressUnit(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ErrorCount++;
|
||||||
|
logger.LogError(e, $"BaseMachine -> GetDatas, Id:{Id} Connection:{ConnectionToken} key {key} existing. ErrorCount {ErrorCount}.");
|
||||||
|
|
||||||
|
if (ErrorCount >= _maxErrorCount)
|
||||||
|
Disconnect();
|
||||||
|
return new ReturnStruct<Dictionary<string, ReturnUnit<double>>>()
|
||||||
|
{
|
||||||
|
Datas = null,
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorCode = -3,
|
||||||
|
ErrorMsg = "Data translation mismatch"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//如果不保持连接,断开连接
|
||||||
|
if (!KeepConnect)
|
||||||
|
BaseUtility.Disconnect();
|
||||||
|
//返回数据
|
||||||
|
if (ans.All(p => p.Value.DeviceValue == null)) ans = null;
|
||||||
|
ErrorCount = 0;
|
||||||
|
return new ReturnStruct<Dictionary<string, ReturnUnit<double>>>
|
||||||
|
{
|
||||||
|
Datas = ans,
|
||||||
|
IsSuccess = true,
|
||||||
|
ErrorCode = 0,
|
||||||
|
ErrorMsg = ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ErrorCount++;
|
||||||
|
logger.LogError(e, $"BaseMachine -> GetDatas, Id:{Id} Connection:{ConnectionToken} error. ErrorCount {ErrorCount}.");
|
||||||
|
|
||||||
|
if (ErrorCount >= _maxErrorCount)
|
||||||
|
Disconnect();
|
||||||
|
return new ReturnStruct<Dictionary<string, ReturnUnit<double>>>()
|
||||||
|
{
|
||||||
|
Datas = null,
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorCode = -100,
|
||||||
|
ErrorMsg = "Unknown Exception"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写入数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="setDataType">写入类型</param>
|
||||||
|
/// <param name="values">需要写入的数据字典,当写入类型为Address时,键为需要写入的地址,当写入类型为CommunicationTag时,键为需要写入的单元的描述</param>
|
||||||
|
/// <returns>是否写入成功</returns>
|
||||||
|
public async virtual Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//检测并连接设备
|
||||||
|
if (!BaseUtility.IsConnected)
|
||||||
|
await BaseUtility.ConnectAsync();
|
||||||
|
//如果设备无法连接,终止
|
||||||
|
if (!BaseUtility.IsConnected) return new ReturnStruct<bool>()
|
||||||
|
{
|
||||||
|
Datas = false,
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorCode = -1,
|
||||||
|
ErrorMsg = "Connection Error"
|
||||||
|
};
|
||||||
|
var addresses = new List<AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>>();
|
||||||
|
//遍历每个要设置的值
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
//根据设置类型找到对应的地址描述
|
||||||
|
AddressUnit<TUnitKey, TAddressKey, TSubAddressKey> address = null;
|
||||||
|
switch (setDataType)
|
||||||
|
{
|
||||||
|
case MachineDataType.Address:
|
||||||
|
{
|
||||||
|
address =
|
||||||
|
GetAddresses.SingleOrDefault(
|
||||||
|
p =>
|
||||||
|
AddressFormater.FormatAddress(p.Area, p.Address, p.SubAddress) == value.Key ||
|
||||||
|
p.DataType != typeof(bool) &&
|
||||||
|
AddressFormater.FormatAddress(p.Area, p.Address) == value.Key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.CommunicationTag:
|
||||||
|
{
|
||||||
|
address =
|
||||||
|
GetAddresses.SingleOrDefault(p => p.CommunicationTag == value.Key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.Name:
|
||||||
|
{
|
||||||
|
address = GetAddresses.SingleOrDefault(p => p.Name == value.Key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.Id:
|
||||||
|
{
|
||||||
|
address = GetAddresses.SingleOrDefault(p => p.Id.ToString() == value.Key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
address =
|
||||||
|
GetAddresses.SingleOrDefault(p => p.CommunicationTag == value.Key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//地址为空报错
|
||||||
|
if (address == null)
|
||||||
|
{
|
||||||
|
logger.LogError($"Machine {ConnectionToken} Address {value.Key} doesn't exist.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//不能写报错
|
||||||
|
if (!address.CanWrite)
|
||||||
|
{
|
||||||
|
logger.LogError($"Machine {ConnectionToken} Address {value.Key} cannot write.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addresses.Add(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
//遍历每条通讯的地址
|
||||||
|
|
||||||
|
|
||||||
|
var valueHelper = ValueHelper.GetInstance(BaseUtility.Endian);
|
||||||
|
|
||||||
|
foreach (var addressUnit in addresses)
|
||||||
|
{
|
||||||
|
//协议主地址字符串
|
||||||
|
var address = AddressFormater.FormatAddress(addressUnit.Area,
|
||||||
|
addressUnit.Address, addressUnit.SubAddress);
|
||||||
|
//获取写入类型
|
||||||
|
var dataType = addressUnit.DataType;
|
||||||
|
KeyValuePair<string, double> value;
|
||||||
|
switch (setDataType)
|
||||||
|
{
|
||||||
|
case MachineDataType.Address:
|
||||||
|
{
|
||||||
|
//获取要写入的值
|
||||||
|
value = values.SingleOrDefault(p => p.Key == address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.CommunicationTag:
|
||||||
|
{
|
||||||
|
value = values.SingleOrDefault(p => p.Key == addressUnit.CommunicationTag);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.Name:
|
||||||
|
{
|
||||||
|
value = values.SingleOrDefault(p => p.Key == addressUnit.Name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MachineDataType.Id:
|
||||||
|
{
|
||||||
|
value = values.SingleOrDefault(p => p.Key == addressUnit.Id.ToString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
value = values.SingleOrDefault(p => p.Key == addressUnit.CommunicationTag);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//将要写入的值加入队列
|
||||||
|
var data = Convert.ChangeType(value.Value / addressUnit.Zoom, dataType);
|
||||||
|
|
||||||
|
//写入数据
|
||||||
|
await
|
||||||
|
BaseUtility.GetUtilityMethods<IUtilityMethodDatas>().SetDatasAsync(address,
|
||||||
|
new object[] { data });
|
||||||
|
}
|
||||||
|
//如果不保持连接,断开连接
|
||||||
|
if (!KeepConnect)
|
||||||
|
BaseUtility.Disconnect();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ErrorCount++;
|
||||||
|
logger.LogError(e, $"BaseMachine -> SetDatas, Id:{Id} Connection:{ConnectionToken} error. ErrorCount {ErrorCount}.");
|
||||||
|
|
||||||
|
if (ErrorCount >= _maxErrorCount)
|
||||||
|
Disconnect();
|
||||||
|
return new ReturnStruct<bool>()
|
||||||
|
{
|
||||||
|
Datas = false,
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorCode = -100,
|
||||||
|
ErrorMsg = "Unknown Exception"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return new ReturnStruct<bool>()
|
||||||
|
{
|
||||||
|
Datas = true,
|
||||||
|
IsSuccess = true,
|
||||||
|
ErrorCode = 0,
|
||||||
|
ErrorMsg = ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过Id获取数据字段定义
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addressUnitId">数据字段Id</param>
|
||||||
|
/// <returns>数据字段</returns>
|
||||||
|
public AddressUnit<TUnitKey, TAddressKey, TSubAddressKey> GetAddressUnitById(TUnitKey addressUnitId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return GetAddresses.SingleOrDefault(p => p.Id.Equals(addressUnitId));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, $"BaseMachine -> GetAddressUnitById Id:{Id} ConnectionToken:{ConnectionToken} addressUnitId:{addressUnitId} Repeated");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取设备的方法集合
|
/// 获取设备的方法集合
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -627,24 +966,6 @@ namespace Modbus.Net
|
|||||||
{
|
{
|
||||||
return BaseUtility.Disconnect();
|
return BaseUtility.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 通过Id获取数据字段定义
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="addressUnitId">数据字段Id</param>
|
|
||||||
/// <returns>数据字段</returns>
|
|
||||||
public AddressUnit<TUnitKey> GetAddressUnitById(TUnitKey addressUnitId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return GetAddresses.SingleOrDefault(p => p.Id.Equals(addressUnitId));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logger.LogError(e, $"BaseMachine -> GetAddressUnitById Id:{Id} ConnectionToken:{ConnectionToken} addressUnitId:{addressUnitId} Repeated");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class BaseMachineEqualityComparer<TKey> : IEqualityComparer<IMachineProperty<TKey>>
|
internal class BaseMachineEqualityComparer<TKey> : IEqualityComparer<IMachineProperty<TKey>>
|
||||||
@@ -664,7 +985,7 @@ namespace Modbus.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通讯单元
|
/// 通讯单元
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CommunicationUnit<TKey> where TKey : IEquatable<TKey>
|
public class CommunicationUnit<TKey, TAddressKey, TSubAddressKey> where TKey : IEquatable<TKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 区域
|
/// 区域
|
||||||
@@ -674,12 +995,12 @@ namespace Modbus.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 地址
|
/// 地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Address { get; set; }
|
public TAddressKey Address { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 子地址
|
/// 子地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SubAddress { get; set; } = 0;
|
public TSubAddressKey SubAddress { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取个数
|
/// 获取个数
|
||||||
@@ -694,7 +1015,7 @@ namespace Modbus.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 原始的地址
|
/// 原始的地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<AddressUnit<TKey>> OriginalAddresses { get; set; }
|
public IEnumerable<AddressUnit<TKey, TAddressKey, TSubAddressKey>> OriginalAddresses { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -710,20 +1031,13 @@ namespace Modbus.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据定义
|
/// 数据定义
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AddressUnit AddressUnit { get; set; }
|
public AddressUnit<string, string, string> AddressUnit { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 地址单元
|
/// 地址单元
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddressUnit : AddressUnit<string>
|
public class AddressUnit<TKey, TAddressKey, TSubAddressKey> : IEquatable<AddressUnit<TKey, TAddressKey, TSubAddressKey>> where TKey : IEquatable<TKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 地址单元
|
|
||||||
/// </summary>
|
|
||||||
public class AddressUnit<TKey> : IEquatable<AddressUnit<TKey>> where TKey : IEquatable<TKey>
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据单元Id
|
/// 数据单元Id
|
||||||
@@ -738,12 +1052,12 @@ namespace Modbus.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 地址
|
/// 地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Address { get; set; }
|
public TAddressKey Address { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// bit位地址
|
/// bit位地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SubAddress { get; set; } = 0;
|
public TSubAddressKey SubAddress { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据类型
|
/// 数据类型
|
||||||
@@ -785,9 +1099,9 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="other">另一个地址</param>
|
/// <param name="other">另一个地址</param>
|
||||||
/// <returns>是否一致</returns>
|
/// <returns>是否一致</returns>
|
||||||
public bool Equals(AddressUnit<TKey> other)
|
public bool Equals(AddressUnit<TKey, TAddressKey, TSubAddressKey> other)
|
||||||
{
|
{
|
||||||
return Area.ToUpper() == other.Area.ToUpper() && Address == other.Address || Id.Equals(other.Id);
|
return Area.ToUpper() == other.Area.ToUpper() && Address.Equals(other.Address) || Id.Equals(other.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,14 +35,14 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="addressUnit"></param>
|
/// <param name="addressUnit"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static AddressUnit MapAddressUnitTUnitKeyToAddressUnit<TUnitKey>(this AddressUnit<TUnitKey> addressUnit) where TUnitKey : IEquatable<TUnitKey>
|
public static AddressUnit<string, string, string> MapAddressUnitTUnitKeyToAddressUnit<TUnitKey, TAddressKey, TSubAddressKey>(this AddressUnit<TUnitKey, TAddressKey, TSubAddressKey> addressUnit) where TUnitKey : IEquatable<TUnitKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
|
||||||
{
|
{
|
||||||
return new AddressUnit()
|
return new AddressUnit<string, string, string>()
|
||||||
{
|
{
|
||||||
Id = addressUnit.Id.ToString(),
|
Id = addressUnit.Id.ToString(),
|
||||||
Area = addressUnit.Area,
|
Area = addressUnit.Area,
|
||||||
Address = addressUnit.Address,
|
Address = addressUnit.Address?.ToString(),
|
||||||
SubAddress = addressUnit.SubAddress,
|
SubAddress = addressUnit.SubAddress?.ToString(),
|
||||||
DataType = addressUnit.DataType,
|
DataType = addressUnit.DataType,
|
||||||
Zoom = addressUnit.Zoom,
|
Zoom = addressUnit.Zoom,
|
||||||
DecimalPos = addressUnit.DecimalPos,
|
DecimalPos = addressUnit.DecimalPos,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net462</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
<LangVersion>10.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<AssemblyName>Modbus.Net</AssemblyName>
|
<AssemblyName>Modbus.Net</AssemblyName>
|
||||||
<RootNamespace>Modbus.Net</RootNamespace>
|
<RootNamespace>Modbus.Net</RootNamespace>
|
||||||
@@ -37,7 +37,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" />
|
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" />
|
||||||
<PackageReference Include="FastEnum" Version="1.8.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||||
|
|||||||
@@ -146,18 +146,6 @@ namespace Modbus.Net
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="unit">协议的实例</param>
|
|
||||||
/// <param name="content">输入信息的结构化描述</param>
|
|
||||||
/// <returns>输出信息的结构化描述</returns>
|
|
||||||
public virtual TPipeUnit SendReceive(
|
|
||||||
TProtocolUnit unit, IInputStruct content)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SendReceiveAsync(unit, content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -177,16 +165,6 @@ namespace Modbus.Net
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送协议内容并接收,一般方法
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="content">写入的内容,使用对象数组描述</param>
|
|
||||||
/// <returns>从设备获取的字节流</returns>
|
|
||||||
public virtual TPipeUnit SendReceive(params object[] content)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SendReceiveAsync(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送协议内容并接收,一般方法(不能使用,如需使用请继承)
|
/// 发送协议内容并接收,一般方法(不能使用,如需使用请继承)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -197,18 +175,6 @@ namespace Modbus.Net
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="unit">协议的实例</param>
|
|
||||||
/// <param name="content">输入信息的结构化描述</param>
|
|
||||||
/// <returns>输出信息的结构化描述</returns>
|
|
||||||
/// <typeparam name="T">IOutputStruct的具体类型</typeparam>
|
|
||||||
public virtual T SendReceive<T>(TProtocolUnit unit, IInputStruct content) where T : class, IOutputStruct
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SendReceiveAsync<T>(unit, content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
/// 发送协议,通过传入需要使用的协议内容和输入结构
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -45,17 +45,6 @@ namespace Modbus.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public byte MasterAddress { get; set; }
|
public byte MasterAddress { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="getByteCount">获取字节数个数</param>
|
|
||||||
/// <returns>接收到的byte数据</returns>
|
|
||||||
public virtual ReturnStruct<byte[]> GetDatas(string startAddress, int getByteCount)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => GetDatasAsync(startAddress, getByteCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -64,18 +53,6 @@ namespace Modbus.Net
|
|||||||
/// <returns>接收到的byte数据</returns>
|
/// <returns>接收到的byte数据</returns>
|
||||||
public abstract Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount);
|
public abstract Task<ReturnStruct<byte[]>> GetDatasAsync(string startAddress, int getByteCount);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="getTypeAndCount">获取类型和个数</param>
|
|
||||||
/// <returns>接收到的对应的类型和数据</returns>
|
|
||||||
public virtual ReturnStruct<object[]> GetDatas(string startAddress,
|
|
||||||
KeyValuePair<Type, int> getTypeAndCount)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => GetDatasAsync(startAddress, getTypeAndCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -123,19 +100,6 @@ namespace Modbus.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">需要接收的类型</typeparam>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="getByteCount">获取字节数个数</param>
|
|
||||||
/// <returns>接收到的对应的类型和数据</returns>
|
|
||||||
public virtual ReturnStruct<T[]> GetDatas<T>(string startAddress,
|
|
||||||
int getByteCount)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => GetDatasAsync<T>(startAddress, getByteCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -181,19 +145,6 @@ namespace Modbus.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="getTypeAndCountList">获取类型和个数的队列</param>
|
|
||||||
/// <returns>获取数据的对象数组,请强制转换成相应类型</returns>
|
|
||||||
public virtual ReturnStruct<object[]> GetDatas(string startAddress,
|
|
||||||
IEnumerable<KeyValuePair<Type, int>> getTypeAndCountList)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
AsyncHelper.RunSync(() => GetDatasAsync(startAddress, getTypeAndCountList));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数据
|
/// 获取数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -244,17 +195,6 @@ namespace Modbus.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startAddress">开始地址</param>
|
|
||||||
/// <param name="setContents">设置数据</param>
|
|
||||||
/// <returns>是否设置成功</returns>
|
|
||||||
public virtual ReturnStruct<bool> SetDatas(string startAddress, object[] setContents)
|
|
||||||
{
|
|
||||||
return AsyncHelper.RunSync(() => SetDatasAsync(startAddress, setContents));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置数据
|
/// 设置数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -31,3 +31,4 @@ Thanks
|
|||||||
* Quartz - Job Scheduler
|
* Quartz - Job Scheduler
|
||||||
* Serilog - Logging
|
* Serilog - Logging
|
||||||
* DotNetty - Network Transporting
|
* DotNetty - Network Transporting
|
||||||
|
* h-opc & Technosoftware.DaAeHdaSolution & OPCFoundation.NetStandard - OPC Trasporting
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Modbus.Net.Modbus;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using MachineJobSchedulerCreator = Modbus.Net.MachineJobSchedulerCreator<Modbus.Net.IMachineMethodDatas, string, double>;
|
using MachineJobSchedulerCreator = Modbus.Net.MachineJobSchedulerCreator<Modbus.Net.IMachineMethodDatas, string, double>;
|
||||||
using ModbusMachine = Modbus.Net.Modbus.ModbusMachine<string, string>;
|
using ModbusMachine = Modbus.Net.Modbus.ModbusMachine<string, string>;
|
||||||
|
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
|
||||||
|
|
||||||
namespace AnyType.Controllers
|
namespace AnyType.Controllers
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.Modbus\Modbus.Net.Modbus.csproj" />
|
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.Modbus\Modbus.Net.Modbus.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.OPC\Modbus.Net.Opc.csproj" />
|
||||||
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.Siemens\Modbus.Net.Siemens.csproj" />
|
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.Siemens\Modbus.Net.Siemens.csproj" />
|
||||||
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net\Modbus.Net.csproj" />
|
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net\Modbus.Net.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DatabaseWriteConnectionString": "Server=10.10.18.245; User ID=root; Password=123456; Database=modbusnettest;"
|
"DatabaseWriteConnectionString": "Server=127.0.0.1; User ID=root; Password=123456; Database=modbusnettest;"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Modbus.Net": {
|
"Modbus.Net": {
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"a:id": "ModbusMachine1",
|
"a:id": "ModbusMachine1",
|
||||||
"b:protocol": "Modbus",
|
"b:protocol": "Modbus",
|
||||||
"c:type": "Tcp",
|
"c:type": "Tcp",
|
||||||
"d:connectionString": "10.10.18.251",
|
"d:connectionString": "127.0.0.1",
|
||||||
"e:addressMap": "AddressMapModbus",
|
"e:addressMap": "AddressMapModbus",
|
||||||
"f:keepConnect": true,
|
"f:keepConnect": true,
|
||||||
"g:slaveAddress": 1,
|
"g:slaveAddress": 1,
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"a:id": "SiemensMachine1",
|
"a:id": "SiemensMachine1",
|
||||||
"b:protocol": "Siemens",
|
"b:protocol": "Siemens",
|
||||||
"c:type": "Tcp",
|
"c:type": "Tcp",
|
||||||
"d:connectionString": "10.10.18.251",
|
"d:connectionString": "127.0.0.1",
|
||||||
"e:model": "S7_1200",
|
"e:model": "S7_1200",
|
||||||
"f:addressMap": "AddressMapSiemens",
|
"f:addressMap": "AddressMapSiemens",
|
||||||
"g:keepConnect": true,
|
"g:keepConnect": true,
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"a:id": "SiemensMachine2",
|
"a:id": "SiemensMachine2",
|
||||||
"b:protocol": "Siemens",
|
"b:protocol": "Siemens",
|
||||||
"c:type": "Ppi",
|
"c:type": "Ppi",
|
||||||
"d:connectionString": "COM11",
|
"d:connectionString": "COM2",
|
||||||
"e:model": "S7_200",
|
"e:model": "S7_200",
|
||||||
"f:addressMap": "AddressMapSiemens",
|
"f:addressMap": "AddressMapSiemens",
|
||||||
"g:keepConnect": true,
|
"g:keepConnect": true,
|
||||||
@@ -61,6 +61,14 @@
|
|||||||
"i:masterAddress": 0,
|
"i:masterAddress": 0,
|
||||||
"j:src": 1,
|
"j:src": 1,
|
||||||
"k:dst": 0
|
"k:dst": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a:id": "OpcMachine1",
|
||||||
|
"b:protocol": "Opc",
|
||||||
|
"c:type": "Da",
|
||||||
|
"d:connectionString": "opcda://localhost/Matrikon.OPC.Simulation.1",
|
||||||
|
"e:addressMap": "AddressMapOpc",
|
||||||
|
"f:tagSpliter": "."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"addressMap": {
|
"addressMap": {
|
||||||
@@ -207,6 +215,25 @@
|
|||||||
"Id": "10",
|
"Id": "10",
|
||||||
"Name": "Test10"
|
"Name": "Test10"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"AddressMapOpc":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Area": "Random",
|
||||||
|
"Address": "Real4",
|
||||||
|
"DataType": "Single",
|
||||||
|
"Id": "1",
|
||||||
|
"Name": "Test1",
|
||||||
|
"DecimalPos": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Area": "Random",
|
||||||
|
"Address": "Real8",
|
||||||
|
"DataType": "Double",
|
||||||
|
"Id": "2",
|
||||||
|
"Name": "Test2",
|
||||||
|
"DecimalPos": 4
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Modbus.Net;
|
|||||||
using Modbus.Net.Modbus;
|
using Modbus.Net.Modbus;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using TripleAdd.Models;
|
using TripleAdd.Models;
|
||||||
|
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
|
||||||
|
|
||||||
namespace TripleAdd.Controllers
|
namespace TripleAdd.Controllers
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Modbus.Net.Modbus;
|
using Modbus.Net.Modbus;
|
||||||
using Modbus.Net.Siemens;
|
using Modbus.Net.Siemens;
|
||||||
|
using AddressUnit = Modbus.Net.AddressUnit<int, int, int>;
|
||||||
|
|
||||||
namespace Modbus.Net.Tests
|
namespace Modbus.Net.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class BaseTest
|
public class BaseTest
|
||||||
{
|
{
|
||||||
private List<AddressUnit<int>>? _addressUnits;
|
private List<AddressUnit>? _addressUnits;
|
||||||
|
|
||||||
private BaseMachine<int, int>? _baseMachine;
|
private BaseMachine<int, int>? _baseMachine;
|
||||||
|
|
||||||
@@ -16,9 +17,9 @@ namespace Modbus.Net.Tests
|
|||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
_addressUnits = new List<AddressUnit<int>>
|
_addressUnits = new List<AddressUnit>
|
||||||
{
|
{
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -26,7 +27,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(bool)
|
DataType = typeof(bool)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -34,7 +35,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 1,
|
SubAddress = 1,
|
||||||
DataType = typeof(bool)
|
DataType = typeof(bool)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 3,
|
Id = 3,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -42,7 +43,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 2,
|
SubAddress = 2,
|
||||||
DataType = typeof(bool)
|
DataType = typeof(bool)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 4,
|
Id = 4,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -50,7 +51,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(byte)
|
DataType = typeof(byte)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 5,
|
Id = 5,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -58,7 +59,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 8,
|
SubAddress = 8,
|
||||||
DataType = typeof(byte)
|
DataType = typeof(byte)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 6,
|
Id = 6,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -66,7 +67,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(ushort)
|
DataType = typeof(ushort)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 7,
|
Id = 7,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -74,7 +75,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(ushort)
|
DataType = typeof(ushort)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 8,
|
Id = 8,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -82,7 +83,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(ushort)
|
DataType = typeof(ushort)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 9,
|
Id = 9,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -90,7 +91,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(ushort)
|
DataType = typeof(ushort)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 10,
|
Id = 10,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -98,7 +99,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(ushort)
|
DataType = typeof(ushort)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 11,
|
Id = 11,
|
||||||
Area = "3X",
|
Area = "3X",
|
||||||
@@ -106,7 +107,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(ushort)
|
DataType = typeof(ushort)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 12,
|
Id = 12,
|
||||||
Area = "4X",
|
Area = "4X",
|
||||||
@@ -114,7 +115,7 @@ namespace Modbus.Net.Tests
|
|||||||
SubAddress = 0,
|
SubAddress = 0,
|
||||||
DataType = typeof(uint)
|
DataType = typeof(uint)
|
||||||
},
|
},
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 13,
|
Id = 13,
|
||||||
Area = "4X",
|
Area = "4X",
|
||||||
@@ -195,7 +196,7 @@ namespace Modbus.Net.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void AddressCombinerSingleTest()
|
public void AddressCombinerSingleTest()
|
||||||
{
|
{
|
||||||
var addressCombiner = new AddressCombinerSingle<int>();
|
var addressCombiner = new AddressCombinerSingle<int, int, int>();
|
||||||
var combinedAddresses = addressCombiner.Combine(_addressUnits!).ToArray();
|
var combinedAddresses = addressCombiner.Combine(_addressUnits!).ToArray();
|
||||||
Assert.AreEqual(combinedAddresses[0].Area, "3X");
|
Assert.AreEqual(combinedAddresses[0].Area, "3X");
|
||||||
Assert.AreEqual(combinedAddresses[0].Address, 1);
|
Assert.AreEqual(combinedAddresses[0].Address, 1);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Modbus.Net.Modbus;
|
using Modbus.Net.Modbus;
|
||||||
|
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
|
||||||
|
|
||||||
namespace Modbus.Net.Tests
|
namespace Modbus.Net.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Modbus.Net.Modbus;
|
using Modbus.Net.Modbus;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using AddressUnit = Modbus.Net.AddressUnit<int, int, int>;
|
||||||
|
|
||||||
namespace Modbus.Net.Tests
|
namespace Modbus.Net.Tests
|
||||||
{
|
{
|
||||||
@@ -35,9 +36,9 @@ namespace Modbus.Net.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task InvokeMachine()
|
public async Task InvokeMachine()
|
||||||
{
|
{
|
||||||
BaseMachine<int, int> baseMachine = new ModbusMachine<int, int>(1, ModbusType.Tcp, _machineIp, new List<AddressUnit<int>>
|
BaseMachine<int, int> baseMachine = new ModbusMachine<int, int>(1, ModbusType.Tcp, _machineIp, new List<AddressUnit>
|
||||||
{
|
{
|
||||||
new AddressUnit<int>
|
new AddressUnit
|
||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Area = "0X",
|
Area = "0X",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.Modbus\Modbus.Net.Modbus.csproj" />
|
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.Modbus\Modbus.Net.Modbus.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.OPC\Modbus.Net.Opc.csproj" />
|
||||||
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.Siemens\Modbus.Net.Siemens.csproj" />
|
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net.Siemens\Modbus.Net.Siemens.csproj" />
|
||||||
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net\Modbus.Net.csproj" />
|
<ProjectReference Include="..\..\Modbus.Net\Modbus.Net\Modbus.Net.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Modbus.Net.Modbus;
|
using Modbus.Net.Modbus;
|
||||||
|
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
|
||||||
|
|
||||||
namespace Modbus.Net.Tests
|
namespace Modbus.Net.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Modbus.Net.Modbus;
|
using Modbus.Net.Modbus;
|
||||||
|
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
|
||||||
|
|
||||||
namespace Modbus.Net.Tests
|
namespace Modbus.Net.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Modbus.Net.Siemens;
|
using Modbus.Net.Siemens;
|
||||||
|
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
|
||||||
|
|
||||||
namespace Modbus.Net.Tests
|
namespace Modbus.Net.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
32
h-opc/h-opc/Common/ClientExtensions.cs
Normal file
32
h-opc/h-opc/Common/ClientExtensions.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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 ReadEvent<T> ReadOrdefault<T>(this IClient<Node> client, string tag, T defaultValue = default(T))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return client.Read<T>(tag);
|
||||||
|
}
|
||||||
|
catch (OpcException)
|
||||||
|
{
|
||||||
|
var readEvent = new ReadEvent<T>();
|
||||||
|
readEvent.Quality = Quality.Good;
|
||||||
|
readEvent.Value = defaultValue;
|
||||||
|
readEvent.SourceTimestamp = DateTime.Now;
|
||||||
|
readEvent.ServerTimestamp = DateTime.Now;
|
||||||
|
return readEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
h-opc/h-opc/Common/IClient.cs
Normal file
99
h-opc/h-opc/Common/IClient.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
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>
|
||||||
|
/// Gets the datatype of an OPC tag
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">Tag to get datatype of</param>
|
||||||
|
/// <returns>System Type</returns>
|
||||||
|
System.Type GetDataType(string tag);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
ReadEvent<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>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an async method.")]
|
||||||
|
void Monitor<T>(string tag, Action<ReadEvent<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>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an async method.")]
|
||||||
|
Task<ReadEvent<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
h-opc/h-opc/Common/Node.cs
Normal file
46
h-opc/h-opc/Common/Node.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
h-opc/h-opc/Common/OpcException.cs
Normal file
69
h-opc/h-opc/Common/OpcException.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using Opc.Ua;
|
||||||
|
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
h-opc/h-opc/Common/OpcStatus.cs
Normal file
18
h-opc/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
|
||||||
|
}
|
||||||
|
}
|
||||||
28
h-opc/h-opc/Common/Quality.cs
Normal file
28
h-opc/h-opc/Common/Quality.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Hylasoft.Opc.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the quality of the value captured
|
||||||
|
/// </summary>
|
||||||
|
public enum Quality
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Quality: Unknown, the value of the quality could not be inferred by the library
|
||||||
|
/// </summary>
|
||||||
|
[Description("Unknown")]
|
||||||
|
Unknown,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quality: Good
|
||||||
|
/// </summary>
|
||||||
|
[Description("Good")]
|
||||||
|
Good,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quality: Bad
|
||||||
|
/// </summary>
|
||||||
|
[Description("Bad")]
|
||||||
|
Bad
|
||||||
|
}
|
||||||
|
}
|
||||||
32
h-opc/h-opc/Common/ReadEvent.cs
Normal file
32
h-opc/h-opc/Common/ReadEvent.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Hylasoft.Opc.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class representing a monitor event on the OPC server
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class ReadEvent<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value that was read from the server
|
||||||
|
/// </summary>
|
||||||
|
public T Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the quality of the signal from the server
|
||||||
|
/// </summary>
|
||||||
|
[DefaultValue(Common.Quality.Unknown)]
|
||||||
|
public Quality Quality { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the source timestamp on when the event ocurred
|
||||||
|
/// </summary>
|
||||||
|
public DateTime SourceTimestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the server timestamp on when the event ocurred
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ServerTimestamp { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
298
h-opc/h-opc/Da/DaClient.cs
Normal file
298
h-opc/h-opc/Da/DaClient.cs
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
using Hylasoft.Opc.Common;
|
||||||
|
using System.Globalization;
|
||||||
|
using Technosoftware.DaAeHdaClient;
|
||||||
|
using Factory = Technosoftware.DaAeHdaClient.Com.Factory;
|
||||||
|
using OpcDa = Technosoftware.DaAeHdaClient.Da;
|
||||||
|
|
||||||
|
namespace Hylasoft.Opc.Da
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client Implementation for DA
|
||||||
|
/// </summary>
|
||||||
|
public partial class DaClient : IClient<DaNode>
|
||||||
|
{
|
||||||
|
private readonly OpcUrl _url;
|
||||||
|
private OpcDa.TsCDaServer _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. WARNING: If server URL includes
|
||||||
|
/// spaces (ex. "RSLinx OPC Server") then pass the server URL in to the constructor as an Opc.URL object
|
||||||
|
/// directly instead.</param>
|
||||||
|
public DaClient(Uri serverUrl)
|
||||||
|
{
|
||||||
|
_url = new OpcUrl(serverUrl.OriginalString)
|
||||||
|
{
|
||||||
|
Scheme = serverUrl.Scheme,
|
||||||
|
HostName = serverUrl.Host
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize a new Data Access Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverUrl">The url of the server to connect to</param>
|
||||||
|
public DaClient(OpcUrl serverUrl)
|
||||||
|
{
|
||||||
|
_url = serverUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the datatype of an OPC tag
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">Tag to get datatype of</param>
|
||||||
|
/// <returns>System Type</returns>
|
||||||
|
public System.Type GetDataType(string tag)
|
||||||
|
{
|
||||||
|
var item = new OpcDa.TsCDaItem { ItemName = tag };
|
||||||
|
OpcDa.TsCDaItemProperty result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var propertyCollection = _server.GetProperties(new[] { item }, new[] { new OpcDa.TsDaPropertyID(1) }, false)[0];
|
||||||
|
result = propertyCollection[0];
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
throw new OpcException("Could not find node because server not connected.");
|
||||||
|
}
|
||||||
|
return result.DataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OpcDa underlying server object.
|
||||||
|
/// </summary>
|
||||||
|
protected OpcDa.TsCDaServer Server
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region interface methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connect the client to the OPC Server
|
||||||
|
/// </summary>
|
||||||
|
public void Connect()
|
||||||
|
{
|
||||||
|
if (Status == OpcStatus.Connected)
|
||||||
|
return;
|
||||||
|
_server = new OpcDa.TsCDaServer(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.GetServerStatus().ServerState != OpcServerState.Operational)
|
||||||
|
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 ReadEvent<T> Read<T>(string tag)
|
||||||
|
{
|
||||||
|
var item = new OpcDa.TsCDaItem { ItemName = tag };
|
||||||
|
if (Status == OpcStatus.NotConnected)
|
||||||
|
{
|
||||||
|
throw new OpcException("Server not connected. Cannot read tag.");
|
||||||
|
}
|
||||||
|
var result = _server.Read(new[] { item })[0];
|
||||||
|
T casted;
|
||||||
|
TryCastResult(result.Value, out casted);
|
||||||
|
|
||||||
|
var readEvent = new ReadEvent<T>();
|
||||||
|
readEvent.Value = casted;
|
||||||
|
readEvent.SourceTimestamp = result.Timestamp;
|
||||||
|
readEvent.ServerTimestamp = result.Timestamp;
|
||||||
|
if (result.Quality == OpcDa.TsCDaQuality.Good) readEvent.Quality = Quality.Good;
|
||||||
|
if (result.Quality == OpcDa.TsCDaQuality.Bad) readEvent.Quality = Quality.Bad;
|
||||||
|
|
||||||
|
return readEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.TsCDaItemValue
|
||||||
|
{
|
||||||
|
ItemName = tag,
|
||||||
|
Value = item
|
||||||
|
};
|
||||||
|
var result = _server.Write(new[] { itmVal })[0];
|
||||||
|
CheckResult(result, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Casts result of monitoring and reading values
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to convert</param>
|
||||||
|
/// <param name="casted">The casted result</param>
|
||||||
|
/// <typeparam name="T">Type of object to try to cast</typeparam>
|
||||||
|
public void TryCastResult<T>(object value, out T casted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
casted = (T)value;
|
||||||
|
}
|
||||||
|
catch (InvalidCastException)
|
||||||
|
{
|
||||||
|
throw new InvalidCastException(
|
||||||
|
string.Format(
|
||||||
|
"Could not monitor tag. Cast failed for type \"{0}\" on the new value \"{1}\" with type \"{2}\". Make sure tag data type matches.",
|
||||||
|
typeof(T), value, value.GetType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 a MonitorEvent object which represents the data point, the second is an `unsubscribe` function to unsubscribe the callback</param>
|
||||||
|
public void Monitor<T>(string tag, Action<ReadEvent<T>, Action> callback)
|
||||||
|
{
|
||||||
|
var subItem = new OpcDa.TsCDaSubscriptionState
|
||||||
|
{
|
||||||
|
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.DataChangedEvent += (handle, requestHandle, values) =>
|
||||||
|
{
|
||||||
|
T casted;
|
||||||
|
TryCastResult(values[0].Value, out casted);
|
||||||
|
var monitorEvent = new ReadEvent<T>();
|
||||||
|
monitorEvent.Value = casted;
|
||||||
|
monitorEvent.SourceTimestamp = values[0].Timestamp;
|
||||||
|
monitorEvent.ServerTimestamp = values[0].Timestamp;
|
||||||
|
if (values[0].Quality == OpcDa.TsCDaQuality.Good) monitorEvent.Quality = Quality.Good;
|
||||||
|
if (values[0].Quality == OpcDa.TsCDaQuality.Bad) monitorEvent.Quality = Quality.Bad;
|
||||||
|
callback(monitorEvent, unsubscribe);
|
||||||
|
};
|
||||||
|
sub.AddItems(new[] { new OpcDa.TsCDaItem { 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.TsCDaItem { ItemName = tag };
|
||||||
|
OpcDa.TsCDaItemValueResult result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = _server.Read(new[] { item })[0];
|
||||||
|
}
|
||||||
|
catch (NullReferenceException)
|
||||||
|
{
|
||||||
|
throw new OpcException("Could not find node because server not connected.");
|
||||||
|
}
|
||||||
|
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.TsCDaBrowsePosition p;
|
||||||
|
var nodes = _server.Browse(new OpcItem(parent.Tag), new OpcDa.TsCDaBrowseFilters(), 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>
|
||||||
|
private void AddNodeToCache(DaNode node)
|
||||||
|
{
|
||||||
|
if (!_nodesCache.ContainsKey(node.Tag))
|
||||||
|
_nodesCache.Add(node.Tag, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CheckResult(IOpcResult result, string tag)
|
||||||
|
{
|
||||||
|
if (result == null)
|
||||||
|
throw new OpcException("The server replied with an empty response");
|
||||||
|
if (result.Result.ToString() != "S_OK" && result.Result.ToString() != "E_READONLY")
|
||||||
|
throw new OpcException(string.Format("Invalid response from the server. (Response Status: {0}, Opc Tag: {1})", result.Result, tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
42
h-opc/h-opc/Da/DaClient_async.cs
Normal file
42
h-opc/h-opc/Da/DaClient_async.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Hylasoft.Opc.Common;
|
||||||
|
|
||||||
|
namespace Hylasoft.Opc.Da
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client Implementation for DA
|
||||||
|
/// </summary>
|
||||||
|
public partial class DaClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Read a tag asynchronusly
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ReadEvent<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
h-opc/h-opc/Da/DaNode.cs
Normal file
22
h-opc/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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
h-opc/h-opc/Ua/ClientUtils.cs
Normal file
98
h-opc/h-opc/Ua/ClientUtils.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using Opc.Ua;
|
||||||
|
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
h-opc/h-opc/Ua/NodeExtensions.cs
Normal file
24
h-opc/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
642
h-opc/h-opc/Ua/UaClient.cs
Normal file
642
h-opc/h-opc/Ua/UaClient.cs
Normal file
@@ -0,0 +1,642 @@
|
|||||||
|
using Hylasoft.Opc.Common;
|
||||||
|
using Opc.Ua;
|
||||||
|
using Opc.Ua.Client;
|
||||||
|
using Opc.Ua.Configuration;
|
||||||
|
|
||||||
|
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;
|
||||||
|
private 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>
|
||||||
|
/// Creates a server object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverUrl">the url of the server to connect to</param>
|
||||||
|
/// <param name="options">custom options to use with ua client</param>
|
||||||
|
public UaClient(Uri serverUrl, UaClientOptions options)
|
||||||
|
{
|
||||||
|
_serverUrl = serverUrl;
|
||||||
|
_options = options;
|
||||||
|
Status = OpcStatus.NotConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options to configure the UA client session
|
||||||
|
/// </summary>
|
||||||
|
public UaClientOptions Options
|
||||||
|
{
|
||||||
|
get { return _options; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OPC Foundation underlying session object
|
||||||
|
/// </summary>
|
||||||
|
protected Session Session
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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).Result;
|
||||||
|
_session.KeepAlive += SessionKeepAlive;
|
||||||
|
_session.SessionClosing += SessionClosing;
|
||||||
|
PostInitializeSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the datatype of an OPC tag
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag">Tag to get datatype of</param>
|
||||||
|
/// <returns>System Type</returns>
|
||||||
|
public System.Type GetDataType(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 type = results[0].WrappedValue.TypeInfo.BuiltInType;
|
||||||
|
return System.Type.GetType("System." + type.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SessionKeepAlive(ISession 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()
|
||||||
|
{
|
||||||
|
Status = OpcStatus.NotConnected;
|
||||||
|
_session.Reconnect();
|
||||||
|
Status = OpcStatus.Connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new OPC session, based on the current session parameters.
|
||||||
|
/// </summary>
|
||||||
|
public void RecreateSession()
|
||||||
|
{
|
||||||
|
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 ReadEvent<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];
|
||||||
|
|
||||||
|
var readEvent = new ReadEvent<T>();
|
||||||
|
readEvent.Value = (T)val.Value;
|
||||||
|
readEvent.SourceTimestamp = val.SourceTimestamp;
|
||||||
|
readEvent.ServerTimestamp = val.ServerTimestamp;
|
||||||
|
if (StatusCode.IsGood(val.StatusCode)) readEvent.Quality = Quality.Good;
|
||||||
|
if (StatusCode.IsBad(val.StatusCode)) readEvent.Quality = Quality.Bad;
|
||||||
|
return readEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <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<ReadEvent<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<ReadEvent<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);
|
||||||
|
var val = results[0];
|
||||||
|
var readEvent = new ReadEvent<T>();
|
||||||
|
readEvent.Value = (T)val.Value;
|
||||||
|
readEvent.SourceTimestamp = val.SourceTimestamp;
|
||||||
|
readEvent.ServerTimestamp = val.ServerTimestamp;
|
||||||
|
if (StatusCode.IsGood(val.StatusCode)) readEvent.Quality = Quality.Good;
|
||||||
|
if (StatusCode.IsBad(val.StatusCode)) readEvent.Quality = Quality.Bad;
|
||||||
|
taskCompletionSource.TrySetResult(readEvent);
|
||||||
|
}
|
||||||
|
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<StatusCode>();
|
||||||
|
_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]);
|
||||||
|
taskCompletionSource.SetResult(response.ServiceResult);
|
||||||
|
}
|
||||||
|
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 a MonitorEvent object which represents the data point, the second is an `unsubscribe` function to unsubscribe the callback</param>
|
||||||
|
public void Monitor<T>(string tag, Action<ReadEvent<T>, Action> callback)
|
||||||
|
{
|
||||||
|
var node = FindNode(tag);
|
||||||
|
|
||||||
|
var sub = new Subscription
|
||||||
|
{
|
||||||
|
PublishingInterval = _options.DefaultMonitorInterval,
|
||||||
|
PublishingEnabled = true,
|
||||||
|
LifetimeCount = _options.SubscriptionLifetimeCount,
|
||||||
|
KeepAliveCount = _options.SubscriptionKeepAliveCount,
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
var monitorEvent = new ReadEvent<T>();
|
||||||
|
monitorEvent.Value = (T)t;
|
||||||
|
monitorEvent.SourceTimestamp = p.Value.SourceTimestamp;
|
||||||
|
monitorEvent.ServerTimestamp = p.Value.ServerTimestamp;
|
||||||
|
if (StatusCode.IsGood(p.Value.StatusCode)) monitorEvent.Quality = Quality.Good;
|
||||||
|
if (StatusCode.IsBad(p.Value.StatusCode)) monitorEvent.Quality = Quality.Bad;
|
||||||
|
callback(monitorEvent, 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
|
||||||
|
if (!_folderCache.ContainsKey(tag))
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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>
|
||||||
|
private void AddNodeToCache(UaNode node)
|
||||||
|
{
|
||||||
|
if (!_nodesCache.ContainsKey(node.Tag))
|
||||||
|
_nodesCache.Add(node.Tag, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return identity login object for a given URI.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">Login URI</param>
|
||||||
|
/// <returns>AnonUser or User with name and password</returns>
|
||||||
|
private UserIdentity GetIdentity(Uri url)
|
||||||
|
{
|
||||||
|
if (_options.UserIdentity != null)
|
||||||
|
{
|
||||||
|
return _options.UserIdentity;
|
||||||
|
}
|
||||||
|
var uriLogin = new UserIdentity();
|
||||||
|
if (!string.IsNullOrEmpty(url.UserInfo))
|
||||||
|
{
|
||||||
|
var uis = url.UserInfo.Split(':');
|
||||||
|
uriLogin = new UserIdentity(uis[0], uis[1]);
|
||||||
|
}
|
||||||
|
return uriLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crappy method to initialize the session. I don't know what many of these things do, sincerely.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<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,
|
||||||
|
ServerConfiguration = new ServerConfiguration
|
||||||
|
{
|
||||||
|
MaxSubscriptionCount = _options.MaxSubscriptionCount,
|
||||||
|
MaxMessageQueueSize = _options.MaxMessageQueueSize,
|
||||||
|
MaxNotificationQueueSize = _options.MaxNotificationQueueSize,
|
||||||
|
MaxPublishRequestCount = _options.MaxPublishRequestCount
|
||||||
|
},
|
||||||
|
SecurityConfiguration = new SecurityConfiguration
|
||||||
|
{
|
||||||
|
AutoAcceptUntrustedCertificates = _options.AutoAcceptUntrustedCertificates
|
||||||
|
},
|
||||||
|
TransportQuotas = new TransportQuotas
|
||||||
|
{
|
||||||
|
OperationTimeout = 600000,
|
||||||
|
MaxStringLength = 1048576,
|
||||||
|
MaxByteStringLength = 1048576,
|
||||||
|
MaxArrayLength = 65535,
|
||||||
|
MaxMessageSize = 4194304,
|
||||||
|
MaxBufferSize = 65535,
|
||||||
|
ChannelLifetime = 600000,
|
||||||
|
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 = await 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: GetIdentity(url),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove an array element by converting it to a list
|
||||||
|
var folderList = folders.ToList();
|
||||||
|
folderList.RemoveAt(0); // remove the first node
|
||||||
|
folders = folderList.ToArray();
|
||||||
|
return folders.Length == 0
|
||||||
|
? found // last node, return it
|
||||||
|
: FindNode(string.Join(".", folders), 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
120
h-opc/h-opc/Ua/UaClientOptions.cs
Normal file
120
h-opc/h-opc/Ua/UaClientOptions.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using OpcUa = Opc.Ua;
|
||||||
|
|
||||||
|
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 application 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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of notifications per publish request.
|
||||||
|
/// The client’s responsibility is to send PublishRequests to the server,
|
||||||
|
/// in order to enable the server to send PublishResponses back.
|
||||||
|
/// The PublishResponses are used to deliver the notifications: but if there
|
||||||
|
/// are no PublishRequests, the server cannot send a notification to the client.
|
||||||
|
/// The server will also verify that the client is alive by checking that
|
||||||
|
/// new PublishRequests are received – LifeTimeCount defines the number of
|
||||||
|
/// PublishingIntervals to wait for a new PublishRequest, before realizing
|
||||||
|
/// that the client is no longer active.The Subscription is then removed from
|
||||||
|
/// the server.
|
||||||
|
/// </summary>
|
||||||
|
public uint SubscriptionLifetimeCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If there is no data to send after the next PublishingInterval,
|
||||||
|
/// the server will skip it. But KeepAlive defines how many intervals may be skipped,
|
||||||
|
/// before an empty notification is sent anyway: to give the client a hint that
|
||||||
|
/// the subscription is still alive in the server and that there just has not been
|
||||||
|
/// any data arriving to the client.
|
||||||
|
/// </summary>
|
||||||
|
public uint SubscriptionKeepAliveCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the max subscription count.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxSubscriptionCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of messages saved in the queue for each subscription.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxMessageQueueSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of notificates saved in the queue for each monitored item.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxNotificationQueueSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the max publish request count.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxPublishRequestCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The identity to connect to the OPC server as
|
||||||
|
/// </summary>
|
||||||
|
public OpcUa.UserIdentity UserIdentity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a client options object
|
||||||
|
/// </summary>
|
||||||
|
public UaClientOptions()
|
||||||
|
{
|
||||||
|
// Initialize default values:
|
||||||
|
ApplicationName = "h-opc-client";
|
||||||
|
AutoAcceptUntrustedCertificates = true;
|
||||||
|
ConfigSectionName = "h-opc-client";
|
||||||
|
DefaultMonitorInterval = 100;
|
||||||
|
SessionName = "h-opc-client";
|
||||||
|
SessionTimeout = 60000U;
|
||||||
|
UseMessageSecurity = false;
|
||||||
|
SubscriptionLifetimeCount = 0;
|
||||||
|
SubscriptionKeepAliveCount = 0;
|
||||||
|
MaxSubscriptionCount = 100;
|
||||||
|
MaxMessageQueueSize = 10;
|
||||||
|
MaxNotificationQueueSize = 100;
|
||||||
|
MaxPublishRequestCount = 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
h-opc/h-opc/Ua/UaNode.cs
Normal file
29
h-opc/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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
h-opc/h-opc/h-opc.csproj
Normal file
15
h-opc/h-opc/h-opc.csproj
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<RootNamespace>h_opc</RootNamespace>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.4.371.96" />
|
||||||
|
<PackageReference Include="Technosoftware.DaAeHdaSolution.DaAeHdaClient" Version="2.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user