Merge branch 'dev'

This commit is contained in:
luosheng
2023-07-16 09:18:00 +08:00
289 changed files with 59339 additions and 1005 deletions

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<AssemblyName>Modbus.Net.BigEndian3412</AssemblyName>
<RootNamespace>Modbus.Net.BigEndian3412</RootNamespace>
<PackageId>Modbus.Net.BigEndian3412</PackageId>
<Version>1.4.2</Version>
<Authors>Chris L.(Luo Sheng)</Authors>
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
<Product>Modbus.Net.Modbus</Product>
<Description>Modbus.Net Modbus Implementation</Description>
<Copyright>Copyright 2023 Hangzhou Delian Science Technology Co.,Ltd.</Copyright>
<PackageProjectUrl>https://github.com/parallelbgls/Modbus.Net/tree/master/Modbus.Net/Modbus.Net.BigEndian3412</PackageProjectUrl>
<RepositoryUrl>https://github.com/parallelbgls/Modbus.Net</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>hardware communicate protocol modbus Delian</PackageTags>
<PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<IncludeSymbols>True</IncludeSymbols>
<IncludeSource>True</IncludeSource>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Modbus.Net\Modbus.Net.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath=""/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,53 @@
using System;
namespace Modbus.Net
{
public partial class Endian
{
public const int BigEndian3412 = 10;
}
public class BigEndian3412ValueHelper : BigEndianLsbValueHelper
{
private static BigEndian3412ValueHelper _bigEndian3412Instance;
/// <summary>
/// 构造器
/// </summary>
protected BigEndian3412ValueHelper()
{
}
/// <summary>
/// 覆写的实例获取
/// </summary>
protected override ValueHelper _Instance => _bigEndian3412Instance;
/// <summary>
/// 是否为大端
/// </summary>
protected new bool LittleEndian => false;
protected new bool LittleEndianBit => false;
/// <summary>
/// 覆盖的获取实例的方法
/// </summary>
public new static BigEndian3412ValueHelper Instance
=> _bigEndian3412Instance ?? (_bigEndian3412Instance = new BigEndian3412ValueHelper());
public override float GetFloat(byte[] data, ref int pos)
{
Array.Reverse(data, pos, 4);
byte temp;
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
var t = BitConverter.ToSingle(data, pos);
temp = data[pos]; data[pos] = data[pos + 2]; data[pos + 2] = temp;
temp = data[pos + 1]; data[pos + 1] = data[pos + 3]; data[pos + 3] = temp;
Array.Reverse(data, pos, 4);
pos += 4;
return t;
}
}
}

View File

@@ -0,0 +1,5 @@
Modbus.Net.BigEndian3412
===================
Self defined EndianHelper implementation of Modbus.Net
Doc has been moved to wiki.

View File

@@ -3,7 +3,7 @@
/// <summary>
/// 南大奥拓NA200H专用AddressFormater
/// </summary>
public class AddressFormaterNA200H : AddressFormater
public class AddressFormaterNA200H : AddressFormater<int, int>
{
/// <summary>
/// 格式化地址

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<AssemblyName>Modbus.Net.Modbus.NA200H</AssemblyName>
<RootNamespace>Modbus.Net.Modbus.NA200H</RootNamespace>
<PackageId>Modbus.Net.Modbus.NA200H</PackageId>
<Version>1.4.1</Version>
<Version>1.4.2</Version>
<Authors>Chris L.(Luo Sheng)</Authors>
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
<Product>Modbus.Net.Modbus</Product>

View File

@@ -1,7 +1,5 @@
Modbus.Net.Modbus
===================
[![NuGet](https://img.shields.io/nuget/v/Modbus.Net.Modbus.NA200H.svg)](https://www.nuget.org/packages/Modbus.Net.Modbus.NA200H/)
NA200H Implementation of Modbus.Net
Doc has been moved to wiki.

View File

@@ -1,18 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<AssemblyName>Modbus.Net.Modbus.SelfDefinedSample</AssemblyName>
<RootNamespace>Modbus.Net.Modbus.SelfDefinedSample</RootNamespace>
<PackageId>Modbus.Net.Modbus.SelfDefinedSample</PackageId>
<Version>1.4.1</Version>
<Version>1.4.2</Version>
<Authors>Chris L.(Luo Sheng)</Authors>
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
<Product>Modbus.Net.Modbus</Product>
<Description>Modbus.Net Modbus Implementation</Description>
<Copyright>Copyright 2023 Hangzhou Delian Science Technology Co.,Ltd.</Copyright>
<PackageProjectUrl>https://github.com/parallelbgls/Modbus.Net/tree/master/Modbus.Net/Modbus.Net.Modbus.NA200H</PackageProjectUrl>
<PackageProjectUrl>https://github.com/parallelbgls/Modbus.Net/tree/master/Modbus.Net/Modbus.Net.Modbus.SelfDefinedSample</PackageProjectUrl>
<RepositoryUrl>https://github.com/parallelbgls/Modbus.Net</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>hardware communicate protocol modbus Delian</PackageTags>

View File

@@ -16,7 +16,7 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
/// <param name="masterAddress">主站号</param>
/// <param name="endian">端格式</param>
public ModbusUtilityTime(int connectionType, byte slaveAddress, byte masterAddress,
Endian endian = Endian.BigEndianLsb)
Endian endian)
: base(connectionType, slaveAddress, masterAddress, endian)
{
}
@@ -30,7 +30,7 @@ namespace Modbus.Net.Modbus.SelfDefinedSample
/// <param name="masterAddress">主站号</param>
/// <param name="endian">端格式</param>
public ModbusUtilityTime(ModbusType connectionType, string connectionString, byte slaveAddress, byte masterAddress,
Endian endian = Endian.BigEndianLsb)
Endian endian)
: base(connectionType, connectionString, slaveAddress, masterAddress, endian)
{
}

View File

@@ -1,7 +1,5 @@
Modbus.Net.Modbus
===================
[![NuGet](https://img.shields.io/nuget/v/Modbus.Net.Modbus.SelfDefinedSample.svg)](https://www.nuget.org/packages/Modbus.Net.Modbus.SelfDefinedSample/)
Self defined function get time and set time implementation of Modbus.Net
Doc has been moved to wiki.

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Modbus标准AddressFormater
/// </summary>
public class AddressFormaterModbus : AddressFormater
public class AddressFormaterModbus : AddressFormater<int, int>
{
/// <summary>
/// 格式化地址

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<AssemblyName>Modbus.Net.Modbus</AssemblyName>
<RootNamespace>Modbus.Net.Modbus</RootNamespace>
<PackageId>Modbus.Net.Modbus</PackageId>
<Version>1.4.1</Version>
<Version>1.4.2</Version>
<Authors>Chris L.(Luo Sheng)</Authors>
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
<Product>Modbus.Net.Modbus</Product>

View File

@@ -21,8 +21,8 @@ namespace Modbus.Net.Modbus
/// <param name="masterAddress">主站号</param>
/// <param name="endian">端格式</param>
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString,
IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress,
Endian endian = Endian.BigEndianLsb)
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, bool keepConnect, byte slaveAddress, byte masterAddress,
Endian endian)
: base(id, getAddresses, keepConnect, slaveAddress, masterAddress)
{
BaseUtility = new ModbusUtility(connectionType, connectionString, slaveAddress, masterAddress, endian);
@@ -42,8 +42,8 @@ namespace Modbus.Net.Modbus
/// <param name="masterAddress">主站号</param>
/// <param name="endian">端格式</param>
public ModbusMachine(TKey id, ModbusType connectionType, string connectionString,
IEnumerable<AddressUnit<TUnitKey>> getAddresses, byte slaveAddress, byte masterAddress,
Endian endian = Endian.BigEndianLsb)
IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, byte slaveAddress, byte masterAddress,
Endian endian)
: this(id, connectionType, connectionString, getAddresses, true, slaveAddress, masterAddress, endian)
{
}

View File

@@ -67,9 +67,9 @@ namespace Modbus.Net.Modbus
var transaction = (ushort)(_sendCount % 65536 + 1);
var tag = (ushort)0;
var leng = (ushort)content.Length;
Array.Copy(BigEndianValueHelper.Instance.GetBytes(transaction), 0, newFormat, 0, 2);
Array.Copy(BigEndianValueHelper.Instance.GetBytes(tag), 0, newFormat, 2, 2);
Array.Copy(BigEndianValueHelper.Instance.GetBytes(leng), 0, newFormat, 4, 2);
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(transaction), 0, newFormat, 0, 2);
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(tag), 0, newFormat, 2, 2);
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(leng), 0, newFormat, 4, 2);
Array.Copy(content, 0, newFormat, 6, content.Length);
_sendCount++;
}

View File

@@ -79,7 +79,7 @@ namespace Modbus.Net.Modbus
/// <param name="masterAddress">主站号</param>
/// <param name="endian">端格式</param>
public ModbusUtility(int connectionType, byte slaveAddress, byte masterAddress,
Endian endian = Endian.BigEndianLsb)
Endian endian)
: base(slaveAddress, masterAddress)
{
Endian = endian;
@@ -97,7 +97,7 @@ namespace Modbus.Net.Modbus
/// <param name="masterAddress">主站号</param>
/// <param name="endian">端格式</param>
public ModbusUtility(ModbusType connectionType, string connectionString, byte slaveAddress, byte masterAddress,
Endian endian = Endian.BigEndianLsb)
Endian endian)
: base(slaveAddress, masterAddress)
{
Endian = endian;

View File

@@ -6,16 +6,16 @@ namespace Modbus.Net.Opc
/// <summary>
/// Opc地址编码器
/// </summary>
public class AddressFormaterOpc<TMachineKey, TUnitKey> : AddressFormater where TMachineKey : IEquatable<TMachineKey>
where TUnitKey : IEquatable<TUnitKey>
public class AddressFormaterOpc<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> : AddressFormater<TAddressKey, TSubAddressKey> where TMachineKey : IEquatable<TMachineKey>
where TUnitKey : IEquatable<TUnitKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
{
/// <summary>
/// 协议构造器
/// </summary>
/// <param name="tagGeter">如何通过BaseMachine和AddressUnit构造Opc的标签</param>
/// <param name="machine">调用这个编码器的设备</param>
public AddressFormaterOpc(Func<BaseMachine<TMachineKey, TUnitKey>, AddressUnit<TUnitKey>, string[]> tagGeter,
BaseMachine<TMachineKey, TUnitKey> machine)
public AddressFormaterOpc(Func<BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey>, AddressUnit<TUnitKey, TAddressKey, TSubAddressKey>, string> tagGeter,
BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> machine)
{
Machine = machine;
TagGeter = tagGeter;
@@ -24,13 +24,13 @@ namespace Modbus.Net.Opc
/// <summary>
/// 设备
/// </summary>
public BaseMachine<TMachineKey, TUnitKey> Machine { get; set; }
public BaseMachine<TMachineKey, TUnitKey, TAddressKey, TSubAddressKey> Machine { get; set; }
/// <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>
/// 编码地址
@@ -38,15 +38,12 @@ namespace Modbus.Net.Opc
/// <param name="area">地址所在的数据区域</param>
/// <param name="address">地址</param>
/// <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;
var strings = TagGeter(Machine, findAddress);
var ans = "";
for (var i = 0; i < strings.Length; i++)
ans += strings[i].Trim().Replace(" ", "") + '\r';
ans = ans.Substring(0, ans.Length - 1);
var ans = TagGeter(Machine, findAddress);
ans = ans.Trim().Replace(" ", "");
return ans;
}
@@ -57,7 +54,7 @@ namespace Modbus.Net.Opc
/// <param name="address">地址</param>
/// <param name="subAddress">子地址(忽略)</param>
/// <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);
}

View File

@@ -3,9 +3,7 @@ using Hylasoft.Opc.Da;
using Hylasoft.Opc.Ua;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using URL = Opc.URL;
namespace Modbus.Net.Opc
{
@@ -73,18 +71,11 @@ namespace Modbus.Net.Opc
public class MyDaClient : DaClient, IClientExtend
{
/// <summary>
/// UaClient Extend
/// 构造函数
/// </summary>
/// <param name="serverUrl">Url address of Opc UA server</param>
/// <param name="serverUrl">OpcDa服务端Url</param>
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>

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<AssemblyName>Modbus.Net.Opc</AssemblyName>
<RootNamespace>Modbus.Net.Opc</RootNamespace>
<PackageId>Modbus.Net.Opc</PackageId>
<Version>1.4.1</Version>
<Version>1.4.2</Version>
<Authors>Chris L.(Luo Sheng)</Authors>
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
<Product>Modbus.Net.Opc</Product>
@@ -20,25 +20,18 @@
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<IncludeSymbols>True</IncludeSymbols>
<IncludeSource>True</IncludeSource>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Platforms>AnyCPU;x86</Platforms>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\Modbus.Net.Opc.xml</DocumentationFile>
</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>
<ProjectReference Include="..\..\h-opc\h-opc\h-opc.csproj" />
<ProjectReference Include="..\Modbus.Net\Modbus.Net.csproj" />
</ItemGroup>

View File

@@ -96,13 +96,36 @@ namespace Modbus.Net.Opc
if (tag != null)
{
var result = await Client.ReadAsync<object>(tag);
logger.LogInformation($"Opc Machine {ConnectionToken} Read Opc tag {tag} for value {result.Value}");
object resultTrans;
if (result.Value?.ToString() == "False")
{
resultTrans = (byte)0;
}
else if (result.Value?.ToString() == "True")
{
resultTrans = (byte)1;
}
else if (result.Value != null)
{
resultTrans = result.Value;
}
else
{
logger.LogError($"Opc Machine {ConnectionToken} Read Opc tag {tag} for value null");
return new OpcParamOut
{
Success = false,
Value = Encoding.ASCII.GetBytes("NoData")
};
}
logger.LogInformation($"Opc Machine {ConnectionToken} Read Opc tag {tag} for value {result.Value} {result.Value.GetType().FullName}");
return new OpcParamOut
{
Success = true,
Value = BigEndianValueHelper.Instance.GetBytes(result.Value, result.Value.GetType())
Value = BigEndianLsbValueHelper.Instance.GetBytes(resultTrans, resultTrans.GetType())
};
}
logger.LogError($"Opc Machine {ConnectionToken} Read Opc tag null");
return new OpcParamOut
{
Success = false,
@@ -143,6 +166,7 @@ namespace Modbus.Net.Opc
catch (Exception e)
{
logger.LogError(e, "Opc client {ConnectionToken} read exception", ConnectionToken);
Disconnect();
return new OpcParamOut
{
Success = false,

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
@@ -19,7 +20,6 @@ namespace Modbus.Net.Opc
/// <param name="host">Opc DA 服务地址</param>
protected OpcDaConnector(string host) : base(host)
{
Client = new MyDaClient(new Uri(ConnectionToken));
}
/// <summary>
@@ -36,5 +36,12 @@ namespace Modbus.Net.Opc
}
return _instances[host];
}
/// <inheritdoc />
public override Task<bool> ConnectAsync()
{
if (Client == null) Client = new MyDaClient(new Uri(ConnectionToken));
return base.ConnectAsync();
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Modbus.Net.Opc
/// <summary>
/// Opc设备
/// </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>
{
/// <summary>
@@ -16,13 +16,12 @@ namespace Modbus.Net.Opc
/// <param name="connectionType">连接类型</param>
/// <param name="connectionString">连接地址</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)
{
BaseUtility = new OpcUtility(connectionType, connectionString);
AddressFormater = new AddressFormaterOpc<TKey, TUnitKey>((machine, unit) => { return new string[] { unit.Area }; }, this);
AddressCombiner = new AddressCombinerSingle<TUnitKey>();
AddressCombinerSet = new AddressCombinerSingle<TUnitKey>();
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);
}
}
}

View File

@@ -171,7 +171,7 @@
/// <returns>结构化的输出数据</returns>
public override IOutputStruct Unformat(OpcParamOut messageBytes, ref int pos)
{
var ansByte = BigEndianValueHelper.Instance.GetByte(messageBytes.Value, ref pos);
var ansByte = BigEndianLsbValueHelper.Instance.GetByte(messageBytes.Value, ref pos);
var ans = ansByte != 0;
return new WriteRequestOpcOutputStruct(ans);
}

View File

@@ -8,20 +8,6 @@ namespace Modbus.Net.Opc
/// </summary>
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>
@@ -47,6 +33,7 @@ namespace Modbus.Net.Opc
public override bool? CheckRight(OpcParamOut content)
{
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")
return null;
return base.CheckRight(content);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Modbus.Net.Opc
{
@@ -19,7 +20,6 @@ namespace Modbus.Net.Opc
/// <param name="host">Opc UA 服务地址</param>
protected OpcUaConnector(string host) : base(host)
{
Client = new MyUaClient(new Uri(ConnectionToken));
}
/// <summary>
@@ -36,5 +36,12 @@ namespace Modbus.Net.Opc
}
return _instances[host];
}
/// <inheritdoc />
public override Task<bool> ConnectAsync()
{
if (Client == null) Client = new MyUaClient(new Uri(ConnectionToken));
return base.ConnectAsync();
}
}
}

View File

@@ -4,4 +4,9 @@
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.

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Siemens地址格式化Modbus.Net专用格式
/// </summary>
public class AddressFormaterSiemens : AddressFormater
public class AddressFormaterSiemens : AddressFormater<int, int>
{
/// <summary>
/// 编码地址
@@ -32,7 +32,7 @@
/// <summary>
/// Siemens地址格式化Siemens格式
/// </summary>
public class AddressFormaterSimenseStandard : AddressFormater
public class AddressFormaterSimenseStandard : AddressFormater<int, int>
{
/// <summary>
/// 编码地址

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<AssemblyName>Modbus.Net.Siemens</AssemblyName>
<RootNamespace>Modbus.Net.Siemens</RootNamespace>
<PackageId>Modbus.Net.Siemens</PackageId>
<Version>1.4.1</Version>
<Version>1.4.2</Version>
<Authors>Chris L.(Luo Sheng)</Authors>
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
<Description>Modbus.Net Siemens Profinet Implementation</Description>

View File

@@ -23,7 +23,7 @@ namespace Modbus.Net.Siemens
/// <param name="src">本机模块位0到7仅200使用其它型号不要填写</param>
/// <param name="dst">PLC模块位0到7仅200使用其它型号不要填写</param>
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)
{
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="dst">PLC模块位0到7仅200使用其它型号不要填写</param>
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)
{
}

View File

@@ -34,16 +34,6 @@ namespace Modbus.Net.Siemens
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>

View File

@@ -1,4 +1,4 @@
using FastEnumUtility;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;
@@ -19,7 +19,7 @@ namespace Modbus.Net.Siemens
public SiemensPpiProtocolLinker(string com, int slaveAddress)
: base(com, slaveAddress, parity:
ConfigurationReader.GetValue("COM:Siemens", "Parity") != null
? FastEnum.Parse<Parity>(ConfigurationReader.GetValue("COM:Siemens", "Parity"))
? Enum.Parse<Parity>(ConfigurationReader.GetValue("COM:Siemens", "Parity"))
: null
)
{

View File

@@ -162,9 +162,9 @@ namespace Modbus.Net.Siemens
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
{
pos = 1;
var masterAddress = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
var slaveAddress = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
var confirmMessage = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
var masterAddress = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
var slaveAddress = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
var confirmMessage = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
return new ComCreateReferenceSiemensOutputStruct(slaveAddress, masterAddress, confirmMessage);
}
}
@@ -236,19 +236,19 @@ namespace Modbus.Net.Siemens
case 0xc0:
{
pos += 2;
tdpuSize = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
tdpuSize = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
break;
}
case 0xc1:
{
pos += 2;
srcTsap = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
srcTsap = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
break;
}
case 0xc2:
{
pos += 2;
dstTsap = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
dstTsap = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
break;
}
}
@@ -334,7 +334,7 @@ namespace Modbus.Net.Siemens
/// <returns>输出数据</returns>
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
{
var confirmByte = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
var confirmByte = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
return new ComConfirmMessageSiemensOutputStruct(confirmByte);
}
}
@@ -399,11 +399,11 @@ namespace Modbus.Net.Siemens
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
{
pos = 4;
var pduRef = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
var pduRef = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
pos = 14;
var maxCalling = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
var maxCalled = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
var maxPdu = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
var maxCalling = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
var maxCalled = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
var maxPdu = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
return new EstablishAssociationSiemensOutputStruct(pduRef, maxCalling, maxCalled, maxPdu);
}
}
@@ -563,7 +563,7 @@ namespace Modbus.Net.Siemens
var dbBlock = r_message.DbBlock;
var area = r_message.Area;
var offsetBit = r_message.Offset * 8;
var offsetBitBytes = BigEndianValueHelper.Instance.GetBytes(offsetBit);
var offsetBitBytes = BigEndianLsbValueHelper.Instance.GetBytes(offsetBit);
return Format(new byte[4], slaveAddress, masterAddress, (byte)0x6c, protoId, rosctr, redId, pduRef, parLg,
datLg, serviceId, numberOfVariables
, variableSpec, vAddrLg, syntaxId, type, numberOfElements, dbBlock, area,
@@ -579,11 +579,11 @@ namespace Modbus.Net.Siemens
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
{
pos = 4;
var pduRef = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
var pduRef = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
pos = 14;
var accessResult = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
var dataType = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
var length = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
var accessResult = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
var dataType = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
var length = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
var byteLength = length / 8;
var values = new byte[byteLength];
Array.Copy(messageBytes, pos, values, 0, byteLength);
@@ -700,7 +700,7 @@ namespace Modbus.Net.Siemens
public override byte[] Format(IInputStruct message)
{
var r_message = (WriteRequestSiemensInputStruct)message;
var valueBytes = BigEndianValueHelper.Instance.ObjectArrayToByteArray(r_message.WriteValue);
var valueBytes = BigEndianLsbValueHelper.Instance.ObjectArrayToByteArray(r_message.WriteValue);
var slaveAddress = r_message.SlaveAddress;
var masterAddress = r_message.MasterAddress;
const byte protoId = 0x32;
@@ -719,7 +719,7 @@ namespace Modbus.Net.Siemens
var dbBlock = r_message.DbBlock;
var area = r_message.Area;
var offsetBit = r_message.Offset * 8;
var offsetBitBytes = BigEndianValueHelper.Instance.GetBytes(offsetBit);
var offsetBitBytes = BigEndianLsbValueHelper.Instance.GetBytes(offsetBit);
const byte reserved = 0x00;
const byte type = (byte)SiemensDataType.OtherAccess;
var numberOfWriteBits = (ushort)(valueBytes.Length * 8);
@@ -739,16 +739,16 @@ namespace Modbus.Net.Siemens
{
if (messageBytes.Length == 1)
{
var accessResult = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
var accessResult = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
return new WriteRequestSiemensOutputStruct(0,
accessResult == 0xe5 ? SiemensAccessResult.NoError : SiemensAccessResult.InvalidAddress);
}
else
{
pos = 4;
var pduRef = BigEndianValueHelper.Instance.GetUShort(messageBytes, ref pos);
var pduRef = BigEndianLsbValueHelper.Instance.GetUShort(messageBytes, ref pos);
pos = 14;
var accessResult = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
var accessResult = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
return new WriteRequestSiemensOutputStruct(pduRef, (SiemensAccessResult)accessResult);
}
}

View File

@@ -15,7 +15,7 @@ namespace Modbus.Net.Siemens
public byte[] BytesExtend(byte[] content)
{
Array.Copy(new byte[] { 0x03, 0x00, 0x00, 0x00, 0x02, 0xf0, 0x80 }, 0, content, 0, 7);
Array.Copy(BigEndianValueHelper.Instance.GetBytes((ushort)content.Length), 0, content, 2, 2);
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes((ushort)content.Length), 0, content, 2, 2);
return content;
}

View File

@@ -7,9 +7,9 @@
get
{
var pos = 15;
return BigEndianValueHelper.Instance.GetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos);
return BigEndianLsbValueHelper.Instance.GetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos);
}
set { TodValue = BigEndianValueHelper.Instance.SetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), 15, value); }
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 15, value); }
}
public byte K0_4
@@ -17,13 +17,13 @@
get
{
var pos = 0;
var byteValue = BigEndianValueHelper.Instance.GetByte(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos);
var byteValue = BigEndianLsbValueHelper.Instance.GetByte(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos);
return (byte)(byteValue%64/4);
}
set
{
var pos = 0;
var byteValue = BigEndianValueHelper.Instance.GetByte(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos);
var byteValue = BigEndianLsbValueHelper.Instance.GetByte(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos);
byteValue = (byte)(byteValue - (byteValue%128/4) + value);
TodValue = (ushort)(TodValue%128 + byteValue*128);
}
@@ -34,9 +34,9 @@
get
{
var pos = 5;
return BigEndianValueHelper.Instance.GetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos);
return BigEndianLsbValueHelper.Instance.GetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos);
}
set { TodValue = BigEndianValueHelper.Instance.SetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), 5, value); }
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 5, value); }
}
public byte UA
@@ -44,15 +44,15 @@
get
{
var pos = 3;
var low = BigEndianValueHelper.Instance.GetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos) ? 1 : 0;
var high = BigEndianValueHelper.Instance.GetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos) ? 1 : 0;
var low = BigEndianLsbValueHelper.Instance.GetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos) ? 1 : 0;
var high = BigEndianLsbValueHelper.Instance.GetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos) ? 1 : 0;
high *= 2;
return (byte) (high + low);
}
set
{
TodValue = BigEndianValueHelper.Instance.SetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), 3, value % 2 >= 1);
TodValue = BigEndianValueHelper.Instance.SetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), 4, value / 2 >= 1);
TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 3, value % 2 >= 1);
TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 4, value / 2 >= 1);
}
}
public bool UZS
@@ -60,9 +60,9 @@
get
{
var pos = 2;
return BigEndianValueHelper.Instance.GetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos);
return BigEndianLsbValueHelper.Instance.GetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos);
}
set { TodValue = BigEndianValueHelper.Instance.SetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), 2, value); }
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 2, value); }
}
public bool ESY
@@ -70,9 +70,9 @@
get
{
var pos = 1;
return BigEndianValueHelper.Instance.GetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos);
return BigEndianLsbValueHelper.Instance.GetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos);
}
set { TodValue = BigEndianValueHelper.Instance.SetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), 1, value); }
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 1, value); }
}
public bool SYA
@@ -80,9 +80,9 @@
get
{
var pos = 0;
return BigEndianValueHelper.Instance.GetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), ref pos);
return BigEndianLsbValueHelper.Instance.GetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), ref pos);
}
set { TodValue = BigEndianValueHelper.Instance.SetBit(BigEndianValueHelper.Instance.GetBytes(TodValue), 0, value); }
set { TodValue = BigEndianLsbValueHelper.Instance.SetBit(BigEndianLsbValueHelper.Instance.GetBytes(TodValue), 0, value); }
}
public ushort TodValue { get; set; }

View File

@@ -78,16 +78,6 @@ namespace Modbus.Net.Siemens
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>
@@ -100,17 +90,6 @@ namespace Modbus.Net.Siemens
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>

View File

@@ -7,6 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net", "Modbus.Net\Mo
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64472271-8B95-4036-ACF9-C10941C1DB0A}"
ProjectSection(SolutionItems) = preProject
..\LICENSE = ..\LICENSE
..\README.md = ..\README.md
EndProjectSection
EndProject
@@ -34,56 +35,152 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TripleAdd", "..\Samples\Tri
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}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "h-opc", "..\h-opc\h-opc\h-opc.csproj", "{DC6425E4-1409-488D-A014-4DCC909CF542}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.BigEndian3412", "Modbus.Net.BigEndian3412\Modbus.Net.BigEndian3412.csproj", "{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.OpcRcw", "..\Technosoftware\OpcRcw\Technosoftware.OpcRcw.csproj", "{7689CBF8-1992-467D-AD45-E1464F705220}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.DaAeHdaClient", "..\Technosoftware\DaAeHdaClient\Technosoftware.DaAeHdaClient.csproj", "{116160B2-7D6D-40A2-839C-7997BC0E1A0C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.DaAeHdaClient.Com", "..\Technosoftware\DaAeHdaClient.Com\Technosoftware.DaAeHdaClient.Com.csproj", "{ACAF0A16-FC51-4369-BFA8-484FF20707D7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Debug|x64.ActiveCfg = Debug|Any CPU
{124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Debug|x64.Build.0 = Debug|Any CPU
{124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Release|Any CPU.Build.0 = Release|Any CPU
{124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Release|x64.ActiveCfg = Release|Any CPU
{124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Release|x64.Build.0 = Release|Any CPU
{FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Debug|x64.ActiveCfg = Debug|Any CPU
{FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Debug|x64.Build.0 = Debug|Any CPU
{FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Release|Any CPU.Build.0 = Release|Any CPU
{FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Release|x64.ActiveCfg = Release|Any CPU
{FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Release|x64.Build.0 = Release|Any CPU
{6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Debug|x64.ActiveCfg = Debug|Any CPU
{6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Debug|x64.Build.0 = Debug|Any CPU
{6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Release|Any CPU.Build.0 = Release|Any CPU
{6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Release|x64.ActiveCfg = Release|Any CPU
{6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Release|x64.Build.0 = Release|Any CPU
{3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Debug|x64.ActiveCfg = Debug|Any CPU
{3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Debug|x64.Build.0 = Debug|Any CPU
{3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Release|Any CPU.Build.0 = Release|Any CPU
{3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Release|x64.ActiveCfg = Release|Any CPU
{3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Release|x64.Build.0 = Release|Any CPU
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Debug|x64.ActiveCfg = Debug|Any CPU
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Debug|x64.Build.0 = Debug|Any CPU
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Release|Any CPU.Build.0 = Release|Any CPU
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Release|x64.ActiveCfg = Release|Any CPU
{22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Release|x64.Build.0 = Release|Any CPU
{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Debug|x64.ActiveCfg = Debug|Any CPU
{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Debug|x64.Build.0 = Debug|Any CPU
{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Release|Any CPU.Build.0 = Release|Any CPU
{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Release|x64.ActiveCfg = Release|Any CPU
{D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Release|x64.Build.0 = Release|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|x64.ActiveCfg = Debug|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|x64.Build.0 = Debug|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.Build.0 = Release|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|x64.ActiveCfg = Release|Any CPU
{1857DA63-3335-428F-84D8-1FA4F8178643}.Release|x64.Build.0 = Release|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|x64.ActiveCfg = Debug|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|x64.Build.0 = Debug|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.Build.0 = Release|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|x64.ActiveCfg = Release|Any CPU
{C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|x64.Build.0 = Release|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|x64.ActiveCfg = Debug|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|x64.Build.0 = Debug|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|Any CPU.Build.0 = Release|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|x64.ActiveCfg = Release|Any CPU
{AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|x64.Build.0 = Release|Any CPU
{414956B8-DBD4-414C-ABD3-565580739646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{414956B8-DBD4-414C-ABD3-565580739646}.Debug|Any CPU.Build.0 = Debug|Any CPU
{414956B8-DBD4-414C-ABD3-565580739646}.Debug|x64.ActiveCfg = Debug|Any CPU
{414956B8-DBD4-414C-ABD3-565580739646}.Debug|x64.Build.0 = Debug|Any CPU
{414956B8-DBD4-414C-ABD3-565580739646}.Release|Any CPU.ActiveCfg = Release|Any CPU
{414956B8-DBD4-414C-ABD3-565580739646}.Release|Any CPU.Build.0 = Release|Any CPU
{414956B8-DBD4-414C-ABD3-565580739646}.Release|x64.ActiveCfg = Release|Any CPU
{414956B8-DBD4-414C-ABD3-565580739646}.Release|x64.Build.0 = Release|Any CPU
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|x64.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.Build.0 = Release|Any CPU
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|x64.ActiveCfg = Release|Any CPU
{C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|x64.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}.Debug|x64.ActiveCfg = Debug|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|x64.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
{DC6425E4-1409-488D-A014-4DCC909CF542}.Release|x64.ActiveCfg = Release|Any CPU
{DC6425E4-1409-488D-A014-4DCC909CF542}.Release|x64.Build.0 = Release|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|x64.ActiveCfg = Debug|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|x64.Build.0 = Debug|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|Any CPU.Build.0 = Release|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|x64.ActiveCfg = Release|Any CPU
{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|x64.Build.0 = Release|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Debug|x64.ActiveCfg = Debug|x64
{7689CBF8-1992-467D-AD45-E1464F705220}.Debug|x64.Build.0 = Debug|x64
{7689CBF8-1992-467D-AD45-E1464F705220}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Release|Any CPU.Build.0 = Release|Any CPU
{7689CBF8-1992-467D-AD45-E1464F705220}.Release|x64.ActiveCfg = Release|x64
{7689CBF8-1992-467D-AD45-E1464F705220}.Release|x64.Build.0 = Release|x64
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|x64.ActiveCfg = Debug|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|x64.Build.0 = Debug|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|Any CPU.Build.0 = Release|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|x64.ActiveCfg = Release|Any CPU
{116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|x64.Build.0 = Release|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|x64.ActiveCfg = Debug|x64
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|x64.Build.0 = Debug|x64
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|Any CPU.Build.0 = Release|Any CPU
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|x64.ActiveCfg = Release|x64
{ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,5 +1,4 @@
using FastEnumUtility;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
@@ -62,12 +61,23 @@ namespace Modbus.Net
}
case "addressMap":
{
paramsSet.Add(AddressReader.ReadAddresses(dic["addressMap"]));
break;
var machineTypeTemp = Assembly.Load("Modbus.Net." + dic["protocol"]).GetType("Modbus.Net." + dic["protocol"] + "." + dic["protocol"] + "Machine`2");
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":
{
paramsSet.Add(FastEnum.Parse<Endian>(dic["endian"]));
paramsSet.Add(Endian.Parse(dic["endian"]));
break;
}
default:
@@ -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()
.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)
.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();
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");
if (addressNew.DataType == null) throw new ArgumentNullException(string.Format("DataType define error {0} {1} {2}", addressMapName, addressNew.Id, address["DataType"]));
ans.Add(addressNew);

View File

@@ -30,7 +30,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="timeoutTime">发送超时时间</param>
/// <param name="isFullDuplex">是否为全双工</param>
protected BaseConnector(int timeoutTime = 10000, bool isFullDuplex = true)
protected BaseConnector(int timeoutTime = 10000, bool isFullDuplex = false)
{
IsFullDuplex = isFullDuplex;
if (timeoutTime < -1) timeoutTime = -1;
@@ -49,12 +49,17 @@ namespace Modbus.Net
/// 发送内部
/// </summary>
/// <param name="message">发送的信息</param>
/// <param name="repeat">是否为重发消息</param>
/// <returns>发送信息的定义</returns>
protected async Task<MessageWaitingDef> SendMsgInner(byte[] message)
protected async Task<MessageWaitingDef> SendMsgInner(byte[] message, bool repeat = false)
{
IDisposable asyncLock = null;
try
{
if (!Controller.IsSending)
{
Controller.SendStart();
}
var messageSendingdef = Controller.AddMessage(message);
if (messageSendingdef != null)
{
@@ -69,6 +74,11 @@ namespace Modbus.Net
success = messageSendingdef.ReceiveMutex.WaitOne(TimeoutTime);
if (success)
{
if (!repeat && messageSendingdef.ReceiveMessage == null)
{
asyncLock?.Dispose();
return await SendMsgInner(message, true);
}
return messageSendingdef;
}
}

View File

@@ -506,7 +506,7 @@ namespace Modbus.Net
_taskCancel = true;
}
private void ReceiveMessage()
private async Task ReceiveMessage()
{
while (!_taskCancel)
{
@@ -549,9 +549,19 @@ namespace Modbus.Net
CacheBytes.RemoveRange(0, confirmed.Item1.Length);
}
}
else if (confirmed.Item2 == false)
else
{
lock (CacheBytes)
{
CacheBytes.RemoveRange(0, confirmed.Item1.Length);
}
var sendMessage = InvokeReturnMessage(confirmed.Item1);
//主动传输事件
if (sendMessage != null)
{
await SendMsgWithoutConfirm(sendMessage);
}
}
}
}

View File

@@ -53,12 +53,17 @@ namespace Modbus.Net
/// 发送内部
/// </summary>
/// <param name="message">发送的信息</param>
/// <param name="repeat">是否为重发消息</param>
/// <returns>发送信息的定义</returns>
protected async Task<MessageWaitingDef> SendMsgInner(byte[] message)
protected async Task<MessageWaitingDef> SendMsgInner(byte[] message, bool repeat = false)
{
IDisposable asyncLock = null;
try
{
if (!Controller.IsSending)
{
Controller.SendStart();
}
var messageSendingdef = Controller.AddMessage(message);
if (messageSendingdef != null)
{
@@ -73,6 +78,11 @@ namespace Modbus.Net
success = messageSendingdef.ReceiveMutex.WaitOne(TimeoutTime);
if (success)
{
if (!repeat && messageSendingdef.ReceiveMessage == null)
{
asyncLock?.Dispose();
return await SendMsgInner(message, true);
}
return messageSendingdef;
}
}

View File

@@ -21,6 +21,11 @@ namespace Modbus.Net
/// </summary>
protected Task SendingThread { get; set; }
/// <summary>
/// 消息维护线程是否在运行
/// </summary>
public virtual bool IsSending => SendingThread.Status.Equals(TaskStatus.Running);
/// <summary>
/// 包切分位置函数
/// </summary>
@@ -92,15 +97,16 @@ namespace Modbus.Net
/// <param name="def">需要添加的信息信息</param>
protected virtual bool AddMessageToList(MessageWaitingDef def)
{
var ans = false;
lock (WaitingMessages)
{
if (WaitingMessages.FirstOrDefault(p => p.Key == def.Key) == null || def.Key == null)
{
WaitingMessages.Add(def);
return true;
ans = true;
}
return false;
}
return ans;
}
/// <summary>
@@ -116,45 +122,89 @@ namespace Modbus.Net
var ans = new List<(byte[], bool)>();
byte[] receiveMessageCopy = new byte[receiveMessage.Length];
Array.Copy(receiveMessage, receiveMessageCopy, receiveMessage.Length);
var length = LengthCalc?.Invoke(receiveMessageCopy);
List<byte[]> duplicatedMessages;
int? length = -1;
try
{
length = LengthCalc?.Invoke(receiveMessageCopy);
}
catch
{
//ignore
}
List<(byte[], bool)> duplicatedMessages;
if (length == null || length == -1) return ans;
if (length == 0) return null;
else if (length == 0) return null;
else
{
duplicatedMessages = new List<byte[]>();
duplicatedMessages = new List<(byte[], bool)>();
var skipLength = 0;
while (receiveMessageCopy.Length >= length)
{
var duplicateMessage = receiveMessageCopy.Take(length.Value).ToArray();
if (CheckRightFunc != null && CheckRightFunc(duplicateMessage) == false)
{
receiveMessageCopy = receiveMessageCopy.TakeLast(receiveMessage.Length - 1).ToArray();
receiveMessageCopy = receiveMessageCopy.TakeLast(receiveMessageCopy.Length - 1).ToArray();
skipLength++;
continue;
}
duplicatedMessages.Add(duplicateMessage);
receiveMessageCopy = receiveMessageCopy.TakeLast(receiveMessage.Length - length.Value).ToArray();
if (skipLength > 0)
{
duplicatedMessages.Add((new byte[skipLength], false));
}
skipLength = 0;
duplicatedMessages.Add((duplicateMessage, true));
receiveMessageCopy = receiveMessageCopy.TakeLast(receiveMessageCopy.Length - length.Value).ToArray();
if (receiveMessageCopy.Length == 0) break;
length = LengthCalc?.Invoke(receiveMessageCopy);
if (length == -1) break;
if (length == 0) return null;
}
if (skipLength > 0)
{
lock (WaitingMessages)
{
var def = GetMessageFromWaitingList(null);
if (def != null)
{
lock (WaitingMessages)
{
if (WaitingMessages.IndexOf(def) >= 0)
{
WaitingMessages.Remove(def);
}
}
def.ReceiveMutex.Set();
}
}
return null;
}
}
foreach (var message in duplicatedMessages)
{
var def = GetMessageFromWaitingList(message);
if (def != null)
if (!message.Item2)
{
def.ReceiveMessage = receiveMessage;
lock (WaitingMessages)
{
WaitingMessages.Remove(def);
}
def.ReceiveMutex.Set();
ans.Add((message, true));
ans.Add((message.Item1, true));
}
else
{
ans.Add((message, false));
var def = GetMessageFromWaitingList(message.Item1);
if (def != null)
{
def.ReceiveMessage = message.Item1;
lock (WaitingMessages)
{
if (WaitingMessages.IndexOf(def) >= 0)
{
WaitingMessages.Remove(def);
}
}
def.ReceiveMutex.Set();
ans.Add((message.Item1, true));
}
else
{
ans.Add((message.Item1, false));
}
}
}
return ans;
@@ -172,7 +222,10 @@ namespace Modbus.Net
{
lock (WaitingMessages)
{
WaitingMessages.Remove(def);
if (WaitingMessages.IndexOf(def) >= 0)
{
WaitingMessages.Remove(def);
}
}
}
}

View File

@@ -20,9 +20,10 @@ namespace Modbus.Net
var ans = 0;
foreach (var position in packageCountPositions)
{
if (position >= receiveMessage.Length) return -1;
if (position > receiveMessage.Length - 1) return -1;
ans = ans * 256 + receiveMessage[position];
}
if (ans + otherCount > receiveMessage.Length) return -1;
return ans + otherCount;
}

View File

@@ -18,8 +18,6 @@ namespace Modbus.Net
private int _waitingListMaxCount;
private readonly Semaphore _taskCycleSema;
/// <summary>
/// 间隔时间
/// </summary>
@@ -29,35 +27,28 @@ namespace Modbus.Net
/// 构造器
/// </summary>
/// <param name="acquireTime">间隔时间</param>
/// <param name="activateSema">是否开启信号量</param>
/// <param name="lengthCalc">包切分长度函数</param>
/// <param name="checkRightFunc">包校验函数</param>
/// <param name="waitingListMaxCount">包等待队列长度</param>
public FifoController(int acquireTime, bool activateSema = true, Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null)
public FifoController(int acquireTime, Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null)
: base(lengthCalc, checkRightFunc)
{
_waitingListMaxCount = int.Parse(waitingListMaxCount != null ? waitingListMaxCount.ToString() : null ?? ConfigurationReader.GetValueDirect("Controller", "WaitingListCount"));
if (activateSema)
{
_taskCycleSema = new Semaphore(0, _waitingListMaxCount);
}
AcquireTime = acquireTime;
}
/// <inheritdoc />
protected override void SendingMessageControlInner()
{
try
while (!_taskCancel)
{
_taskCycleSema?.WaitOne();
while (!_taskCancel)
if (AcquireTime > 0)
{
if (AcquireTime > 0)
{
Thread.Sleep(AcquireTime);
}
bool sendSuccess = false;
lock (WaitingMessages)
Thread.Sleep(AcquireTime);
}
lock (WaitingMessages)
{
try
{
if (_currentSendingPos == null)
{
@@ -65,7 +56,6 @@ namespace Modbus.Net
{
_currentSendingPos = WaitingMessages.First();
_currentSendingPos.SendMutex.Set();
sendSuccess = true;
}
}
else
@@ -73,32 +63,27 @@ namespace Modbus.Net
if (WaitingMessages.Count <= 0)
{
_currentSendingPos = null;
_taskCycleSema?.Close();
sendSuccess = true;
}
else if (WaitingMessages.Count > WaitingMessages.IndexOf(_currentSendingPos) + 1)
else if (WaitingMessages.IndexOf(_currentSendingPos) == -1)
{
_currentSendingPos = WaitingMessages[WaitingMessages.IndexOf(_currentSendingPos) + 1];
_currentSendingPos = WaitingMessages.First();
_currentSendingPos.SendMutex.Set();
sendSuccess = true;
}
}
}
if (sendSuccess)
catch (ObjectDisposedException e)
{
_taskCycleSema?.WaitOne();
logger.LogError(e, "Controller _currentSendingPos disposed");
_currentSendingPos = null;
}
catch (Exception e)
{
logger.LogError(e, "Controller throws exception");
_taskCancel = true;
}
}
}
catch (ObjectDisposedException)
{
//ignore
}
catch (Exception e)
{
logger.LogError(e, "Controller throws exception");
}
Clear();
}
/// <inheritdoc />
@@ -138,11 +123,11 @@ namespace Modbus.Net
{
return false;
}
var success = base.AddMessageToList(def);
if (success)
if (_taskCancel)
{
_taskCycleSema?.Release();
return false;
}
var success = base.AddMessageToList(def);
return success;
}
}

View File

@@ -19,12 +19,11 @@ namespace Modbus.Net
/// </summary>
/// <param name="keyMatches">匹配字典每个Collection代表一个匹配集合每一个匹配集合中的数字代表需要匹配的位置最后计算出来的数字是所有位置数字按照集合排序后叠放在一起</param>
/// <param name="acquireTime">获取间隔</param>
/// <param name="activateSema">是否开启信号量</param>
/// <param name="lengthCalc">包长度计算</param>
/// <param name="checkRightFunc">包校验函数</param>
/// <param name="waitingListMaxCount">包等待队列长度</param>
public MatchController(ICollection<(int, int)>[] keyMatches, int acquireTime, bool activateSema = true,
Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null) : base(acquireTime, activateSema, lengthCalc, checkRightFunc, waitingListMaxCount)
public MatchController(ICollection<(int, int)>[] keyMatches, int acquireTime,
Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null) : base(acquireTime, lengthCalc, checkRightFunc, waitingListMaxCount)
{
KeyMatches = keyMatches;
}
@@ -39,7 +38,7 @@ namespace Modbus.Net
int tmpCount = 0, tmpCount2 = 0;
foreach (var matchPos in matchPoses)
{
if (matchPos.Item1 >= message.Length || matchPos.Item2 >= message.Length) return null;
if (matchPos.Item1 > message.Length - 1 || matchPos.Item2 > message.Length - 1) return null;
tmpCount = tmpCount * 256 + message[matchPos.Item1];
tmpCount2 = tmpCount2 * 256 + message[matchPos.Item2];
}
@@ -52,6 +51,7 @@ namespace Modbus.Net
/// <inheritdoc />
protected override MessageWaitingDef GetMessageFromWaitingList(byte[] receiveMessage)
{
if (receiveMessage == null) return null;
var returnKey = GetKeyFromMessage(receiveMessage);
MessageWaitingDef ans;
lock (WaitingMessages)

View File

@@ -8,10 +8,15 @@ namespace Modbus.Net
/// </summary>
public class MatchDirectlySendController : MatchController
{
/// <summary>
/// 消息维护线程是否在运行
/// </summary>
public override bool IsSending => true;
/// <inheritdoc />
public MatchDirectlySendController(ICollection<(int, int)>[] keyMatches,
Func<byte[], int> lengthCalc = null, Func<byte[], bool?> checkRightFunc = null, int? waitingListMaxCount = null) : base(keyMatches,
0, false, lengthCalc, checkRightFunc, waitingListMaxCount)
0, lengthCalc, checkRightFunc, waitingListMaxCount)
{
}

View File

@@ -0,0 +1,152 @@
using System;
namespace Modbus.Net
{
/// <summary>
/// 端格式
/// </summary>
public partial class Endian
{
/// <summary>
/// 小端
/// </summary>
public const int LittleEndianLsb = 1;
/// <summary>
/// 大端-大端位
/// </summary>
public const int BigEndianLsb = 2;
/// <summary>
/// 大端-大端位
/// </summary>
public const int BigEndianMsb = 3;
#pragma warning disable
protected int Value { get; set; }
protected Endian(int value)
{
Value = value;
}
public static implicit operator int(Endian endian)
{
return endian.Value;
}
public static implicit operator Endian(int value)
{
return new Endian(value);
}
public static implicit operator Endian(string value)
{
return Endian.Parse(value);
}
public static implicit operator string(Endian endian)
{
return endian.ToString();
}
public static Endian Parse(string value)
{
var Assemblies = AssemblyHelper.GetAllLibraryAssemblies();
foreach (var assembly in Assemblies)
{
if (assembly.GetType("Modbus.Net.Endian")?.GetField(value) != null)
{
return (int)assembly.GetType("Modbus.Net.Endian").GetField(value).GetValue(null);
}
}
throw new NotSupportedException("Endian name " + value + " is not supported.");
}
public override string ToString()
{
var Assemblies = AssemblyHelper.GetAllLibraryAssemblies();
foreach (var assembly in Assemblies)
{
var endianType = assembly.GetType("Modbus.Net.Endian");
if (endianType != null)
{
foreach (var field in endianType.GetFields())
{
if ((int)field.GetValue(null) == Value)
{
return field.Name;
}
}
}
}
return null;
}
#pragma warning restore
}
/// <summary>
/// 读写设备值的方式
/// </summary>
public enum MachineDataType
{
/// <summary>
/// 地址
/// </summary>
Address,
/// <summary>
/// 通讯标识
/// </summary>
CommunicationTag,
/// <summary>
/// 名称
/// </summary>
Name,
/// <summary>
/// Id
/// </summary>
Id
}
/// <summary>
/// 波特率
/// </summary>
public enum BaudRate
{
#pragma warning disable
BaudRate75 = 75,
BaudRate110 = 110,
BaudRate134 = 134,
BaudRate150 = 150,
BaudRate300 = 300,
BaudRate600 = 600,
BaudRate1200 = 1200,
BaudRate1800 = 1800,
BaudRate2400 = 2400,
BaudRate4800 = 4800,
BaudRate9600 = 9600,
BaudRate14400 = 14400,
BaudRate19200 = 19200,
BaudRate38400 = 38400,
BaudRate57600 = 57600,
BaudRate115200 = 115200,
BaudRate128000 = 128000,
BaudRate230400 = 230400,
BaudRate460800 = 460800,
BaudRate921600 = 921600,
#pragma warning restore
}
/// <summary>
/// 数据位
/// </summary>
public enum DataBits
{
#pragma warning disable
Seven = 7,
Eight = 8,
#pragma warning restore
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Modbus.Net
{
/// <summary>
/// 程序集辅助类
/// </summary>
public static class AssemblyHelper
{
/// <summary>
/// 获取与Modbus.Net相关的所有程序集
/// </summary>
/// <returns></returns>
public static List<Assembly> GetAllLibraryAssemblies()
{
List<Assembly> allAssemblies = new List<Assembly>();
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
allAssemblies.Add(Assembly.Load("Modbus.Net"));
foreach (string dll in Directory.GetFiles(path, "Modbus.Net.*.dll"))
allAssemblies.Add(Assembly.LoadFile(dll));
return allAssemblies;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Modbus.Net
namespace Modbus.Net
{
#if NET462
#pragma warning disable 1591

View File

@@ -8,16 +8,17 @@ using System.Text;
namespace Modbus.Net
{
/// <summary>
/// 值与字节数组之间转换的辅助类小端格式这是一个Singleton类
/// 值与字节数组之间转换的辅助类
/// </summary>
public class ValueHelper
public abstract class ValueHelper
{
private static readonly ILogger<ValueHelper> logger = LogProvider.CreateLogger<ValueHelper>();
/// <summary>
/// 兼容数据类型对应的字节长度
/// </summary>
public Dictionary<string, double> ByteLength = new Dictionary<string, double>
/// <summary>
/// 兼容数据类型对应的字节长度
/// </summary>
public static Dictionary<string, double> ByteLength => new Dictionary<string, double>
{
{"System.Boolean", 0.125},
{"System.Byte", 1},
@@ -31,10 +32,315 @@ namespace Modbus.Net
{"System.Double", 8}
};
/// <summary>
/// 将一个byte数字转换为一个byte元素的数组。
/// </summary>
/// <param name="value">byte数字</param>
/// <returns>byte数组</returns>
public abstract byte[] GetBytes(byte value);
/// <summary>
/// 将short数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(short value);
/// <summary>
/// 将int数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(int value);
/// <summary>
/// 将long数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(long value);
/// <summary>
/// 将ushort数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(ushort value);
/// <summary>
/// 将uint数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(uint value);
/// <summary>
/// 将ulong数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(ulong value);
/// <summary>
/// 将float数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(float value);
/// <summary>
/// 将double数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(double value);
/// <summary>
/// 将object数字转换为byte数组
/// </summary>
/// <param name="value">待转换的值</param>
/// <param name="type">待转换的值的类型</param>
/// <returns>转换后的byte数组</returns>
public abstract byte[] GetBytes(object value, Type type);
/// <summary>
/// 将byte数组中相应的位置转换为对应类型的数字
/// </summary>
/// <param name="data">待转换的字节数组</param>
/// <param name="pos">转换数字的位置</param>
/// <param name="subPos">转换数字的比特位置仅Type为bool的时候有效</param>
/// <param name="t">转换的类型</param>
/// <returns>转换出的数字</returns>
public abstract object GetValue(byte[] data, ref int pos, ref int subPos, Type t);
/// <summary>
/// 将byte数组中相应的位置转换为byte数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract byte GetByte(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为short数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract short GetShort(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为int数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract int GetInt(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为long数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract long GetLong(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为ushort数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract ushort GetUShort(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为uint数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract uint GetUInt(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为ulong数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract ulong GetULong(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为float数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract float GetFloat(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为double数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public abstract double GetDouble(byte[] data, ref int pos);
/// <summary>
/// 将byte数组中相应的位置转换为字符串
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="count">转换的个数</param>
/// <param name="pos">转换数字的位置</param>
/// <param name="encoding">编码方法</param>
/// <returns>转换出的字符串</returns>
public abstract string GetString(byte[] data, int count, ref int pos, Encoding encoding);
/// <summary>
/// 将byte数组中相应的位置转换为8个bit数字
/// </summary>
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的位数组</returns>
public abstract bool[] GetBits(byte[] data, ref int pos);
/// <summary>
/// 获取一个byte中相对应的bit数组展开中第n个位置中的bit元素。
/// </summary>
/// <param name="number">byte数字</param>
/// <param name="pos">bit数组中的对应位置</param>
/// <param name="subPos">小数位</param>
/// <returns>对应位置的bit元素</returns>
public abstract bool GetBit(byte number, ref int pos, ref int subPos);
/// <summary>
/// 获取一个字节数组中某个Bit位的数据
/// </summary>
/// <param name="number">byte数字</param>
/// <param name="pos">bit数组中的对应位置</param>
/// <param name="subPos">小数位</param>
/// <returns>对应位置的bit元素</returns>
public abstract bool GetBit(byte[] number, ref int pos, ref int subPos);
/// <summary>
/// 反转一个字节的8个Bit位
/// </summary>
/// <param name="originalByte">原始bit数组</param>
/// <returns>反转的bit数组</returns>
public abstract byte ReverseByte(byte originalByte);
/// <summary>
/// 将待转换的对象数组转换为需要发送的byte数组
/// </summary>
/// <param name="contents">object数组</param>
/// <returns>byte数组</returns>
public abstract byte[] ObjectArrayToByteArray(object[] contents);
/// <summary>
/// 将byte数组转换为用户指定类型的数组通过object数组的方式返回用户需要再把object转换为自己需要的类型或调用ObjectArrayToDestinationArray返回单一类型的目标数组。
/// </summary>
/// <param name="contents">byte数组</param>
/// <param name="translateTypeAndCount">单一的类型和需要转换的个数的键值对</param>
/// <returns>object数组</returns>
public abstract object[] ByteArrayToObjectArray(byte[] contents, KeyValuePair<Type, int> translateTypeAndCount);
/// <summary>
/// 将一个byte数组转换成用户指定类型的数组使用模板参数确定需要转换的类型
/// </summary>
/// <typeparam name="T">目标数组类型</typeparam>
/// <param name="contents">待转换的数组</param>
/// <param name="getCount">转换的个数</param>
/// <returns>以T为类型的数组</returns>
public abstract T[] ByteArrayToDestinationArray<T>(byte[] contents, int getCount);
/// <summary>
/// 将byte数组转换为用户指定类型的数组通过object数组的方式返回用户需要再把object转换为自己需要的类型或调用ObjectArrayToDestinationArray返回单一类型的目标数组。
/// </summary>
/// <param name="contents">byte数组</param>
/// <param name="translateTypeAndCount">
/// 一连串类型和需要转换的个数的键值对该方法会依次转换每一个需要转的目标数据类型。比如typeof(int),5; typeof(short),3
/// 会转换出8个元素当然前提是byte数组足够长的时候5个int和3个short然后全部变为object类型返回。
/// </param>
/// <returns>object数组</returns>
public abstract object[] ByteArrayToObjectArray(byte[] contents, IEnumerable<KeyValuePair<Type, int>> translateTypeAndCount);
/// <summary>
/// 将object数组转换为目标类型的单一数组
/// </summary>
/// <typeparam name="T">需要转换的目标类型</typeparam>
/// <param name="contents">object数组</param>
/// <returns>目标数组</returns>
public abstract T[] ObjectArrayToDestinationArray<T>(object[] contents);
/// <summary>
/// 在一个数组中写一个值
/// </summary>
/// <param name="contents">待写入的字节数组</param>
/// <param name="setPos">设置的位置</param>
/// <param name="subPos">设置的比特位位置仅setValue为bit的时候有效</param>
/// <param name="setValue">写入的值</param>
/// <returns>写入是否成功</returns>
public abstract bool SetValue(byte[] contents, int setPos, int subPos, object setValue);
/// <summary>
/// 将short值设置到byte数组中
/// </summary>
/// <param name="contents">待设置的byte数组</param>
/// <param name="pos">设置的位置</param>
/// <param name="setValue">要设置的值</param>
/// <returns>设置是否成功</returns>
public abstract bool SetValue(byte[] contents, int pos, object setValue);
/// <summary>
/// 设置一组数据中的一个bit
/// </summary>
/// <param name="contents">待设置的字节数组</param>
/// <param name="pos">位置</param>
/// <param name="subPos">bit位置</param>
/// <param name="setValue">bit数</param>
/// <returns>设置是否成功</returns>
public abstract bool SetBit(byte[] contents, int pos, int subPos, bool setValue);
/// <summary>
/// 根据端格式获取ValueHelper实例
/// </summary>
/// <param name="endian">端格式</param>
/// <returns>对应的ValueHelper实例</returns>
public static ValueHelper GetInstance(Endian endian)
{
if (EndianInstanceCache.ContainsKey(endian.ToString()))
{
return EndianInstanceCache[endian.ToString()];
}
foreach (var assembly in AssemblyHelper.GetAllLibraryAssemblies())
{
var valueHelper = assembly.GetType("Modbus.Net." + endian.ToString() + "ValueHelper")?.GetProperty("Instance")?.GetValue(null, null) as ValueHelper;
if (valueHelper != null)
{
EndianInstanceCache[endian.ToString()] = valueHelper;
return valueHelper;
}
}
throw new NotImplementedException("ValueHelper " + endian.ToString() + " doesn't exist.");
}
/// <summary>
/// ValueHelper实例的缓存
/// </summary>
protected static Dictionary<string, ValueHelper> EndianInstanceCache = new Dictionary<string, ValueHelper>();
}
/// <summary>
/// 值与字节数组之间转换的辅助类小端格式这是一个Singleton类
/// </summary>
public class LittleEndianLsbValueHelper : ValueHelper
{
private static readonly ILogger<LittleEndianLsbValueHelper> logger = LogProvider.CreateLogger<LittleEndianLsbValueHelper>();
/// <summary>
/// 构造器
/// </summary>
protected ValueHelper()
protected LittleEndianLsbValueHelper()
{
}
@@ -53,7 +359,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">byte数字</param>
/// <returns>byte数组</returns>
public byte[] GetBytes(byte value)
public override byte[] GetBytes(byte value)
{
return new[] { value };
}
@@ -63,7 +369,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(short value)
public override byte[] GetBytes(short value)
{
return BitConverter.GetBytes(value);
}
@@ -73,7 +379,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(int value)
public override byte[] GetBytes(int value)
{
return BitConverter.GetBytes(value);
}
@@ -83,7 +389,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(long value)
public override byte[] GetBytes(long value)
{
return BitConverter.GetBytes(value);
}
@@ -93,7 +399,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(ushort value)
public override byte[] GetBytes(ushort value)
{
return BitConverter.GetBytes(value);
}
@@ -103,7 +409,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(uint value)
public override byte[] GetBytes(uint value)
{
return BitConverter.GetBytes(value);
}
@@ -113,7 +419,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(ulong value)
public override byte[] GetBytes(ulong value)
{
return BitConverter.GetBytes(value);
}
@@ -123,7 +429,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(float value)
public override byte[] GetBytes(float value)
{
return BitConverter.GetBytes(value);
}
@@ -133,7 +439,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="value">待转换的值</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(double value)
public override byte[] GetBytes(double value)
{
return BitConverter.GetBytes(value);
}
@@ -144,7 +450,7 @@ namespace Modbus.Net
/// <param name="value">待转换的值</param>
/// <param name="type">待转换的值的类型</param>
/// <returns>转换后的byte数组</returns>
public virtual byte[] GetBytes(object value, Type type)
public override byte[] GetBytes(object value, Type type)
{
switch (type.FullName)
{
@@ -193,6 +499,11 @@ namespace Modbus.Net
var bytes = _Instance.GetBytes((byte)value);
return bytes;
}
case "System.Boolean":
{
var bytes = _Instance.GetBytes((bool)value ? (byte)1 : (byte)0);
return bytes;
}
default:
{
throw new NotImplementedException("没有实现除整数以外的其它转换方式");
@@ -208,7 +519,7 @@ namespace Modbus.Net
/// <param name="subPos">转换数字的比特位置仅Type为bool的时候有效</param>
/// <param name="t">转换的类型</param>
/// <returns>转换出的数字</returns>
public virtual object GetValue(byte[] data, ref int pos, ref int subPos, Type t)
public override object GetValue(byte[] data, ref int pos, ref int subPos, Type t)
{
switch (t.FullName)
{
@@ -275,7 +586,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual byte GetByte(byte[] data, ref int pos)
public override byte GetByte(byte[] data, ref int pos)
{
var t = data[pos];
pos += 1;
@@ -288,7 +599,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual short GetShort(byte[] data, ref int pos)
public override short GetShort(byte[] data, ref int pos)
{
var t = BitConverter.ToInt16(data, pos);
pos += 2;
@@ -301,7 +612,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual int GetInt(byte[] data, ref int pos)
public override int GetInt(byte[] data, ref int pos)
{
var t = BitConverter.ToInt32(data, pos);
pos += 4;
@@ -314,7 +625,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual long GetLong(byte[] data, ref int pos)
public override long GetLong(byte[] data, ref int pos)
{
var t = BitConverter.ToInt64(data, pos);
pos += 8;
@@ -327,7 +638,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual ushort GetUShort(byte[] data, ref int pos)
public override ushort GetUShort(byte[] data, ref int pos)
{
var t = BitConverter.ToUInt16(data, pos);
pos += 2;
@@ -340,7 +651,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual uint GetUInt(byte[] data, ref int pos)
public override uint GetUInt(byte[] data, ref int pos)
{
var t = BitConverter.ToUInt32(data, pos);
pos += 4;
@@ -353,7 +664,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual ulong GetULong(byte[] data, ref int pos)
public override ulong GetULong(byte[] data, ref int pos)
{
var t = BitConverter.ToUInt64(data, pos);
pos += 8;
@@ -366,7 +677,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual float GetFloat(byte[] data, ref int pos)
public override float GetFloat(byte[] data, ref int pos)
{
var t = BitConverter.ToSingle(data, pos);
pos += 4;
@@ -379,7 +690,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的数字</returns>
public virtual double GetDouble(byte[] data, ref int pos)
public override double GetDouble(byte[] data, ref int pos)
{
var t = BitConverter.ToDouble(data, pos);
pos += 8;
@@ -394,7 +705,7 @@ namespace Modbus.Net
/// <param name="pos">转换数字的位置</param>
/// <param name="encoding">编码方法</param>
/// <returns>转换出的字符串</returns>
public virtual string GetString(byte[] data, int count, ref int pos, Encoding encoding)
public override string GetString(byte[] data, int count, ref int pos, Encoding encoding)
{
var t = encoding.GetString(data, pos, count);
pos += count;
@@ -407,7 +718,7 @@ namespace Modbus.Net
/// <param name="data">待转换的数组</param>
/// <param name="pos">转换数字的位置</param>
/// <returns>转换出的位数组</returns>
public virtual bool[] GetBits(byte[] data, ref int pos)
public override bool[] GetBits(byte[] data, ref int pos)
{
var t = new bool[8];
var temp = data[pos];
@@ -427,7 +738,7 @@ namespace Modbus.Net
/// <param name="pos">bit数组中的对应位置</param>
/// <param name="subPos">小数位</param>
/// <returns>对应位置的bit元素</returns>
public bool GetBit(byte number, ref int pos, ref int subPos)
public override bool GetBit(byte number, ref int pos, ref int subPos)
{
if (subPos < 0 && subPos >= 8) throw new IndexOutOfRangeException();
var ans = number % 2;
@@ -454,7 +765,7 @@ namespace Modbus.Net
/// <param name="pos">bit数组中的对应位置</param>
/// <param name="subPos">小数位</param>
/// <returns>对应位置的bit元素</returns>
public virtual bool GetBit(byte[] number, ref int pos, ref int subPos)
public override bool GetBit(byte[] number, ref int pos, ref int subPos)
{
return GetBit(number[pos], ref pos, ref subPos);
}
@@ -464,7 +775,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="originalByte">原始bit数组</param>
/// <returns>反转的bit数组</returns>
public virtual byte ReverseByte(byte originalByte)
public override byte ReverseByte(byte originalByte)
{
byte result = 0;
for (var i = 0; i < 8; i++)
@@ -481,7 +792,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="contents">object数组</param>
/// <returns>byte数组</returns>
public virtual byte[] ObjectArrayToByteArray(object[] contents)
public override byte[] ObjectArrayToByteArray(object[] contents)
{
var b = false;
//先查找传入的结构中有没有数组,有的话将其打开
@@ -604,7 +915,7 @@ namespace Modbus.Net
/// <param name="contents">byte数组</param>
/// <param name="translateTypeAndCount">单一的类型和需要转换的个数的键值对</param>
/// <returns>object数组</returns>
public virtual object[] ByteArrayToObjectArray(byte[] contents, KeyValuePair<Type, int> translateTypeAndCount)
public override object[] ByteArrayToObjectArray(byte[] contents, KeyValuePair<Type, int> translateTypeAndCount)
{
return ByteArrayToObjectArray(contents, new List<KeyValuePair<Type, int>> { translateTypeAndCount });
}
@@ -616,7 +927,7 @@ namespace Modbus.Net
/// <param name="contents">待转换的数组</param>
/// <param name="getCount">转换的个数</param>
/// <returns>以T为类型的数组</returns>
public virtual T[] ByteArrayToDestinationArray<T>(byte[] contents, int getCount)
public override T[] ByteArrayToDestinationArray<T>(byte[] contents, int getCount)
{
var objectArray = _Instance.ByteArrayToObjectArray(contents,
new KeyValuePair<Type, int>(typeof(T), getCount));
@@ -632,7 +943,7 @@ namespace Modbus.Net
/// 会转换出8个元素当然前提是byte数组足够长的时候5个int和3个short然后全部变为object类型返回。
/// </param>
/// <returns>object数组</returns>
public virtual object[] ByteArrayToObjectArray(byte[] contents,
public override object[] ByteArrayToObjectArray(byte[] contents,
IEnumerable<KeyValuePair<Type, int>> translateTypeAndCount)
{
var translation = new List<object>();
@@ -729,7 +1040,7 @@ namespace Modbus.Net
/// <typeparam name="T">需要转换的目标类型</typeparam>
/// <param name="contents">object数组</param>
/// <returns>目标数组</returns>
public T[] ObjectArrayToDestinationArray<T>(object[] contents)
public override T[] ObjectArrayToDestinationArray<T>(object[] contents)
{
var array = new T[contents.Length];
Array.Copy(contents, array, contents.Length);
@@ -744,7 +1055,7 @@ namespace Modbus.Net
/// <param name="subPos">设置的比特位位置仅setValue为bit的时候有效</param>
/// <param name="setValue">写入的值</param>
/// <returns>写入是否成功</returns>
public bool SetValue(byte[] contents, int setPos, int subPos, object setValue)
public override bool SetValue(byte[] contents, int setPos, int subPos, object setValue)
{
var type = setValue.GetType();
@@ -814,7 +1125,7 @@ namespace Modbus.Net
/// <param name="pos">设置的位置</param>
/// <param name="setValue">要设置的值</param>
/// <returns>设置是否成功</returns>
public virtual bool SetValue(byte[] contents, int pos, object setValue)
public override bool SetValue(byte[] contents, int pos, object setValue)
{
try
{
@@ -864,7 +1175,7 @@ namespace Modbus.Net
/// <param name="subPos">bit位置</param>
/// <param name="setValue">bit数</param>
/// <returns>设置是否成功</returns>
public virtual bool SetBit(byte[] contents, int pos, int subPos, bool setValue)
public override bool SetBit(byte[] contents, int pos, int subPos, bool setValue)
{
try
{
@@ -890,35 +1201,7 @@ namespace Modbus.Net
/// <summary>
/// ValueHelper单例的实例
/// </summary>
public static ValueHelper Instance => _instance ?? (_instance = new ValueHelper());
/// <summary>
/// 根据端格式获取ValueHelper实例
/// </summary>
/// <param name="endian">端格式</param>
/// <returns>对应的ValueHelper实例</returns>
public static ValueHelper GetInstance(Endian endian)
{
switch (endian)
{
case Endian.LittleEndianLsb:
{
return Instance;
}
case Endian.BigEndianLsb:
{
return BigEndianValueHelper.Instance;
}
case Endian.BigEndianMsb:
{
return BigEndianMsbValueHelper.Instance;
}
default:
{
return Instance;
}
}
}
public static ValueHelper Instance => _instance ?? (_instance = new LittleEndianLsbValueHelper());
#endregion
}
@@ -926,14 +1209,14 @@ namespace Modbus.Net
/// <summary>
/// 值与字节数组之间转换的辅助类大端格式这是一个Singleton类
/// </summary>
public class BigEndianValueHelper : ValueHelper
public class BigEndianLsbValueHelper : LittleEndianLsbValueHelper
{
private static BigEndianValueHelper _bigEndianInstance;
private static BigEndianLsbValueHelper _bigEndianInstance;
/// <summary>
/// 构造器
/// </summary>
protected BigEndianValueHelper()
protected BigEndianLsbValueHelper()
{
}
@@ -950,8 +1233,8 @@ namespace Modbus.Net
/// <summary>
/// 覆盖的获取实例的方法
/// </summary>
public new static BigEndianValueHelper Instance
=> _bigEndianInstance ?? (_bigEndianInstance = new BigEndianValueHelper());
public new static BigEndianLsbValueHelper Instance
=> _bigEndianInstance ?? (_bigEndianInstance = new BigEndianLsbValueHelper());
/// <summary>
/// 将short数字转换为byte数组
@@ -1169,9 +1452,9 @@ namespace Modbus.Net
/// <summary>
/// 值与字节数组之间转换的辅助类(大端-大端位格式这是一个Singleton类
/// </summary>
public class BigEndianMsbValueHelper : BigEndianValueHelper
public class BigEndianMsbValueHelper : BigEndianLsbValueHelper
{
private static BigEndianValueHelper _bigEndianInstance;
private static BigEndianMsbValueHelper _bigEndianInstance;
/// <summary>
/// 构造函数
@@ -1198,7 +1481,7 @@ namespace Modbus.Net
/// <summary>
/// 覆盖的实例获取方法
/// </summary>
public new static BigEndianValueHelper Instance
public new static BigEndianMsbValueHelper Instance
=> _bigEndianInstance ?? (_bigEndianInstance = new BigEndianMsbValueHelper());
/// <summary>

View File

@@ -7,6 +7,11 @@ namespace Modbus.Net
/// </summary>
public interface IController
{
/// <summary>
/// 消息维护线程是否在运行
/// </summary>
bool IsSending { get; }
/// <summary>
/// 增加信息
/// </summary>

View File

@@ -15,26 +15,12 @@ namespace Modbus.Net
/// </summary>
public interface IMachineMethodDatas : IMachineMethod
{
/// <summary>
/// 读取数据
/// </summary>
/// <returns>从设备读取的数据</returns>
ReturnStruct<Dictionary<string, ReturnUnit<double>>> GetDatas(MachineDataType getDataType);
/// <summary>
/// 读取数据
/// </summary>
/// <returns>从设备读取的数据</returns>
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>

View File

@@ -39,13 +39,6 @@ namespace Modbus.Net
/// <returns></returns>
bool Disconnect();
/// <summary>
/// 发送协议内容并接收,一般方法
/// </summary>
/// <param name="content">写入的内容,使用对象数组描述</param>
/// <returns>从设备获取的字节流</returns>
TPipeUnit SendReceive(params object[] content);
/// <summary>
/// 发送协议内容并接收,一般方法
/// </summary>
@@ -53,14 +46,6 @@ namespace Modbus.Net
/// <returns>从设备获取的字节流</returns>
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>
@@ -69,16 +54,6 @@ namespace Modbus.Net
/// <returns>输出信息的结构化描述</returns>
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>

View File

@@ -31,13 +31,6 @@ namespace Modbus.Net
/// <returns>设备是否断开成功</returns>
bool Disconnect();
/// <summary>
/// 发送并接收数据
/// </summary>
/// <param name="content">发送协议的内容</param>
/// <returns>接收协议的内容</returns>
TParamOut SendReceive(TParamIn content);
/// <summary>
/// 发送并接收数据
/// </summary>
@@ -45,13 +38,6 @@ namespace Modbus.Net
/// <returns>接收协议的内容</returns>
Task<TParamOut> SendReceiveAsync(TParamIn content);
/// <summary>
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
/// </summary>
/// <param name="content">发送协议的内容</param>
/// <returns>接收协议的内容</returns>
TParamOut SendReceiveWithoutExtAndDec(TParamIn content);
/// <summary>
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
/// </summary>
@@ -65,19 +51,5 @@ namespace Modbus.Net
/// <param name="content">接收协议的内容</param>
/// <returns>协议是否是正确的</returns>
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);
}
}

View File

@@ -16,14 +16,6 @@ namespace Modbus.Net
/// </summary>
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>
@@ -32,14 +24,6 @@ namespace Modbus.Net
/// <returns>接收到的byte数据</returns>
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>
@@ -48,15 +32,6 @@ namespace Modbus.Net
/// <returns>接收到的对应的类型和数据</returns>
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>
@@ -66,14 +41,6 @@ namespace Modbus.Net
/// <returns>接收到的对应的类型和数据</returns>
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>
@@ -81,14 +48,6 @@ namespace Modbus.Net
/// <param name="getTypeAndCountList">获取类型和个数的队列</param>
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>

View File

@@ -0,0 +1,59 @@
using Quartz;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Modbus.Net
{
/// <summary>
/// Repeated JobChaningJobListener
/// </summary>
public class JobChainingJobLIstenerWithDataMapRepeated : JobChainingJobListenerWithDataMap
{
/// <summary>
/// Job repeat count, -1 means infinity, 0 means 1 time.
/// </summary>
protected int RepeatCount { get; set; }
/// <summary>
/// JobChaningJobListener with DataMap passing from parent job to next job
/// </summary>
/// <param name="name">Job name</param>
/// <param name="overwriteKeys">If key is overwritable, parent job will pass the value to next job event next job contains that key</param>
/// <param name="repeatCount">Repeatation count for job chain</param>
public JobChainingJobLIstenerWithDataMapRepeated(string name, ICollection<string> overwriteKeys, int repeatCount) : base(name, overwriteKeys)
{
RepeatCount = repeatCount;
}
#nullable enable
/// <inheritdoc />
public override async Task JobWasExecuted(IJobExecutionContext context,
JobExecutionException? jobException,
CancellationToken cancellationToken = default)
{
await base.JobWasExecuted(context, jobException, cancellationToken);
if (RepeatCount == 0) return;
ChainLinks.TryGetValue(context.JobDetail.Key, out var sj);
if (sj == null)
{
var chainRoot = context.JobDetail.Key;
var chainParent = ChainLinks.FirstOrDefault(p => p.Value == context.JobDetail.Key).Key;
while (chainParent != null)
{
chainRoot = chainParent;
chainParent = ChainLinks.FirstOrDefault(p => p.Value == chainParent).Key;
}
if (RepeatCount > 0)
{
RepeatCount--;
}
var sjJobDetail = await context.Scheduler.GetJobDetail(chainRoot);
await context.Scheduler.AddJob(sjJobDetail!, true, false);
await context.Scheduler.TriggerJob(chainRoot, cancellationToken).ConfigureAwait(false);
}
}
#nullable disable
}
}

View File

@@ -23,10 +23,13 @@ namespace Modbus.Net
{
Name = name;
OverWriteKeys = overwriteKeys;
chainLinks = new Dictionary<JobKey, JobKey>();
ChainLinks = new Dictionary<JobKey, JobKey>();
}
private readonly Dictionary<JobKey, JobKey> chainLinks;
/// <summary>
/// Job chain links
/// </summary>
protected readonly Dictionary<JobKey, JobKey> ChainLinks;
/// <inheritdoc />
public override string Name { get; }
@@ -44,7 +47,7 @@ namespace Modbus.Net
/// <param name="secondJob">a JobKey with the name and group of the follow-up job</param>
public void AddJobChainLink(JobKey firstJob, JobKey secondJob)
{
chainLinks.Add(firstJob, secondJob);
ChainLinks.Add(firstJob, secondJob);
}
#nullable enable
@@ -53,7 +56,7 @@ namespace Modbus.Net
JobExecutionException? jobException,
CancellationToken cancellationToken = default)
{
chainLinks.TryGetValue(context.JobDetail.Key, out var sj);
ChainLinks.TryGetValue(context.JobDetail.Key, out var sj);
if (sj == null)
{

View File

@@ -37,7 +37,7 @@ namespace Modbus.Net
/// <returns></returns>
public static async Task<MachineGetJobScheduler<TMachineMethod, TMachineKey, TReturnUnit>> CreateScheduler(string triggerKey, int count = 0, int intervalSecond = 1)
{
return await CreateScheduler(triggerKey, count, (double)intervalSecond);
return await CreateSchedulerMillisecond(triggerKey, count, intervalSecond * 1000);
}
/// <summary>
@@ -48,29 +48,39 @@ namespace Modbus.Net
/// <param name="intervalMilliSecond">间隔毫秒数</param>
/// <returns></returns>
public static async Task<MachineGetJobScheduler<TMachineMethod, TMachineKey, TReturnUnit>> CreateSchedulerMillisecond(string triggerKey, int count = 0, int intervalMilliSecond = 1000)
{
return await CreateScheduler(triggerKey, count, intervalMilliSecond / 1000.0);
}
private static async Task<MachineGetJobScheduler<TMachineMethod, TMachineKey, TReturnUnit>> CreateScheduler(string triggerKey, int count = 0, double interval = 1)
{
IScheduler scheduler = await StdSchedulerFactory.GetDefaultScheduler();
ITrigger trigger;
if (count >= 0)
if (intervalMilliSecond <= 0)
{
trigger = TriggerBuilder.Create()
.WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey)
.StartNow()
.WithSimpleSchedule(b => b.WithInterval(TimeSpan.FromSeconds(interval)).WithRepeatCount(count))
.Build();
}
else if (count >= 0)
trigger = TriggerBuilder.Create()
.WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey)
.StartNow()
.WithSimpleSchedule(b => b.WithInterval(TimeSpan.FromMilliseconds(intervalMilliSecond)).WithRepeatCount(count))
.Build();
else
trigger = TriggerBuilder.Create()
.WithIdentity(triggerKey, "Modbus.Net.DataQuery.Group." + triggerKey)
.StartNow()
.WithSimpleSchedule(b => b.WithInterval(TimeSpan.FromSeconds(interval)).RepeatForever())
.WithSimpleSchedule(b => b.WithInterval(TimeSpan.FromMilliseconds(intervalMilliSecond)).RepeatForever())
.Build();
var listener = new JobChainingJobListenerWithDataMap("Modbus.Net.DataQuery.Chain." + triggerKey, new string[2] { "Value", "SetValue" });
IJobListener listener;
if (intervalMilliSecond <= 0)
{
listener = new JobChainingJobLIstenerWithDataMapRepeated("Modbus.Net.DataQuery.Chain." + triggerKey, new string[2] { "Value", "SetValue" }, count);
}
else
{
listener = new JobChainingJobListenerWithDataMap("Modbus.Net.DataQuery.Chain." + triggerKey, new string[2] { "Value", "SetValue" });
}
scheduler.ListenerManager.AddJobListener(listener, GroupMatcher<JobKey>.GroupEquals("Modbus.Net.DataQuery.Group." + triggerKey));
if (await scheduler.GetTrigger(new TriggerKey(triggerKey)) != null)
@@ -344,9 +354,9 @@ namespace Modbus.Net
/// 执行任务
/// </summary>
/// <returns></returns>
public async Task Run()
public Task Run()
{
await _scheduler.Start();
return _scheduler.Start();
}
}

View File

@@ -28,7 +28,10 @@ namespace Modbus.Net
{
Task.Factory.StartNew(async () =>
{
Thread.Sleep((int)(intervalSecond * 1000.0 / _machineCount * index));
if (intervalSecond > 0)
{
Thread.Sleep((int)(intervalSecond * 1000.0 / _machineCount * index));
}
var getJobScheduler = await MachineJobSchedulerCreator<TMachineMethod, TMachineKey, TReturnUnit>.CreateScheduler("Trigger" + index, count, intervalSecond);
await machineJobTemplate(machine, getJobScheduler);
});

View File

@@ -1,48 +1,8 @@
using FastEnumUtility;
using System;
using System.IO.Ports;
namespace Modbus.Net
{
/// <summary>
/// 波特率
/// </summary>
public enum BaudRate
{
#pragma warning disable
BaudRate75 = 75,
BaudRate110 = 110,
BaudRate134 = 134,
BaudRate150 = 150,
BaudRate300 = 300,
BaudRate600 = 600,
BaudRate1200 = 1200,
BaudRate1800 = 1800,
BaudRate2400 = 2400,
BaudRate4800 = 4800,
BaudRate9600 = 9600,
BaudRate14400 = 14400,
BaudRate19200 = 19200,
BaudRate38400 = 38400,
BaudRate57600 = 57600,
BaudRate115200 = 115200,
BaudRate128000 = 128000,
BaudRate230400 = 230400,
BaudRate460800 = 460800,
BaudRate921600 = 921600,
#pragma warning restore
}
/// <summary>
/// 数据位
/// </summary>
public enum DataBits
{
#pragma warning disable
Seven = 7,
Eight = 8,
#pragma warning restore
}
/// <summary>
/// 串口连接对象
/// </summary>
@@ -63,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,
int? connectionTimeout = null, bool? isFullDuplex = null)
{
baudRate = FastEnum.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"));
stopBits = FastEnum.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"));
handshake = FastEnum.Parse<Handshake>(handshake != null ? handshake.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Handshake"));
baudRate = Enum.Parse<BaudRate>(baudRate != null ? baudRate.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "BaudRate"));
parity = Enum.Parse<Parity>(parity != null ? parity.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "Parity"));
stopBits = Enum.Parse<StopBits>(stopBits != null ? stopBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "StopBits"));
dataBits = Enum.Parse<DataBits>(dataBits != null ? dataBits.ToString() : null ?? ConfigurationReader.GetValue("COM:" + com, "DataBits"));
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"));
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);

View File

@@ -41,7 +41,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="content">扩展前的基本协议内容</param>
/// <returns>扩展后的协议内容</returns>
public override byte[] BytesExtend(byte[] content)
public virtual byte[] BytesExtend(byte[] content)
{
//自动查找相应的协议放缩类,命令规则为——当前的实际类名(注意是继承后的)+"BytesExtend"。
var bytesExtend =
@@ -56,7 +56,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="content">缩减前的完整协议内容</param>
/// <returns>缩减后的协议内容</returns>
public override byte[] BytesDecact(byte[] content)
public virtual byte[] BytesDecact(byte[] content)
{
//自动查找相应的协议放缩类,命令规则为——当前的实际类名(注意是继承后的)+"BytesExtend"。
var bytesExtend =
@@ -122,16 +122,6 @@ namespace Modbus.Net
/// </summary>
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>
@@ -139,19 +129,8 @@ namespace Modbus.Net
/// <returns>接收协议的内容</returns>
public virtual async Task<TParamOut> SendReceiveAsync(TParamIn content)
{
var extBytes = BytesExtend(content);
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(extBytes);
return BytesDecact(receiveBytes);
}
/// <summary>
/// 发送并接收数据,不进行协议扩展和收缩,用于特殊协议
/// </summary>
/// <param name="content">发送协议的内容</param>
/// <returns>接收协议的内容</returns>
public virtual TParamOut SendReceiveWithoutExtAndDec(TParamIn content)
{
return AsyncHelper.RunSync(() => SendReceiveWithoutExtAndDecAsync(content));
var receiveBytes = await SendReceiveWithoutExtAndDecAsync(content);
return receiveBytes;
}
/// <summary>
@@ -180,25 +159,5 @@ namespace Modbus.Net
Disconnect();
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();
}
}
}

View File

@@ -7,20 +7,20 @@ namespace Modbus.Net
/// <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>
/// <param name="addresses">需要进行组合的地址</param>
/// <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>
public class AddressCombinerContinus<TKey> : AddressCombiner<TKey> where TKey : IEquatable<TKey>
public class AddressCombinerContinus<TKey> : AddressCombiner<TKey, int, int> where TKey : IEquatable<TKey>
{
/// <summary>
/// 构造函数
@@ -48,7 +48,7 @@ namespace Modbus.Net
/// </summary>
/// <param name="addresses">需要组合的地址</param>
/// <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
@@ -58,7 +58,7 @@ namespace Modbus.Net
group address by address.Area
into grouped
select grouped;
var ans = new List<CommunicationUnit<TKey>>();
var ans = new List<CommunicationUnit<TKey, int, int>>();
foreach (var groupedAddress in groupedAddresses)
{
var area = groupedAddress.Key;
@@ -69,7 +69,7 @@ namespace Modbus.Net
//上一个地址类型
Type preType = null;
//记录一个地址组合当中的所有原始地址
var originalAddresses = new List<AddressUnit<TKey>>();
var originalAddresses = new List<AddressUnit<TKey, int, int>>();
//对组合内地址从小到大进行排序
var orderedAddresses =
groupedAddress.OrderBy(
@@ -114,7 +114,7 @@ namespace Modbus.Net
AddressTranslator.GetAreaByteLength(address.Area)))
{
//上一个地址域压入返回结果,并把当前记录的结果清空。
ans.Add(new CommunicationUnit<TKey>
ans.Add(new CommunicationUnit<TKey, int, int>
{
Area = area,
Address = (int)Math.Floor(initNum),
@@ -124,7 +124,7 @@ namespace Modbus.Net
AddressHelper.MapProtocolGetCountToAbstractByteCount(
preNum - (int)Math.Floor(initNum),
AddressTranslator.GetAreaByteLength(address.Area),
BigEndianValueHelper.Instance.ByteLength[preType.FullName])),
ValueHelper.ByteLength[preType.FullName])),
DataType = typeof(byte),
OriginalAddresses = originalAddresses.ToList()
});
@@ -144,7 +144,7 @@ namespace Modbus.Net
preType = address.DataType;
}
//最后一个地址域压入返回结果
ans.Add(new CommunicationUnit<TKey>
ans.Add(new CommunicationUnit<TKey, int, int>
{
Area = area,
Address = (int)Math.Floor(initNum),
@@ -153,7 +153,7 @@ namespace Modbus.Net
Math.Ceiling(
AddressHelper.MapProtocolGetCountToAbstractByteCount(
preNum - (int)Math.Floor(initNum), AddressTranslator.GetAreaByteLength(area),
BigEndianValueHelper.Instance.ByteLength[preType.FullName])),
ValueHelper.ByteLength[preType.FullName])),
DataType = typeof(byte),
OriginalAddresses = originalAddresses.ToList()
});
@@ -167,30 +167,30 @@ namespace Modbus.Net
/// </summary>
/// <param name="ans">拆分前的连续地址池</param>
/// <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)
{
var oldByteCount = communicationUnit.GetCount *
BigEndianValueHelper.Instance.ByteLength[communicationUnit.DataType.FullName];
ValueHelper.ByteLength[communicationUnit.DataType.FullName];
var oldOriginalAddresses = communicationUnit.OriginalAddresses.ToList();
while (oldByteCount * BigEndianValueHelper.Instance.ByteLength[communicationUnit.DataType.FullName] >
while (oldByteCount * ValueHelper.ByteLength[communicationUnit.DataType.FullName] >
MaxLength)
{
var newOriginalAddresses = new List<AddressUnit<TKey>>();
var newOriginalAddresses = new List<AddressUnit<TKey, int, int>>();
var newByteCount = 0.0;
var newAddressUnitStart = oldOriginalAddresses.First();
do
{
var currentAddressUnit = oldOriginalAddresses.First();
newByteCount += BigEndianValueHelper.Instance.ByteLength[currentAddressUnit.DataType.FullName];
newByteCount += ValueHelper.ByteLength[currentAddressUnit.DataType.FullName];
if (newByteCount > MaxLength) break;
oldByteCount -= BigEndianValueHelper.Instance.ByteLength[currentAddressUnit.DataType.FullName];
oldByteCount -= ValueHelper.ByteLength[currentAddressUnit.DataType.FullName];
newOriginalAddresses.Add(currentAddressUnit);
oldOriginalAddresses.RemoveAt(0);
} while (newByteCount < MaxLength);
var newCommunicationUnit = new CommunicationUnit<TKey>
var newCommunicationUnit = new CommunicationUnit<TKey, int, int>
{
Area = newAddressUnitStart.Area,
Address = newAddressUnitStart.Address,
@@ -199,7 +199,7 @@ namespace Modbus.Net
GetCount =
(int)
Math.Ceiling(newByteCount /
BigEndianValueHelper.Instance.ByteLength[communicationUnit.DataType.FullName]),
ValueHelper.ByteLength[communicationUnit.DataType.FullName]),
OriginalAddresses = newOriginalAddresses
};
@@ -214,7 +214,7 @@ namespace Modbus.Net
communicationUnit.GetCount =
(int)
Math.Ceiling(oldByteCount /
BigEndianValueHelper.Instance.ByteLength[communicationUnit.DataType.FullName]);
ValueHelper.ByteLength[communicationUnit.DataType.FullName]);
communicationUnit.OriginalAddresses = oldOriginalAddresses;
newAns.Add(communicationUnit);
}
@@ -225,26 +225,26 @@ namespace Modbus.Net
/// <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>
/// <param name="addresses">需要组合的地址</param>
/// <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
addresses.Select(
address =>
new CommunicationUnit<TKey>
new CommunicationUnit<TKey, TAddressKey, TSubAddressKey>
{
Area = address.Area,
Address = address.Address,
SubAddress = address.SubAddress,
DataType = address.DataType,
GetCount = 1,
OriginalAddresses = new List<AddressUnit<TKey>> { address }
OriginalAddresses = new List<AddressUnit<TKey, TAddressKey, TSubAddressKey>> { address }
}).ToList();
}
}
@@ -254,7 +254,7 @@ namespace Modbus.Net
/// </summary>
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; }
}
@@ -285,11 +285,11 @@ namespace Modbus.Net
/// </summary>
/// <param name="addresses">需要组合的地址</param>
/// <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 addressesGaps = new List<CommunicationUnitGap<TKey>>();
CommunicationUnit<TKey> preCommunicationUnit = null;
CommunicationUnit<TKey, int, int> preCommunicationUnit = null;
foreach (var continusAddress in continusAddresses)
{
if (preCommunicationUnit == null)
@@ -309,7 +309,7 @@ namespace Modbus.Net
continusAddress.Address, preCommunicationUnit.Address,
AddressTranslator.GetAreaByteLength(continusAddress.Area)) -
preCommunicationUnit.GetCount *
BigEndianValueHelper.Instance.ByteLength[
ValueHelper.ByteLength[
preCommunicationUnit.DataType.FullName])
};
addressesGaps.Add(gap);
@@ -327,24 +327,24 @@ namespace Modbus.Net
nowAddress = continusAddresses[index];
index--;
var preAddress = continusAddresses[index];
if (nowAddress.GetCount * BigEndianValueHelper.Instance.ByteLength[nowAddress.DataType.FullName] +
preAddress.GetCount * BigEndianValueHelper.Instance.ByteLength[preAddress.DataType.FullName] +
if (nowAddress.GetCount * ValueHelper.ByteLength[nowAddress.DataType.FullName] +
preAddress.GetCount * ValueHelper.ByteLength[preAddress.DataType.FullName] +
orderedGap.GapNumber > MaxLength) continue;
jumpNumberInner -= orderedGap.GapNumber;
if (jumpNumberInner < 0) break;
continusAddresses.RemoveAt(index);
continusAddresses.RemoveAt(index);
//合并两个已有的地址段,变为一个新的地址段
var newAddress = new CommunicationUnit<TKey>
var newAddress = new CommunicationUnit<TKey, int, int>
{
Area = nowAddress.Area,
Address = preAddress.Address,
GetCount =
(int)
(preAddress.GetCount * BigEndianValueHelper.Instance.ByteLength[preAddress.DataType.FullName]) +
(preAddress.GetCount * ValueHelper.ByteLength[preAddress.DataType.FullName]) +
orderedGap.GapNumber +
(int)
(nowAddress.GetCount * BigEndianValueHelper.Instance.ByteLength[nowAddress.DataType.FullName]),
(nowAddress.GetCount * ValueHelper.ByteLength[nowAddress.DataType.FullName]),
DataType = typeof(byte),
OriginalAddresses = preAddress.OriginalAddresses.ToList().Union(nowAddress.OriginalAddresses)
};
@@ -382,10 +382,10 @@ namespace Modbus.Net
/// </summary>
/// <param name="addresses">需要组合的地址</param>
/// <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 count = addressUnits.Sum(address => BigEndianValueHelper.Instance.ByteLength[address.DataType.FullName]);
var addressUnits = addresses as IList<AddressUnit<TKey, int, int>> ?? addresses.ToList();
var count = addressUnits.Sum(address => ValueHelper.ByteLength[address.DataType.FullName]);
return
new AddressCombinerNumericJump<TKey>((int)(count * Percentage / 100.0), MaxLength, AddressTranslator)
.Combine(

View File

@@ -1,9 +1,11 @@
namespace Modbus.Net
using System;
namespace Modbus.Net
{
/// <summary>
/// 地址编码器
/// </summary>
public abstract class AddressFormater
public abstract class AddressFormater<TAddressKey, TSubAddressKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
{
/// <summary>
/// 编码地址
@@ -11,7 +13,7 @@
/// <param name="area">地址所在的数据区域</param>
/// <param name="address">地址</param>
/// <returns>编码后的地址</returns>
public abstract string FormatAddress(string area, int address);
public abstract string FormatAddress(string area, TAddressKey address);
/// <summary>
/// 编码地址
@@ -20,13 +22,13 @@
/// <param name="address">地址</param>
/// <param name="subAddress">子地址</param>
/// <returns>编码后的地址</returns>
public abstract string FormatAddress(string area, int address, int subAddress);
public abstract string FormatAddress(string area, TAddressKey address, TSubAddressKey subAddress);
}
/// <summary>
/// 基本的地址编码器
/// </summary>
public class AddressFormaterBase : AddressFormater
public class AddressFormaterBase : AddressFormater<int, int>
{
/// <summary>
/// 编码地址

View File

@@ -80,7 +80,7 @@ namespace Modbus.Net
double byteLength)
{
return protocolAddress +
BigEndianValueHelper.Instance.ByteLength[nextPositionBetweenType.FullName] / byteLength;
ValueHelper.ByteLength[nextPositionBetweenType.FullName] / byteLength;
}
/// <summary>
@@ -92,7 +92,7 @@ namespace Modbus.Net
public static double GetAbstractCoordinateNextPosition(double abstractAddress, Type nextPositionBetweenType)
{
return abstractAddress +
BigEndianValueHelper.Instance.ByteLength[nextPositionBetweenType.FullName];
ValueHelper.ByteLength[nextPositionBetweenType.FullName];
}
}
}

View File

@@ -6,68 +6,17 @@ using System.Threading.Tasks;
namespace Modbus.Net
{
/// <summary>
/// 读写设备值的方式
/// </summary>
public enum MachineDataType
{
/// <summary>
/// 地址
/// </summary>
Address,
/// <summary>
/// 通讯标识
/// </summary>
CommunicationTag,
/// <summary>
/// 名称
/// </summary>
Name,
/// <summary>
/// Id
/// </summary>
Id
}
/// <summary>
/// 设备
/// </summary>
/// <typeparam name="TKey">设备的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>
where TKey : IEquatable<TKey>
where TUnitKey : IEquatable<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>
@@ -76,70 +25,18 @@ namespace Modbus.Net
/// <param name="keepConnect">是否保持连接</param>
/// <param name="slaveAddress">从站地址</param>
/// <param name="masterAddress">主站地址</param>
protected BaseMachine(TKey id, IEnumerable<AddressUnit<TUnitKey>> getAddresses, bool keepConnect, byte slaveAddress,
byte masterAddress) : this(id, getAddresses, keepConnect)
protected BaseMachine(TKey id, IEnumerable<AddressUnit<TUnitKey, int, int>> getAddresses, bool keepConnect, byte slaveAddress,
byte masterAddress) : base(id, getAddresses, keepConnect)
{
SlaveAddress = slaveAddress;
MasterAddress = masterAddress;
}
private readonly int _maxErrorCount = 3;
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>
@@ -151,20 +48,26 @@ namespace Modbus.Net
public byte MasterAddress { get; set; }
/// <summary>
/// 读取数据
/// 与设备实际通讯的连续地址
/// </summary>
/// <returns>从设备读取的数据</returns>
public ReturnStruct<Dictionary<string, ReturnUnit<double>>> GetDatas(MachineDataType getDataType)
{
return AsyncHelper.RunSync(() => GetDatasAsync(getDataType));
}
protected IEnumerable<CommunicationUnit<TUnitKey, int, int>> CommunicateAddresses
=> GetAddresses != null ? AddressCombiner.Combine(GetAddresses) : null;
/// <summary>
/// 获取地址组合器
/// </summary>
public AddressCombiner<TUnitKey, int, int> AddressCombiner { get; set; }
/// <summary>
/// 写入地址组合器
/// </summary>
public AddressCombiner<TUnitKey, int, int> AddressCombinerSet { get; set; }
/// <summary>
/// 读取数据
/// </summary>
/// <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
{
@@ -192,7 +95,7 @@ namespace Modbus.Net
communicateAddress.SubAddress),
(int)
Math.Ceiling(communicateAddress.GetCount *
BigEndianValueHelper.Instance.ByteLength[
ValueHelper.ByteLength[
communicateAddress.DataType.FullName]));
@@ -210,7 +113,7 @@ namespace Modbus.Net
else if (datas.Datas.Length != 0 && datas.Datas.Length <
(int)
Math.Ceiling(communicateAddress.GetCount *
BigEndianValueHelper.Instance.ByteLength[
ValueHelper.ByteLength[
communicateAddress.DataType.FullName]))
{
return new ReturnStruct<Dictionary<string, ReturnUnit<double>>>()
@@ -342,18 +245,7 @@ namespace Modbus.Net
/// <param name="setDataType">写入类型</param>
/// <param name="values">需要写入的数据字典当写入类型为Address时键为需要写入的地址当写入类型为CommunicationTag时键为需要写入的单元的描述</param>
/// <returns>是否写入成功</returns>
public ReturnStruct<bool> SetDatas(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)
public async override Task<ReturnStruct<bool>> SetDatasAsync(MachineDataType setDataType, Dictionary<string, double> values)
{
try
{
@@ -368,12 +260,12 @@ namespace Modbus.Net
ErrorCode = -1,
ErrorMsg = "Connection Error"
};
var addresses = new List<AddressUnit<TUnitKey>>();
var addresses = new List<AddressUnit<TUnitKey, int, int>>();
//遍历每个要设置的值
foreach (var value in values)
{
//根据设置类型找到对应的地址描述
AddressUnit<TUnitKey> address = null;
AddressUnit<TUnitKey, int, int> address = null;
switch (setDataType)
{
case MachineDataType.Address:
@@ -437,7 +329,7 @@ namespace Modbus.Net
AddressFormater.FormatAddress(communicateAddress.Area, communicateAddress.Address, 0),
(int)
Math.Ceiling(communicateAddress.GetCount *
BigEndianValueHelper.Instance.ByteLength[
ValueHelper.ByteLength[
communicateAddress.DataType.FullName]));
var valueHelper = ValueHelper.GetInstance(BaseUtility.Endian);
@@ -458,7 +350,7 @@ namespace Modbus.Net
else if (datas.Datas.Length <
(int)
Math.Ceiling(communicateAddress.GetCount *
BigEndianValueHelper.Instance.ByteLength[
ValueHelper.ByteLength[
communicateAddress.DataType.FullName]))
return new ReturnStruct<bool>()
{
@@ -576,6 +468,49 @@ namespace Modbus.Net
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>
/// 是否处于连接状态
@@ -612,6 +547,383 @@ namespace Modbus.Net
/// </summary>
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>
@@ -653,24 +965,6 @@ namespace Modbus.Net
{
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>>
@@ -690,7 +984,7 @@ namespace Modbus.Net
/// <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>
/// 区域
@@ -700,12 +994,12 @@ namespace Modbus.Net
/// <summary>
/// 地址
/// </summary>
public int Address { get; set; }
public TAddressKey Address { get; set; }
/// <summary>
/// 子地址
/// </summary>
public int SubAddress { get; set; } = 0;
public TSubAddressKey SubAddress { get; set; }
/// <summary>
/// 获取个数
@@ -720,7 +1014,7 @@ namespace Modbus.Net
/// <summary>
/// 原始的地址
/// </summary>
public IEnumerable<AddressUnit<TKey>> OriginalAddresses { get; set; }
public IEnumerable<AddressUnit<TKey, TAddressKey, TSubAddressKey>> OriginalAddresses { get; set; }
}
/// <summary>
@@ -736,20 +1030,13 @@ namespace Modbus.Net
/// <summary>
/// 数据定义
/// </summary>
public AddressUnit AddressUnit { get; set; }
public AddressUnit<string, string, string> AddressUnit { get; set; }
}
/// <summary>
/// 地址单元
/// </summary>
public class AddressUnit : AddressUnit<string>
{
}
/// <summary>
/// 地址单元
/// </summary>
public class AddressUnit<TKey> : IEquatable<AddressUnit<TKey>> where TKey : IEquatable<TKey>
public class AddressUnit<TKey, TAddressKey, TSubAddressKey> : IEquatable<AddressUnit<TKey, TAddressKey, TSubAddressKey>> where TKey : IEquatable<TKey> where TAddressKey : IEquatable<TAddressKey> where TSubAddressKey : IEquatable<TSubAddressKey>
{
/// <summary>
/// 数据单元Id
@@ -764,12 +1051,12 @@ namespace Modbus.Net
/// <summary>
/// 地址
/// </summary>
public int Address { get; set; }
public TAddressKey Address { get; set; }
/// <summary>
/// bit位地址
/// </summary>
public int SubAddress { get; set; } = 0;
public TSubAddressKey SubAddress { get; set; }
/// <summary>
/// 数据类型
@@ -811,9 +1098,9 @@ namespace Modbus.Net
/// </summary>
/// <param name="other">另一个地址</param>
/// <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);
}
}
}

View File

@@ -35,14 +35,14 @@ namespace Modbus.Net
/// </summary>
/// <param name="addressUnit"></param>
/// <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(),
Area = addressUnit.Area,
Address = addressUnit.Address,
SubAddress = addressUnit.SubAddress,
Address = addressUnit.Address?.ToString(),
SubAddress = addressUnit.SubAddress?.ToString(),
DataType = addressUnit.DataType,
Zoom = addressUnit.Zoom,
DecimalPos = addressUnit.DecimalPos,

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<AssemblyName>Modbus.Net</AssemblyName>
<RootNamespace>Modbus.Net</RootNamespace>
<PackageId>Modbus.Net</PackageId>
<Version>1.4.1</Version>
<Version>1.4.2</Version>
<Product>Modbus.Net</Product>
<Authors>Chris L.(Luo Sheng)</Authors>
<Company>Hangzhou Delian Science Technology Co.,Ltd.</Company>
@@ -37,7 +37,6 @@
<ItemGroup>
<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.Json" Version="7.0.0" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />

View File

@@ -146,18 +146,6 @@ namespace Modbus.Net
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>
@@ -177,16 +165,6 @@ namespace Modbus.Net
return null;
}
/// <summary>
/// 发送协议内容并接收,一般方法
/// </summary>
/// <param name="content">写入的内容,使用对象数组描述</param>
/// <returns>从设备获取的字节流</returns>
public virtual TPipeUnit SendReceive(params object[] content)
{
return AsyncHelper.RunSync(() => SendReceiveAsync(content));
}
/// <summary>
/// 发送协议内容并接收,一般方法(不能使用,如需使用请继承)
/// </summary>
@@ -197,18 +175,6 @@ namespace Modbus.Net
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>

View File

@@ -288,7 +288,8 @@ But after ":", property should match constructor except protocol, which refer to
The main target of Modbus.Net is building a high extensable hardware communication protocol, so we allow everyone to extend the protocol.
To extend Modbus.Net, first of all ValueHelper.cs in Modbus.Net is a really powerful tool that you can use to modify values in byte array.There are two ValueHelpers: ValueHelper(Little Endian) and BigEndianValueHelper(Big Endian). Remember using the correct one.
To extend Modbus.Net, first of all ValueHelper.cs in Modbus.Net is a really powerful tool that you can use to modify values in byte array.There are three ValueHelpers: LittleEndianLsbValueHelper(Little Endian), BigEndianLsbValueHelper(Big Endian) and BigEndianMsbValueHelper(Big Endian with bit reverse). Remember using the correct one.<br>
If you want to write a new one, just write in namespace "Modbus.Net" in assmenly start with "Modbus.Net." and Modbus.Net will automatically load it.
In this tutorial I will use Modbus.Net.Modbus to tell you how to implement your own protocol.
@@ -314,9 +315,9 @@ public class ReadDataModbusProtocol : ProtocolUnit
public override IOutputStruct Unformat(byte[] messageBytes, ref int pos)
{
byte slaveAddress = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
byte functionCode = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
byte dataCount = BigEndianValueHelper.Instance.GetByte(messageBytes, ref pos);
byte slaveAddress = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
byte functionCode = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
byte dataCount = BigEndianLsbValueHelper.Instance.GetByte(messageBytes, ref pos);
byte[] dataValue = new byte[dataCount];
Array.Copy(messageBytes, 3, dataValue, 0, dataCount);
return new ReadDataModbusOutputStruct(slaveAddress, functionCode, dataCount, dataValue);
@@ -368,8 +369,8 @@ public class ModbusTcpProtocolLinkerBytesExtend : ProtocolLinkerBytesExtend
byte[] newFormat = new byte[6 + content.Length];
int tag = 0;
ushort leng = (ushort)content.Length;
Array.Copy(BigEndianValueHelper.Instance.GetBytes(tag), 0, newFormat, 0, 4);
Array.Copy(BigEndianValueHelper.Instance.GetBytes(leng), 0, newFormat, 4, 2);
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(tag), 0, newFormat, 0, 4);
Array.Copy(BigEndianLsbValueHelper.Instance.GetBytes(leng), 0, newFormat, 4, 2);
Array.Copy(content, 0, newFormat, 6, content.Length);
return newFormat;
}

View File

@@ -4,27 +4,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
/// <summary>
/// 端格式
/// </summary>
public enum Endian
{
/// <summary>
/// 小端
/// </summary>
LittleEndianLsb,
/// <summary>
/// 大端-小端位
/// </summary>
BigEndianLsb,
/// <summary>
/// 大端-大端位
/// </summary>
BigEndianMsb
}
namespace Modbus.Net
{
/// <summary>
@@ -66,17 +45,6 @@ namespace Modbus.Net
/// </summary>
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>
@@ -85,18 +53,6 @@ namespace Modbus.Net
/// <returns>接收到的byte数据</returns>
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>
@@ -109,7 +65,7 @@ namespace Modbus.Net
try
{
var typeName = getTypeAndCount.Key.FullName;
var bCount = BigEndianValueHelper.Instance.ByteLength[typeName];
var bCount = ValueHelper.ByteLength[typeName];
var getReturnValue = await GetDatasAsync(startAddress,
(int)Math.Ceiling(bCount * getTypeAndCount.Value));
var getBytes = getReturnValue;
@@ -144,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>
@@ -202,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>
@@ -230,7 +160,7 @@ namespace Modbus.Net
var bAllCount = (
from getTypeAndCount in translateTypeAndCount
let typeName = getTypeAndCount.Key.FullName
let bCount = BigEndianValueHelper.Instance.ByteLength[typeName]
let bCount = ValueHelper.ByteLength[typeName]
select (int)Math.Ceiling(bCount * getTypeAndCount.Value)).Sum();
var getReturnValue = await GetDatasAsync(startAddress, bAllCount);
var getBytes = getReturnValue;
@@ -265,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>

View File

@@ -3,7 +3,7 @@
"TCP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"
@@ -16,7 +16,7 @@
"UDP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"

View File

@@ -31,3 +31,4 @@ Thanks
* Quartz - Job Scheduler
* Serilog - Logging
* DotNetty - Network Transporting
* h-opc & Technosoftware.DaAeHdaSolution & OPCFoundation.NetStandard - OPC Trasporting

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc;
using Modbus.Net;
using Modbus.Net.Modbus;
using System.Diagnostics;
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
using MachineJobSchedulerCreator = Modbus.Net.MachineJobSchedulerCreator<Modbus.Net.IMachineMethodDatas, string, double>;
using ModbusMachine = Modbus.Net.Modbus.ModbusMachine<string, string>;
@@ -55,7 +56,7 @@ namespace AnyType.Controllers
Value = 0,
Type = unitValue.DataType.Name
};
var machine = new ModbusMachine("1", ModbusType.Tcp, "10.10.18.251:502", addressUnits, true, 2, 0);
var machine = new ModbusMachine("1", ModbusType.Tcp, "10.10.18.251:502", addressUnits, true, 2, 0, Endian.BigEndianLsb);
//启动任务
await MachineJobSchedulerCreator.CreateScheduler("Trigger1", -1, 1).Result.From(machine.Id, machine, MachineDataType.CommunicationTag).Result.Query("Query1",
returnValues =>

View File

@@ -3,7 +3,7 @@
"TCP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"
@@ -16,7 +16,7 @@
"UDP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"

View File

@@ -30,12 +30,12 @@ namespace CrossLamp.Controllers
{
if (_utility == null)
{
_utility = new ModbusUtility(ModbusType.Tcp, "10.10.18.251", 2, 0);
_utility = new ModbusUtility(ModbusType.Tcp, "10.10.18.251", 2, 0, Endian.BigEndianLsb);
await _utility.ConnectAsync();
}
Lamp light = new Lamp();
object[] lampsbyte = (await _utility.GetDatasAsync("0X 1", new KeyValuePair<Type, int>(typeof(bool), 7))).Datas;
bool[] lamps = BigEndianValueHelper.Instance.ObjectArrayToDestinationArray<bool>(lampsbyte);
bool[] lamps = BigEndianLsbValueHelper.Instance.ObjectArrayToDestinationArray<bool>(lampsbyte);
if (lamps[0])
{
light.MainLamp = LightLamp.Red.ToString();

View File

@@ -3,7 +3,7 @@
"TCP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"
@@ -16,7 +16,7 @@
"UDP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"

View File

@@ -5,11 +5,17 @@ namespace MachineJob
// simple log provider to get something to the console
public class ConsoleLogProvider : ILogProvider
{
private readonly IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", true)
.Build();
public Logger GetLogger(string name)
{
return (level, func, exception, parameters) =>
{
if (func != null)
if (level >= configuration.GetSection("Quartz").GetValue<Quartz.Logging.LogLevel>("LogLevel") && func != null)
{
Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters);
}

View File

@@ -1,6 +1,6 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MachineJob
{
@@ -15,7 +15,7 @@ namespace MachineJob
private static readonly string connectionString = configuration.GetConnectionString("DatabaseWriteConnectionString")!;
public DbSet<DatabaseWriteEntity> DatabaseWrites { get; set; }
public DbSet<DatabaseWriteEntity>? DatabaseWrites { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
@@ -23,6 +23,7 @@ namespace MachineJob
}
}
[Table(name: "databasewrites")]
public class DatabaseWriteEntity
{
[Key]

View File

@@ -23,6 +23,7 @@
<ItemGroup>
<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\Modbus.Net.csproj" />
</ItemGroup>

View File

@@ -61,10 +61,16 @@ namespace MachineJob.Service
//}
//4. 使用MultipleMachinesJobScheduler
//return Task.Run(() => MultipleMachinesJobScheduler.RunScheduler(machines, async (machine, scheduler) =>
//{
// /await scheduler.From(machine.Id + ".From", machine, MachineDataType.Name).Result.Query(machine.Id + ".ConsoleQuery", QueryConsole).Result.To(machine.Id + ".To", machine).Result.Deal(machine.Id + ".Deal", OnSuccess, OnFailure).Result.Run();
//}, -1, 10));
//5. 不设置固定时间连续触发Job
return Task.Run(() => MultipleMachinesJobScheduler.RunScheduler(machines, async (machine, scheduler) =>
{
await scheduler.From(machine.Id + ".From", machine, MachineDataType.Name).Result.Query(machine.Id + ".ConsoleQuery", QueryConsole).Result.To(machine.Id + ".To", machine).Result.Deal(machine.Id + ".Deal", OnSuccess, OnFailure).Result.Run();
}, -1, 10));
}, -1, 0));
}
public override Task StopAsync(CancellationToken cancellationToken)
@@ -94,11 +100,12 @@ namespace MachineJob.Service
_logger.LogInformation(dataReturnDef.MachineId + " " + value.Key + " " + value.Value.DeviceValue);
}
/*
try
{
using (var context = new DatabaseWriteContext())
{
context.DatabaseWrites.Add(new DatabaseWriteEntity
context.DatabaseWrites?.Add(new DatabaseWriteEntity
{
Value1 = values["Test1"].DeviceValue,
Value2 = values["Test2"].DeviceValue,
@@ -119,7 +126,7 @@ namespace MachineJob.Service
{
//ignore
}
*/
Random r = new Random();
foreach (var value in values)
{

View File

@@ -1,7 +1,21 @@
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug"
"Default": "Debug",
"Override": {
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Debug"
}
}
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Debug"
}
},
"Quartz": {
"LogLevel": "Debug"
}
}

View File

@@ -1,7 +1,21 @@
{
"Serilog": {
"MinimumLevel": {
"Default": "Error"
"Default": "Error",
"Override": {
"Microsoft": "Error",
"Microsoft.Hosting.Lifetime": "Error"
}
}
},
"Logging": {
"LogLevel": {
"Default": "Error",
"Microsoft": "Error",
"Microsoft.Hosting.Lifetime": "Error"
}
},
"Quartz": {
"LogLevel": "Error"
}
}

View File

@@ -3,7 +3,7 @@
"TCP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"
@@ -16,7 +16,7 @@
"UDP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"

View File

@@ -8,8 +8,18 @@
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Quartz": {
"LogLevel": "Info"
},
"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": {
@@ -18,7 +28,7 @@
"a:id": "ModbusMachine1",
"b:protocol": "Modbus",
"c:type": "Tcp",
"d:connectionString": "10.10.18.251",
"d:connectionString": "127.0.0.1",
"e:addressMap": "AddressMapModbus",
"f:keepConnect": true,
"g:slaveAddress": 1,
@@ -29,7 +39,7 @@
"a:id": "SiemensMachine1",
"b:protocol": "Siemens",
"c:type": "Tcp",
"d:connectionString": "10.10.18.251",
"d:connectionString": "127.0.0.1",
"e:model": "S7_1200",
"f:addressMap": "AddressMapSiemens",
"g:keepConnect": true,
@@ -45,7 +55,7 @@
"d:connectionString": "COM1",
"e:addressMap": "AddressMapModbus",
"f:keepConnect": true,
"g:slaveAddress": 3,
"g:slaveAddress": 1,
"h:masterAddress": 2,
"i:endian": "BigEndianLsb"
},
@@ -53,7 +63,7 @@
"a:id": "SiemensMachine2",
"b:protocol": "Siemens",
"c:type": "Ppi",
"d:connectionString": "COM11",
"d:connectionString": "COM2",
"e:model": "S7_200",
"f:addressMap": "AddressMapSiemens",
"g:keepConnect": true,
@@ -61,6 +71,14 @@
"i:masterAddress": 0,
"j:src": 1,
"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": {
@@ -207,6 +225,24 @@
"Id": "10",
"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
}
]
}
}

View File

@@ -3,6 +3,7 @@ using Modbus.Net;
using Modbus.Net.Modbus;
using System.Diagnostics;
using TripleAdd.Models;
using AddressUnit = Modbus.Net.AddressUnit<string, int, int>;
namespace TripleAdd.Controllers
{
@@ -41,12 +42,12 @@ namespace TripleAdd.Controllers
{
if (utility == null)
{
utility = new ModbusUtility(ModbusType.Tcp, "10.10.18.251", 2, 0);
utility = new ModbusUtility(ModbusType.Tcp, "10.10.18.251", 2, 0, Endian.BigEndianLsb);
utility.AddressTranslator = new AddressTranslatorModbus();
await utility.ConnectAsync();
}
object[] getNum = (await utility.GetDatasAsync("4X 1", new KeyValuePair<Type, int>(typeof(ushort), 4))).Datas;
ushort[] getNumUshorts = BigEndianValueHelper.Instance.ObjectArrayToDestinationArray<ushort>(getNum);
ushort[] getNumUshorts = BigEndianLsbValueHelper.Instance.ObjectArrayToDestinationArray<ushort>(getNum);
return SetValue(getNumUshorts);
}
@@ -60,7 +61,7 @@ namespace TripleAdd.Controllers
new AddressUnit() {Id = "2", Area = "4X", Address = 2, CommunicationTag = "Add2", DataType = typeof(ushort), Zoom = 1, DecimalPos = 0},
new AddressUnit() {Id = "3", Area = "4X", Address = 3, CommunicationTag = "Add3", DataType = typeof(ushort), Zoom = 1, DecimalPos = 0},
new AddressUnit() {Id = "4", Area = "4X", Address = 4, CommunicationTag = "Ans", DataType = typeof(ushort), Zoom = 1, DecimalPos = 0},
}, 2, 0);
}, 2, 0, Endian.BigEndianLsb);
machine.AddressCombiner = new AddressCombinerContinus<string>(machine.AddressTranslator, 100000);
machine.AddressCombinerSet = new AddressCombinerContinus<string>(machine.AddressTranslator, 100000);
}

View File

@@ -3,7 +3,7 @@
"TCP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"
@@ -16,7 +16,7 @@
"UDP": {
"ConnectionTimeout": "5000",
"FetchSleepTime": "100",
"FullDuplex": "False",
"FullDuplex": "True",
"Modbus": {
"ModbusPort": "502",
"IP": "192.168.1.1"

View File

@@ -0,0 +1,876 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using System.Runtime.InteropServices;
using Technosoftware.DaAeHdaClient.Ae;
using Technosoftware.DaAeHdaClient.Da;
#endregion
#pragma warning disable 0618
namespace Technosoftware.DaAeHdaClient.Com.Ae
{
/// <summary>
/// Defines COM marshalling/unmarshalling functions for AE.
/// </summary>
internal class Interop
{
/// <summary>
/// Converts a standard FILETIME to an OpcRcw.Ae.FILETIME structure.
/// </summary>
internal static OpcRcw.Ae.FILETIME Convert(FILETIME input)
{
var output = new OpcRcw.Ae.FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Converts an OpcRcw.Ae.FILETIME to a standard FILETIME structure.
/// </summary>
internal static FILETIME Convert(OpcRcw.Ae.FILETIME input)
{
var output = new FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Converts the HRESULT to a system type.
/// </summary>
internal static OpcResult GetResultID(int input)
{
// must check for this error because of a code collision with a DA code.
if (input == Result.E_INVALIDBRANCHNAME)
{
return OpcResult.Ae.E_INVALIDBRANCHNAME;
}
return Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input);
}
/// <summary>
/// Unmarshals and deallocates a OPCEVENTSERVERSTATUS structure.
/// </summary>
internal static OpcServerStatus GetServerStatus(ref IntPtr pInput, bool deallocate)
{
OpcServerStatus output = null;
if (pInput != IntPtr.Zero)
{
var status = (OpcRcw.Ae.OPCEVENTSERVERSTATUS)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Ae.OPCEVENTSERVERSTATUS));
output = new OpcServerStatus();
output.VendorInfo = status.szVendorInfo;
output.ProductVersion = string.Format("{0}.{1}.{2}", status.wMajorVersion, status.wMinorVersion, status.wBuildNumber);
output.MajorVersion = status.wMajorVersion;
output.MinorVersion = status.wMinorVersion;
output.BuildNumber = status.wBuildNumber;
output.ServerState = (OpcServerState)status.dwServerState;
output.StatusInfo = null;
output.StartTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftStartTime));
output.CurrentTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftCurrentTime));
output.LastUpdateTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftLastUpdateTime));
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Ae.OPCEVENTSERVERSTATUS));
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts a NodeType value to the OPCAEBROWSETYPE equivalent.
/// </summary>
internal static OpcRcw.Ae.OPCAEBROWSETYPE GetBrowseType(TsCAeBrowseType input)
{
switch (input)
{
case TsCAeBrowseType.Area: return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_AREA;
case TsCAeBrowseType.Source: return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_SOURCE;
}
return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_AREA;
}
/// <summary>
/// Converts an array of ONEVENTSTRUCT structs to an array of EventNotification objects.
/// </summary>
internal static TsCAeEventNotification[] GetEventNotifications(OpcRcw.Ae.ONEVENTSTRUCT[] input)
{
TsCAeEventNotification[] output = null;
if (input != null && input.Length > 0)
{
output = new TsCAeEventNotification[input.Length];
for (var ii = 0; ii < input.Length; ii++)
{
output[ii] = GetEventNotification(input[ii]);
}
}
return output;
}
/// <summary>
/// Converts a ONEVENTSTRUCT struct to a EventNotification object.
/// </summary>
internal static TsCAeEventNotification GetEventNotification(OpcRcw.Ae.ONEVENTSTRUCT input)
{
var output = new TsCAeEventNotification();
output.SourceID = input.szSource;
output.Time = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(input.ftTime));
output.Severity = input.dwSeverity;
output.Message = input.szMessage;
output.EventType = (TsCAeEventType)input.dwEventType;
output.EventCategory = input.dwEventCategory;
output.ChangeMask = input.wChangeMask;
output.NewState = input.wNewState;
output.Quality = new TsCDaQuality(input.wQuality);
output.ConditionName = input.szConditionName;
output.SubConditionName = input.szSubconditionName;
output.AckRequired = input.bAckRequired != 0;
output.ActiveTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(input.ftActiveTime));
output.Cookie = input.dwCookie;
output.ActorID = input.szActorID;
var attributes = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref input.pEventAttributes, input.dwNumEventAttrs, false);
output.SetAttributes(attributes);
return output;
}
/// <summary>
/// Converts an array of OPCCONDITIONSTATE structs to an array of Condition objects.
/// </summary>
internal static TsCAeCondition[] GetConditions(ref IntPtr pInput, int count, bool deallocate)
{
TsCAeCondition[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCAeCondition[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
var condition = (OpcRcw.Ae.OPCCONDITIONSTATE)Marshal.PtrToStructure(pos, typeof(OpcRcw.Ae.OPCCONDITIONSTATE));
output[ii] = new TsCAeCondition();
output[ii].State = condition.wState;
output[ii].Quality = new TsCDaQuality(condition.wQuality);
output[ii].Comment = condition.szComment;
output[ii].AcknowledgerID = condition.szAcknowledgerID;
output[ii].CondLastActive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftCondLastActive));
output[ii].CondLastInactive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftCondLastInactive));
output[ii].SubCondLastActive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftSubCondLastActive));
output[ii].LastAckTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftLastAckTime));
output[ii].ActiveSubCondition.Name = condition.szActiveSubCondition;
output[ii].ActiveSubCondition.Definition = condition.szASCDefinition;
output[ii].ActiveSubCondition.Severity = condition.dwASCSeverity;
output[ii].ActiveSubCondition.Description = condition.szASCDescription;
// unmarshal sub-conditions.
var names = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCNames, condition.dwNumSCs, deallocate);
var severities = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref condition.pdwSCSeverities, condition.dwNumSCs, deallocate);
var definitions = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCDefinitions, condition.dwNumSCs, deallocate);
var descriptions = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCDescriptions, condition.dwNumSCs, deallocate);
output[ii].SubConditions.Clear();
if (condition.dwNumSCs > 0)
{
for (var jj = 0; jj < names.Length; jj++)
{
var subcondition = new TsCAeSubCondition();
subcondition.Name = names[jj];
subcondition.Severity = severities[jj];
subcondition.Definition = definitions[jj];
subcondition.Description = descriptions[jj];
output[ii].SubConditions.Add(subcondition);
}
}
// unmarshal attributes.
var values = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref condition.pEventAttributes, condition.dwNumEventAttrs, deallocate);
var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref condition.pErrors, condition.dwNumEventAttrs, deallocate);
output[ii].Attributes.Clear();
if (condition.dwNumEventAttrs > 0)
{
for (var jj = 0; jj < values.Length; jj++)
{
var attribute = new TsCAeAttributeValue();
attribute.ID = 0;
attribute.Value = values[jj];
attribute.Result = GetResultID(errors[jj]);
output[ii].Attributes.Add(attribute);
}
}
// deallocate structure.
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Ae.OPCCONDITIONSTATE));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Ae.OPCCONDITIONSTATE)));
}
// deallocate array.
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/*
/// <summary>
/// Converts an array of COM HRESULTs structures to .NET ResultID objects.
/// </summary>
internal static ResultID[] GetResultIDs(ref IntPtr pInput, int count, bool deallocate)
{
ResultID[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new ResultID[count];
int[] errors = OpcCom.Interop.GetInt32s(ref pInput, count, deallocate);
for (int ii = 0; ii < count; ii++)
{
output[ii] = OpcCom.Interop.GetResultID(errors[ii]);
}
}
return output;
}
/// <summary>
/// Converts an array of COM SourceServer structures to .NET SourceServer objects.
/// </summary>
internal static SourceServer[] GetSourceServers(ref IntPtr pInput, int count, bool deallocate)
{
SourceServer[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new SourceServer[count];
IntPtr pos = pInput;
for (int ii = 0; ii < count; ii++)
{
OpcRcw.Dx.SourceServer server = (OpcRcw.Dx.SourceServer)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.SourceServer));
output[ii] = new SourceServer();
output[ii].ItemName = server.szItemName;
output[ii].ItemPath = server.szItemPath;
output[ii].Version = server.szVersion;
output[ii].Name = server.szName;
output[ii].Description = server.szDescription;
output[ii].ServerType = server.szServerType;
output[ii].ServerURL = server.szServerURL;
output[ii].DefaultConnected = server.bDefaultSourceServerConnected != 0;
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.SourceServer)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts an array of .NET SourceServer objects to COM SourceServer structures.
/// </summary>
internal static OpcRcw.Dx.SourceServer[] GetSourceServers(SourceServer[] input)
{
OpcRcw.Dx.SourceServer[] output = null;
if (input != null && input.Length > 0)
{
output = new OpcRcw.Dx.SourceServer[input.Length];
for (int ii = 0; ii < input.Length; ii++)
{
output[ii] = new OpcRcw.Dx.SourceServer();
output[ii].dwMask = (uint)OpcRcw.Dx.Mask.All;
output[ii].szItemName = input[ii].ItemName;
output[ii].szItemPath = input[ii].ItemPath;
output[ii].szVersion = input[ii].Version;
output[ii].szName = input[ii].Name;
output[ii].szDescription = input[ii].Description;
output[ii].szServerType = input[ii].ServerType;
output[ii].szServerURL = input[ii].ServerURL;
output[ii].bDefaultSourceServerConnected = (input[ii].DefaultConnected)?1:0;
}
}
return output;
}
/// <summary>
/// Converts an array of COM DXGeneralResponse structure to a .NET GeneralResponse object.
/// </summary>
internal static GeneralResponse GetGeneralResponse(OpcRcw.Dx.DXGeneralResponse input, bool deallocate)
{
Opc.Dx.IdentifiedResult[] results = Interop.GetIdentifiedResults(ref input.pIdentifiedResults, input.dwCount, deallocate);
return new GeneralResponse(input.szConfigurationVersion, results);
}
/// <summary>
/// Converts an array of COM IdentifiedResult structures to .NET IdentifiedResult objects.
/// </summary>
internal static Opc.Dx.IdentifiedResult[] GetIdentifiedResults(ref IntPtr pInput, int count, bool deallocate)
{
Opc.Dx.IdentifiedResult[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new Opc.Dx.IdentifiedResult[count];
IntPtr pos = pInput;
for (int ii = 0; ii < count; ii++)
{
OpcRcw.Dx.IdentifiedResult result = (OpcRcw.Dx.IdentifiedResult)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.IdentifiedResult));
output[ii] = new Opc.Dx.IdentifiedResult();
output[ii].ItemName = result.szItemName;
output[ii].ItemPath = result.szItemPath;
output[ii].Version = result.szVersion;
output[ii].ResultID = OpcCom.Interop.GetResultID(result.hResultCode);
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Dx.IdentifiedResult));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.IdentifiedResult)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts an array of COM DXConnection structures to .NET DXConnection objects.
/// </summary>
internal static DXConnection[] GetDXConnections(ref IntPtr pInput, int count, bool deallocate)
{
DXConnection[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new DXConnection[count];
IntPtr pos = pInput;
for (int ii = 0; ii < count; ii++)
{
OpcRcw.Dx.DXConnection connection = (OpcRcw.Dx.DXConnection)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.DXConnection));
output[ii] = GetDXConnection(connection, deallocate);
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Dx.DXConnection));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.DXConnection)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts an array of .NET DXConnection objects to COM DXConnection structures.
/// </summary>
internal static OpcRcw.Dx.DXConnection[] GetDXConnections(DXConnection[] input)
{
OpcRcw.Dx.DXConnection[] output = null;
if (input != null && input.Length > 0)
{
output = new OpcRcw.Dx.DXConnection[input.Length];
for (int ii = 0; ii < input.Length; ii++)
{
output[ii] = GetDXConnection(input[ii]);
}
}
return output;
}
/// <summary>
/// Converts a .NET DXConnection object to COM DXConnection structure.
/// </summary>
internal static OpcRcw.Dx.DXConnection GetDXConnection(DXConnection input)
{
OpcRcw.Dx.DXConnection output = new OpcRcw.Dx.DXConnection();
// set output default values.
output.dwMask = 0;
output.szItemPath = null;
output.szItemName = null;
output.szVersion = null;
output.dwBrowsePathCount = 0;
output.pszBrowsePaths = IntPtr.Zero;
output.szName = null;
output.szDescription = null;
output.szKeyword = null;
output.bDefaultSourceItemConnected = 0;
output.bDefaultTargetItemConnected = 0;
output.bDefaultOverridden = 0;
output.vDefaultOverrideValue = null;
output.vSubstituteValue = null;
output.bEnableSubstituteValue = 0;
output.szTargetItemPath = null;
output.szTargetItemName = null;
output.szSourceServerName = null;
output.szSourceItemPath = null;
output.szSourceItemName = null;
output.dwSourceItemQueueSize = 0;
output.dwUpdateRate = 0;
output.fltDeadBand = 0;
output.szVendorData = null;
// item name
if (input.ItemName != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.ItemName;
output.szItemName = input.ItemName;
}
// item path
if (input.ItemPath != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.ItemPath;
output.szItemPath = input.ItemPath;
}
// version
if (input.Version != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.Version;
output.szVersion = input.Version;
}
// browse paths
if (input.BrowsePaths.Count > 0)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.BrowsePaths;
output.dwBrowsePathCount = input.BrowsePaths.Count;
output.pszBrowsePaths = OpcCom.Interop.GetUnicodeStrings(input.BrowsePaths.ToArray());
}
// name
if (input.Name != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.Name;
output.szName = input.Name;
}
// description
if (input.Description != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.Description;
output.szDescription = input.Description;
}
// keyword
if (input.Keyword != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.Keyword;
output.szKeyword = input.Keyword;
}
// default source item connected
if (input.DefaultSourceItemConnectedSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultSourceItemConnected;
output.bDefaultSourceItemConnected = (input.DefaultSourceItemConnected)?1:0;
}
// default target item connected
if (input.DefaultTargetItemConnectedSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultTargetItemConnected;
output.bDefaultTargetItemConnected = (input.DefaultTargetItemConnected)?1:0;
}
// default overridden
if (input.DefaultOverriddenSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultOverridden;
output.bDefaultOverridden = (input.DefaultOverridden)?1:0;
}
// default override value
if (input.DefaultOverrideValue != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultOverrideValue;
output.vDefaultOverrideValue = input.DefaultOverrideValue;
}
// substitute value
if (input.SubstituteValue != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SubstituteValue;
output.vSubstituteValue = input.SubstituteValue;
}
// enable substitute value
if (input.EnableSubstituteValueSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.EnableSubstituteValue;
output.bEnableSubstituteValue = (input.EnableSubstituteValue)?1:0;
}
// target item name
if (input.TargetItemName != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.TargetItemName;
output.szTargetItemName = input.TargetItemName;
}
// target item path
if (input.TargetItemPath != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.TargetItemPath;
output.szTargetItemPath = input.TargetItemPath;
}
// source server name
if (input.SourceServerName != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceServerName;
output.szSourceServerName = input.SourceServerName;
}
// source item name
if (input.SourceItemName != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemName;
output.szSourceItemName = input.SourceItemName;
}
// source item path
if (input.SourceItemPath != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemPath;
output.szSourceItemPath = input.SourceItemPath;
}
// source item queue size
if (input.SourceItemQueueSizeSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemQueueSize;
output.dwSourceItemQueueSize = input.SourceItemQueueSize;
}
// update rate
if (input.UpdateRateSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.UpdateRate;
output.dwUpdateRate = input.UpdateRate;
}
// deadband
if (input.DeadbandSpecified)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.DeadBand;
output.fltDeadBand = input.Deadband;
}
// vendor data
if (input.VendorData != null)
{
output.dwMask |= (uint)OpcRcw.Dx.Mask.VendorData;
output.szVendorData = input.VendorData;
}
return output;
}
/// <summary>
/// Converts a COM DXConnection structure to a .NET DXConnection object.
/// </summary>
internal static DXConnection GetDXConnection(OpcRcw.Dx.DXConnection input, bool deallocate)
{
DXConnection output = new DXConnection();
// set output default values.
output.ItemPath = null;
output.ItemName = null;
output.Version = null;
output.BrowsePaths.Clear();
output.Name = null;
output.Description = null;
output.Keyword = null;
output.DefaultSourceItemConnected = false;
output.DefaultSourceItemConnectedSpecified = false;
output.DefaultTargetItemConnected = false;
output.DefaultTargetItemConnectedSpecified = false;
output.DefaultOverridden = false;
output.DefaultOverriddenSpecified = false;
output.DefaultOverrideValue = null;
output.SubstituteValue = null;
output.EnableSubstituteValue = false;
output.EnableSubstituteValueSpecified = false;
output.TargetItemPath = null;
output.TargetItemName = null;
output.SourceServerName = null;
output.SourceItemPath = null;
output.SourceItemName = null;
output.SourceItemQueueSize = 0;
output.SourceItemQueueSizeSpecified = false;
output.UpdateRate = 0;
output.UpdateRateSpecified = false;
output.Deadband = 0;
output.DeadbandSpecified = false;
output.VendorData = null;
// item name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.ItemName) != 0)
{
output.ItemName = input.szItemName;
}
// item path
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.ItemPath) != 0)
{
output.ItemPath = input.szItemPath;
}
// version
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Version) != 0)
{
output.Version = input.szVersion;
}
// browse paths
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.BrowsePaths) != 0)
{
string[] browsePaths = OpcCom.Interop.GetUnicodeStrings(ref input.pszBrowsePaths, input.dwBrowsePathCount, deallocate);
if (browsePaths != null)
{
output.BrowsePaths.AddRange(browsePaths);
}
}
// name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Name) != 0)
{
output.Name = input.szName;
}
// description
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Description) != 0)
{
output.Description = input.szDescription;
}
// keyword
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Keyword) != 0)
{
output.Keyword = input.szKeyword;
}
// default source item connected
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultSourceItemConnected) != 0)
{
output.DefaultSourceItemConnected = input.bDefaultSourceItemConnected != 0;
output.DefaultSourceItemConnectedSpecified = true;
}
// default target item connected
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultTargetItemConnected) != 0)
{
output.DefaultTargetItemConnected = input.bDefaultTargetItemConnected != 0;
output.DefaultTargetItemConnectedSpecified = true;
}
// default overridden
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultOverridden) != 0)
{
output.DefaultOverridden = input.bDefaultOverridden != 0;
output.DefaultOverriddenSpecified = true;
}
// default override value
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultOverrideValue) != 0)
{
output.DefaultOverrideValue = input.vDefaultOverrideValue;
}
// substitute value
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SubstituteValue) != 0)
{
output.SubstituteValue = input.vSubstituteValue;
}
// enable substitute value
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.EnableSubstituteValue) != 0)
{
output.EnableSubstituteValue = input.bEnableSubstituteValue != 0;
output.EnableSubstituteValueSpecified = true;
}
// target item name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.TargetItemName) != 0)
{
output.TargetItemName = input.szTargetItemName;
}
// target item path
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.TargetItemPath) != 0)
{
output.TargetItemPath = input.szTargetItemPath;
}
// source server name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceServerName) != 0)
{
output.SourceServerName = input.szSourceServerName;
}
// source item name
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemName) != 0)
{
output.SourceItemName = input.szSourceItemName;
}
// source item path
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemPath) != 0)
{
output.SourceItemPath = input.szSourceItemPath;
}
// source item queue size
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemQueueSize) != 0)
{
output.SourceItemQueueSize = input.dwSourceItemQueueSize;
output.SourceItemQueueSizeSpecified = true;
}
// update rate
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.UpdateRate) != 0)
{
output.UpdateRate = input.dwUpdateRate;
output.UpdateRateSpecified = true;
}
// deadband
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DeadBand) != 0)
{
output.Deadband = input.fltDeadBand;
output.DeadbandSpecified = true;
}
// vendor data
if ((input.dwMask & (uint)OpcRcw.Dx.Mask.VendorData) != 0)
{
output.VendorData = input.szVendorData;
}
return output;
}
/// <summary>
/// Converts an array of .NET ItemIdentifier objects to COM ItemIdentifier structures.
/// </summary>
internal static OpcRcw.Dx.ItemIdentifier[] GetItemIdentifiers(Opc.Dx.ItemIdentifier[] input)
{
OpcRcw.Dx.ItemIdentifier[] output = null;
if (input != null && input.Length > 0)
{
output = new OpcRcw.Dx.ItemIdentifier[input.Length];
for (int ii = 0; ii < input.Length; ii++)
{
output[ii] = new OpcRcw.Dx.ItemIdentifier();
output[ii].szItemName = input[ii].ItemName;
output[ii].szItemPath = input[ii].ItemPath;
output[ii].szVersion = input[ii].Version;
}
}
return output;
}
*/
}
}

View File

@@ -0,0 +1,51 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Ae
{
/// <summary>
/// Defines all well known COM AE HRESULT codes.
/// </summary>
internal struct Result
{
/// <remarks/>
public const int S_ALREADYACKED = +0x00040200; // 0x00040200
/// <remarks/>
public const int S_INVALIDBUFFERTIME = +0x00040201; // 0x00040201
/// <remarks/>
public const int S_INVALIDMAXSIZE = +0x00040202; // 0x00040202
/// <remarks/>
public const int S_INVALIDKEEPALIVETIME = +0x00040203; // 0x00040203
/// <remarks/>
public const int E_INVALIDBRANCHNAME = -0x3FFBFDFD; // 0xC0040203
/// <remarks/>
public const int E_INVALIDTIME = -0x3FFBFDFC; // 0xC0040204
/// <remarks/>
public const int E_BUSY = -0x3FFBFDFB; // 0xC0040205
/// <remarks/>
public const int E_NOINFO = -0x3FFBFDFA; // 0xC0040206
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,610 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using System.Runtime.InteropServices;
using Technosoftware.DaAeHdaClient.Ae;
using Technosoftware.DaAeHdaClient.Utilities;
using Technosoftware.OpcRcw.Ae;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Ae
{
/// <summary>
/// A .NET wrapper for a COM server that implements the AE subscription interfaces.
/// </summary>
[Serializable]
internal class Subscription : ITsCAeSubscription
{
#region Constructors
/// <summary>
/// Initializes the object with the specified URL and COM server.
/// </summary>
internal Subscription(TsCAeSubscriptionState state, object subscription)
{
subscription_ = subscription;
clientHandle_ = OpcConvert.Clone(state.ClientHandle);
supportsAe11_ = true;
callback_ = new Callback(state.ClientHandle);
// check if the V1.1 interfaces are supported.
try
{
var server = (IOPCEventSubscriptionMgt2)subscription_;
}
catch
{
supportsAe11_ = false;
}
}
#endregion
#region IDisposable Members
/// <summary>
/// The finalizer.
/// </summary>
~Subscription()
{
Dispose(false);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose(bool disposing) executes in two distinct scenarios.
/// If disposing equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.
/// If disposing equals false, the method has been called by the
/// runtime from inside the finalizer and you should not reference
/// other objects. Only unmanaged resources can be disposed.
/// </summary>
/// <param name="disposing">If true managed and unmanaged resources can be disposed. If false only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (!disposed_)
{
lock (lock_)
{
if (disposing)
{
// Free other state (managed objects).
if (subscription_ != null)
{
// close all connections.
if (connection_ != null)
{
try
{
connection_.Dispose();
}
catch
{
// Ignore. COM Server probably no longer connected
}
connection_ = null;
}
}
}
// Free your own state (unmanaged objects).
// Set large fields to null.
if (subscription_ != null)
{
// release subscription object.
try
{
Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(subscription_);
}
catch
{
// Ignore. COM Server probably no longer connected
}
subscription_ = null;
}
}
disposed_ = true;
}
}
#endregion
#region Technosoftware.DaAeHdaClient.ISubscription Members
/// <summary>
/// An event to receive data change updates.
/// </summary>
public event TsCAeDataChangedEventHandler DataChangedEvent
{
add { lock (this) { Advise(); callback_.DataChangedEvent += value; } }
remove { lock (this) { callback_.DataChangedEvent -= value; Unadvise(); } }
}
//======================================================================
// State Management
/// <summary>
/// Returns the current state of the subscription.
/// </summary>
/// <returns>The current state of the subscription.</returns>
public TsCAeSubscriptionState GetState()
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// initialize arguments.
int pbActive;
int pdwBufferTime;
int pdwMaxSize;
var pdwKeepAliveTime = 0;
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).GetState(
out pbActive,
out pdwBufferTime,
out pdwMaxSize,
out _);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetState", e);
}
// get keep alive.
if (supportsAe11_)
{
try
{
((IOPCEventSubscriptionMgt2)subscription_).GetKeepAlive(out pdwKeepAliveTime);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt2.GetKeepAlive", e);
}
}
// build results
var state = new TsCAeSubscriptionState
{
Active = pbActive != 0,
ClientHandle = clientHandle_,
BufferTime = pdwBufferTime,
MaxSize = pdwMaxSize,
KeepAlive = pdwKeepAliveTime
};
// return results.
return state;
}
}
/// <summary>
/// Changes the state of a subscription.
/// </summary>
/// <param name="masks">A bit mask that indicates which elements of the subscription state are changing.</param>
/// <param name="state">The new subscription state.</param>
/// <returns>The actual subscription state after applying the changes.</returns>
public TsCAeSubscriptionState ModifyState(int masks, TsCAeSubscriptionState state)
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// initialize arguments.
var active = (state.Active) ? 1 : 0;
var hActive = GCHandle.Alloc(active, GCHandleType.Pinned);
var hBufferTime = GCHandle.Alloc(state.BufferTime, GCHandleType.Pinned);
var hMaxSize = GCHandle.Alloc(state.MaxSize, GCHandleType.Pinned);
var pbActive = ((masks & (int)TsCAeStateMask.Active) != 0) ? hActive.AddrOfPinnedObject() : IntPtr.Zero;
var pdwBufferTime = ((masks & (int)TsCAeStateMask.BufferTime) != 0) ? hBufferTime.AddrOfPinnedObject() : IntPtr.Zero;
var pdwMaxSize = ((masks & (int)TsCAeStateMask.MaxSize) != 0) ? hMaxSize.AddrOfPinnedObject() : IntPtr.Zero;
var phClientSubscription = 0;
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).SetState(
pbActive,
pdwBufferTime,
pdwMaxSize,
phClientSubscription,
out _,
out _);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SetState", e);
}
finally
{
if (hActive.IsAllocated) hActive.Free();
if (hBufferTime.IsAllocated) hBufferTime.Free();
if (hMaxSize.IsAllocated) hMaxSize.Free();
}
// update keep alive.
if (((masks & (int)TsCAeStateMask.KeepAlive) != 0) && supportsAe11_)
{
try
{
((IOPCEventSubscriptionMgt2)subscription_).SetKeepAlive(
state.KeepAlive,
out _);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt2.SetKeepAlive", e);
}
}
// return current state.
return GetState();
}
}
//======================================================================
// Filter Management
/// <summary>
/// Returns the current filters for the subscription.
/// </summary>
/// <returns>The current filters for the subscription.</returns>
public TsCAeSubscriptionFilters GetFilters()
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// initialize arguments.
int pdwEventType;
int pdwNumCategories;
IntPtr ppidEventCategories;
int pdwLowSeverity;
int pdwHighSeverity;
int pdwNumAreas;
IntPtr ppsAreaList;
int pdwNumSources;
IntPtr ppsSourceList;
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).GetFilter(
out pdwEventType,
out pdwNumCategories,
out ppidEventCategories,
out pdwLowSeverity,
out pdwHighSeverity,
out pdwNumAreas,
out ppsAreaList,
out pdwNumSources,
out ppsSourceList);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetFilter", e);
}
// marshal results
var categoryIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppidEventCategories, pdwNumCategories, true);
var areaIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppsAreaList, pdwNumAreas, true);
var sourceIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppsSourceList, pdwNumSources, true);
// build results.
var filters = new TsCAeSubscriptionFilters
{
EventTypes = pdwEventType,
LowSeverity = pdwLowSeverity,
HighSeverity = pdwHighSeverity
};
filters.Categories.AddRange(categoryIDs);
filters.Areas.AddRange(areaIDs);
filters.Sources.AddRange(sourceIDs);
// return results.
return filters;
}
}
/// <summary>
/// Sets the current filters for the subscription.
/// </summary>
/// <param name="filters">The new filters to use for the subscription.</param>
public void SetFilters(TsCAeSubscriptionFilters filters)
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).SetFilter(
filters.EventTypes,
filters.Categories.Count,
filters.Categories.ToArray(),
filters.LowSeverity,
filters.HighSeverity,
filters.Areas.Count,
filters.Areas.ToArray(),
filters.Sources.Count,
filters.Sources.ToArray());
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SetFilter", e);
}
}
}
//======================================================================
// Attribute Management
/// <summary>
/// Returns the set of attributes to return with event notifications.
/// </summary>
/// <returns>The set of attributes to returned with event notifications.</returns>
public int[] GetReturnedAttributes(int eventCategory)
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// initialize arguments.
int pdwCount;
IntPtr ppidAttributeIDs;
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).GetReturnedAttributes(
eventCategory,
out pdwCount,
out ppidAttributeIDs);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetReturnedAttributes", e);
}
// marshal results
var attributeIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppidAttributeIDs, pdwCount, true);
// return results.
return attributeIDs;
}
}
/// <summary>
/// Selects the set of attributes to return with event notifications.
/// </summary>
/// <param name="eventCategory">The specific event category for which the attributes apply.</param>
/// <param name="attributeIDs">The list of attribute ids to return.</param>
public void SelectReturnedAttributes(int eventCategory, int[] attributeIDs)
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).SelectReturnedAttributes(
eventCategory,
attributeIDs?.Length ?? 0,
attributeIDs ?? Array.Empty<int>());
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SelectReturnedAttributes", e);
}
}
}
//======================================================================
// Refresh
/// <summary>
/// Force a refresh for all active conditions and inactive, unacknowledged conditions whose event notifications match the filter of the event subscription.
/// </summary>
public void Refresh()
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).Refresh(0);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.Refresh", e);
}
}
}
/// <summary>
/// Cancels an outstanding refresh request.
/// </summary>
public void CancelRefresh()
{
lock (this)
{
// verify state and arguments.
if (subscription_ == null) throw new NotConnectedException();
// invoke COM method.
try
{
((IOPCEventSubscriptionMgt)subscription_).CancelRefresh(0);
}
catch (Exception e)
{
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.CancelRefresh", e);
}
}
}
#endregion
#region IOPCEventSink Members
/// <summary>
/// A class that implements the IOPCEventSink interface.
/// </summary>
private class Callback : IOPCEventSink
{
/// <summary>
/// Initializes the object with the containing subscription object.
/// </summary>
public Callback(object clientHandle)
{
clientHandle_ = clientHandle;
}
/// <summary>
/// Raised when data changed callbacks arrive.
/// </summary>
public event TsCAeDataChangedEventHandler DataChangedEvent
{
add { lock (this) { DataChangedEventHandler += value; } }
remove { lock (this) { DataChangedEventHandler -= value; } }
}
/// <summary>
/// Called when a data changed event is received.
/// </summary>
public void OnEvent(
int hClientSubscription,
int bRefresh,
int bLastRefresh,
int dwCount,
ONEVENTSTRUCT[] pEvents)
{
LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions, true);
try
{
lock (this)
{
// do nothing if no connections.
if (DataChangedEventHandler == null) return;
// un marshal item values.
var notifications = Interop.GetEventNotifications(pEvents);
foreach (var notification in notifications)
{
notification.ClientHandle = clientHandle_;
}
if (!LicenseHandler.IsExpired)
{
// invoke the callback.
DataChangedEventHandler?.Invoke(notifications, bRefresh != 0, bLastRefresh != 0);
}
}
}
catch (Exception e)
{
Utils.Trace(e, "Exception '{0}' in event handler.", e.Message);
}
}
#region Private Members
private object clientHandle_;
private event TsCAeDataChangedEventHandler DataChangedEventHandler;
#endregion
}
#endregion
#region Private Methods
/// <summary>
/// Establishes a connection point callback with the COM server.
/// </summary>
private void Advise()
{
if (connection_ == null)
{
connection_ = new ConnectionPoint(subscription_, typeof(IOPCEventSink).GUID);
connection_.Advise(callback_);
}
}
/// <summary>
/// Closes a connection point callback with the COM server.
/// </summary>
private void Unadvise()
{
if (connection_ != null)
{
if (connection_.Unadvise() == 0)
{
connection_.Dispose();
connection_ = null;
}
}
}
#endregion
#region Private Members
private object subscription_;
private object clientHandle_;
private bool supportsAe11_ = true;
private ConnectionPoint connection_;
private Callback callback_;
/// <summary>
/// The synchronization object for subscription access
/// </summary>
private static volatile object lock_ = new object();
private bool disposed_;
#endregion
}
}

View File

@@ -0,0 +1,97 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// Manages the license to enable the different product versions.
/// </summary>
public partial class ApplicationInstance
{
#region Nested Enums
/// <summary>
/// The possible authentication levels.
/// </summary>
[Flags]
public enum AuthenticationLevel : uint
{
/// <summary>
/// Tells DCOM to choose the authentication level using its normal security blanket negotiation algorithm.
/// </summary>
Default = 0,
/// <summary>
/// Performs no authentication.
/// </summary>
None = 1,
/// <summary>
/// Authenticates the credentials of the client only when the client establishes a relationship with the server. Datagram transports always use Packet instead.
/// </summary>
Connect = 2,
/// <summary>
/// Authenticates only at the beginning of each remote procedure call when the server receives the request. Datagram transports use Packet instead.
/// </summary>
Call = 3,
/// <summary>
/// Authenticates that all data received is from the expected client.
/// </summary>
Packet = 4,
/// <summary>
/// Authenticates and verifies that none of the data transferred between client and server has been modified.
/// </summary>
Integrity = 5,
/// <summary>
/// Authenticates all previous levels and encrypts the argument value of each remote procedure call.
/// </summary>
Privacy = 6,
}
#endregion
#region Public Methods
/// <summary>
/// Initializes COM security. This should be called directly at the beginning of an application and can only be called once.
/// </summary>
/// <param name="authenticationLevel">The default authentication level for the process. Both servers and clients use this parameter when they call CoInitializeSecurity. With the Windows Update KB5004442 a higher authentication level of Integrity must be used.</param>
public static void InitializeSecurity(AuthenticationLevel authenticationLevel)
{
if (!InitializeSecurityCalled)
{
Com.Interop.InitializeSecurity((uint)authenticationLevel);
InitializeSecurityCalled = true;
}
}
#endregion
#region Internal Fields
internal static bool InitializeSecurityCalled;
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,107 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using Technosoftware.OpcRcw.Comn;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// Adds and removes a connection point to a server.
/// </summary>
internal class ConnectionPoint : IDisposable
{
/// <summary>
/// The COM server that supports connection points.
/// </summary>
private IConnectionPoint server_;
/// <summary>
/// The id assigned to the connection by the COM server.
/// </summary>
private int cookie_;
/// <summary>
/// The number of times Advise() has been called without a matching Unadvise().
/// </summary>
private int refs_;
/// <summary>
/// Initializes the object by finding the specified connection point.
/// </summary>
public ConnectionPoint(object server, Guid iid)
{
((IConnectionPointContainer)server).FindConnectionPoint(ref iid, out server_);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (server_ != null)
{
while (Unadvise() > 0)
{
}
try
{
Utilities.Interop.ReleaseServer(server_);
}
catch
{
// Ignore. COM Server probably no longer connected
}
server_ = null;
}
}
/// <summary>
/// The cookie returned in the advise call.
/// </summary>
public int Cookie => cookie_;
//=====================================================================
// IConnectionPoint
/// <summary>
/// Establishes a connection, if necessary and increments the reference count.
/// </summary>
public int Advise(object callback)
{
if (refs_++ == 0) server_.Advise(callback, out cookie_);
return refs_;
}
/// <summary>
/// Decrements the reference count and closes the connection if no more references.
/// </summary>
public int Unadvise()
{
if (--refs_ == 0) server_.Unadvise(cookie_);
return refs_;
}
}
}

View File

@@ -0,0 +1,60 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using Technosoftware.DaAeHdaClient.Da;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Da
{
/// <summary>
/// Implements an object that handles multi-step browse operations.
/// </summary>
[Serializable]
internal class BrowsePosition : TsCDaBrowsePosition
{
/// <summary>
/// The continuation point for a browse operation.
/// </summary>
internal string ContinuationPoint = null;
/// <summary>
/// Indicates that elements that meet the filter criteria have not been returned.
/// </summary>
internal bool MoreElements = false;
/// <summary>
/// Initializes a browse position
/// </summary>
internal BrowsePosition(
OpcItem itemID,
TsCDaBrowseFilters filters,
string continuationPoint)
:
base(itemID, filters)
{
ContinuationPoint = continuationPoint;
}
}
}

View File

@@ -0,0 +1,905 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;
using Technosoftware.DaAeHdaClient.Da;
#endregion
#pragma warning disable 0618
namespace Technosoftware.DaAeHdaClient.Com.Da
{
/// <summary>
/// Contains state information for a single asynchronous Technosoftware.DaAeHdaClient.Com.Da.Interop.
/// </summary>
internal class Interop
{
/// <summary>
/// Converts a standard FILETIME to an OpcRcw.Da.FILETIME structure.
/// </summary>
internal static OpcRcw.Da.FILETIME Convert(FILETIME input)
{
var output = new OpcRcw.Da.FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Converts an OpcRcw.Da.FILETIME to a standard FILETIME structure.
/// </summary>
internal static FILETIME Convert(OpcRcw.Da.FILETIME input)
{
var output = new FILETIME();
output.dwLowDateTime = input.dwLowDateTime;
output.dwHighDateTime = input.dwHighDateTime;
return output;
}
/// <summary>
/// Allocates and marshals a OPCSERVERSTATUS structure.
/// </summary>
internal static OpcRcw.Da.OPCSERVERSTATUS GetServerStatus(OpcServerStatus input, int groupCount)
{
var output = new OpcRcw.Da.OPCSERVERSTATUS();
if (input != null)
{
output.szVendorInfo = input.VendorInfo;
output.wMajorVersion = 0;
output.wMinorVersion = 0;
output.wBuildNumber = 0;
output.dwServerState = (OpcRcw.Da.OPCSERVERSTATE)input.ServerState;
output.ftStartTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.StartTime));
output.ftCurrentTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.CurrentTime));
output.ftLastUpdateTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.LastUpdateTime));
output.dwBandWidth = -1;
output.dwGroupCount = groupCount;
output.wReserved = 0;
if (input.ProductVersion != null)
{
var versions = input.ProductVersion.Split(new char[] { '.' });
if (versions.Length > 0)
{
try { output.wMajorVersion = System.Convert.ToInt16(versions[0]); }
catch { output.wMajorVersion = 0; }
}
if (versions.Length > 1)
{
try { output.wMinorVersion = System.Convert.ToInt16(versions[1]); }
catch { output.wMinorVersion = 0; }
}
output.wBuildNumber = 0;
for (var ii = 2; ii < versions.Length; ii++)
{
try
{
output.wBuildNumber = (short)(output.wBuildNumber * 100 + System.Convert.ToInt16(versions[ii]));
}
catch
{
output.wBuildNumber = 0;
break;
}
}
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCSERVERSTATUS structure.
/// </summary>
internal static OpcServerStatus GetServerStatus(ref IntPtr pInput, bool deallocate)
{
OpcServerStatus output = null;
if (pInput != IntPtr.Zero)
{
var status = (OpcRcw.Da.OPCSERVERSTATUS)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCSERVERSTATUS));
output = new OpcServerStatus();
output.VendorInfo = status.szVendorInfo;
output.ProductVersion = string.Format("{0}.{1}.{2}", status.wMajorVersion, status.wMinorVersion, status.wBuildNumber);
output.ServerState = (OpcServerState)status.dwServerState;
output.StatusInfo = null;
output.StartTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftStartTime));
output.CurrentTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftCurrentTime));
output.LastUpdateTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftLastUpdateTime));
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCSERVERSTATUS));
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Converts a browseFilter values to the COM equivalent.
/// </summary>
internal static OpcRcw.Da.OPCBROWSEFILTER GetBrowseFilter(TsCDaBrowseFilter input)
{
switch (input)
{
case TsCDaBrowseFilter.All: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL;
case TsCDaBrowseFilter.Branch: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_BRANCHES;
case TsCDaBrowseFilter.Item: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ITEMS;
}
return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL;
}
/// <summary>
/// Converts a browseFilter values from the COM equivalent.
/// </summary>
internal static TsCDaBrowseFilter GetBrowseFilter(OpcRcw.Da.OPCBROWSEFILTER input)
{
switch (input)
{
case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL: return TsCDaBrowseFilter.All;
case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_BRANCHES: return TsCDaBrowseFilter.Branch;
case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ITEMS: return TsCDaBrowseFilter.Item;
}
return TsCDaBrowseFilter.All;
}
/// <summary>
/// Allocates and marshals an array of HRESULT codes.
/// </summary>
internal static IntPtr GetHRESULTs(IOpcResult[] results)
{
// extract error codes from results.
var errors = new int[results.Length];
for (var ii = 0; ii < results.Length; ii++)
{
if (results[ii] != null)
{
errors[ii] = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(results[ii].Result);
}
else
{
errors[ii] = Result.E_INVALIDHANDLE;
}
}
// marshal error codes.
var pErrors = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * results.Length);
Marshal.Copy(errors, 0, pErrors, results.Length);
// return results.
return pErrors;
}
/// <summary>
/// Unmarshals and deallocates an array of OPCBROWSEELEMENT structures.
/// </summary>
internal static TsCDaBrowseElement[] GetBrowseElements(ref IntPtr pInput, int count, bool deallocate)
{
TsCDaBrowseElement[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCDaBrowseElement[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
output[ii] = GetBrowseElement(pos, deallocate);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Allocates and marshals an array of OPCBROWSEELEMENT structures.
/// </summary>
internal static IntPtr GetBrowseElements(TsCDaBrowseElement[] input, bool propertiesRequested)
{
var output = IntPtr.Zero;
if (input != null && input.Length > 0)
{
output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT)) * input.Length);
var pos = output;
for (var ii = 0; ii < input.Length; ii++)
{
var element = GetBrowseElement(input[ii], propertiesRequested);
Marshal.StructureToPtr(element, pos, false);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT)));
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCBROWSEELEMENT structures.
/// </summary>
internal static TsCDaBrowseElement GetBrowseElement(IntPtr pInput, bool deallocate)
{
TsCDaBrowseElement output = null;
if (pInput != IntPtr.Zero)
{
var element = (OpcRcw.Da.OPCBROWSEELEMENT)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCBROWSEELEMENT));
output = new TsCDaBrowseElement();
output.Name = element.szName;
output.ItemPath = null;
output.ItemName = element.szItemID;
output.IsItem = ((element.dwFlagValue & OpcRcw.Da.Constants.OPC_BROWSE_ISITEM) != 0);
output.HasChildren = ((element.dwFlagValue & OpcRcw.Da.Constants.OPC_BROWSE_HASCHILDREN) != 0);
output.Properties = GetItemProperties(ref element.ItemProperties, deallocate);
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCBROWSEELEMENT));
}
}
return output;
}
/// <summary>
/// Allocates and marshals an OPCBROWSEELEMENT structure.
/// </summary>
internal static OpcRcw.Da.OPCBROWSEELEMENT GetBrowseElement(TsCDaBrowseElement input, bool propertiesRequested)
{
var output = new OpcRcw.Da.OPCBROWSEELEMENT();
if (input != null)
{
output.szName = input.Name;
output.szItemID = input.ItemName;
output.dwFlagValue = 0;
output.ItemProperties = GetItemProperties(input.Properties);
if (input.IsItem)
{
output.dwFlagValue |= OpcRcw.Da.Constants.OPC_BROWSE_ISITEM;
}
if (input.HasChildren)
{
output.dwFlagValue |= OpcRcw.Da.Constants.OPC_BROWSE_HASCHILDREN;
}
}
return output;
}
/// <summary>
/// Creates an array of property codes.
/// </summary>
internal static int[] GetPropertyIDs(TsDaPropertyID[] propertyIDs)
{
var output = new ArrayList();
if (propertyIDs != null)
{
foreach (var propertyID in propertyIDs)
{
output.Add(propertyID.Code);
}
}
return (int[])output.ToArray(typeof(int));
}
/// <summary>
/// Creates an array of property codes.
/// </summary>
internal static TsDaPropertyID[] GetPropertyIDs(int[] propertyIDs)
{
var output = new ArrayList();
if (propertyIDs != null)
{
foreach (var propertyID in propertyIDs)
{
output.Add(GetPropertyID(propertyID));
}
}
return (TsDaPropertyID[])output.ToArray(typeof(TsDaPropertyID));
}
/// <summary>
/// Unmarshals and deallocates an array of OPCITEMPROPERTIES structures.
/// </summary>
internal static TsCDaItemPropertyCollection[] GetItemPropertyCollections(ref IntPtr pInput, int count, bool deallocate)
{
TsCDaItemPropertyCollection[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCDaItemPropertyCollection[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
var list = (OpcRcw.Da.OPCITEMPROPERTIES)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMPROPERTIES));
output[ii] = new TsCDaItemPropertyCollection();
output[ii].ItemPath = null;
output[ii].ItemName = null;
output[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(list.hrErrorID);
var properties = GetItemProperties(ref list, deallocate);
if (properties != null)
{
output[ii].AddRange(properties);
}
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMPROPERTIES));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Allocates and marshals an array of OPCITEMPROPERTIES structures.
/// </summary>
internal static IntPtr GetItemPropertyCollections(TsCDaItemPropertyCollection[] input)
{
var output = IntPtr.Zero;
if (input != null && input.Length > 0)
{
output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES)) * input.Length);
var pos = output;
for (var ii = 0; ii < input.Length; ii++)
{
var properties = new OpcRcw.Da.OPCITEMPROPERTIES();
if (input[ii].Count > 0)
{
properties = GetItemProperties((TsCDaItemProperty[])input[ii].ToArray(typeof(TsCDaItemProperty)));
}
properties.hrErrorID = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input[ii].Result);
Marshal.StructureToPtr(properties, pos, false);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES)));
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCITEMPROPERTIES structures.
/// </summary>
internal static TsCDaItemProperty[] GetItemProperties(ref OpcRcw.Da.OPCITEMPROPERTIES input, bool deallocate)
{
TsCDaItemProperty[] output = null;
if (input.dwNumProperties > 0)
{
output = new TsCDaItemProperty[input.dwNumProperties];
var pos = input.pItemProperties;
for (var ii = 0; ii < output.Length; ii++)
{
try
{
output[ii] = GetItemProperty(pos, deallocate);
}
catch (Exception e)
{
output[ii] = new TsCDaItemProperty();
output[ii].Description = e.Message;
output[ii].Result = OpcResult.E_FAIL;
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(input.pItemProperties);
input.pItemProperties = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Allocates and marshals an array of OPCITEMPROPERTIES structures.
/// </summary>
internal static OpcRcw.Da.OPCITEMPROPERTIES GetItemProperties(TsCDaItemProperty[] input)
{
var output = new OpcRcw.Da.OPCITEMPROPERTIES();
if (input != null && input.Length > 0)
{
output.hrErrorID = Result.S_OK;
output.dwReserved = 0;
output.dwNumProperties = input.Length;
output.pItemProperties = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY)) * input.Length);
var error = false;
var pos = output.pItemProperties;
for (var ii = 0; ii < input.Length; ii++)
{
var property = GetItemProperty(input[ii]);
Marshal.StructureToPtr(property, pos, false);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY)));
if (input[ii].Result.Failed())
{
error = true;
}
}
// set flag indicating one or more properties contained errors.
if (error)
{
output.hrErrorID = Result.S_FALSE;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCITEMPROPERTY structures.
/// </summary>
internal static TsCDaItemProperty GetItemProperty(IntPtr pInput, bool deallocate)
{
TsCDaItemProperty output = null;
if (pInput != IntPtr.Zero)
{
try
{
var property = (OpcRcw.Da.OPCITEMPROPERTY)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCITEMPROPERTY));
output = new TsCDaItemProperty();
output.ID = GetPropertyID(property.dwPropertyID);
output.Description = property.szDescription;
output.DataType = Technosoftware.DaAeHdaClient.Com.Interop.GetType((VarEnum)property.vtDataType);
output.ItemPath = null;
output.ItemName = property.szItemID;
output.Value = UnmarshalPropertyValue(output.ID, property.vValue);
output.Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(property.hrErrorID);
// convert COM DA code to unified DA code.
if (property.hrErrorID == Result.E_BADRIGHTS) output.Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS);
}
catch (Exception)
{
}
if (deallocate)
{
Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCITEMPROPERTY));
}
}
return output;
}
/// <summary>
/// Allocates and marshals an arary of OPCITEMPROPERTY structures.
/// </summary>
internal static OpcRcw.Da.OPCITEMPROPERTY GetItemProperty(TsCDaItemProperty input)
{
var output = new OpcRcw.Da.OPCITEMPROPERTY();
if (input != null)
{
output.dwPropertyID = input.ID.Code;
output.szDescription = input.Description;
output.vtDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(input.DataType);
output.vValue = MarshalPropertyValue(input.ID, input.Value);
output.wReserved = 0;
output.hrErrorID = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input.Result);
// set the property data type.
var description = TsDaPropertyDescription.Find(input.ID);
if (description != null)
{
output.vtDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(description.Type);
}
// convert unified DA code to COM DA code.
if (input.Result == OpcResult.Da.E_WRITEONLY) output.hrErrorID = Result.E_BADRIGHTS;
}
return output;
}
/// <remarks/>
public static TsDaPropertyID GetPropertyID(int input)
{
var fields = typeof(TsDaProperty).GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (var field in fields)
{
var property = (TsDaPropertyID)field.GetValue(typeof(TsDaPropertyID));
if (input == property.Code)
{
return property;
}
}
return new TsDaPropertyID(input);
}
/// <summary>
/// Converts the property value to a type supported by the unified interface.
/// </summary>
internal static object UnmarshalPropertyValue(TsDaPropertyID propertyID, object input)
{
if (input == null) return null;
try
{
if (propertyID == TsDaProperty.DATATYPE)
{
return Technosoftware.DaAeHdaClient.Com.Interop.GetType((VarEnum)System.Convert.ToUInt16(input));
}
if (propertyID == TsDaProperty.ACCESSRIGHTS)
{
switch (System.Convert.ToInt32(input))
{
case OpcRcw.Da.Constants.OPC_READABLE: return TsDaAccessRights.Readable;
case OpcRcw.Da.Constants.OPC_WRITEABLE: return TsDaAccessRights.Writable;
case OpcRcw.Da.Constants.OPC_READABLE | OpcRcw.Da.Constants.OPC_WRITEABLE:
{
return TsDaAccessRights.ReadWritable;
}
}
return null;
}
if (propertyID == TsDaProperty.EUTYPE)
{
switch ((OpcRcw.Da.OPCEUTYPE)input)
{
case OpcRcw.Da.OPCEUTYPE.OPC_NOENUM: return TsDaEuType.NoEnum;
case OpcRcw.Da.OPCEUTYPE.OPC_ANALOG: return TsDaEuType.Analog;
case OpcRcw.Da.OPCEUTYPE.OPC_ENUMERATED: return TsDaEuType.Enumerated;
}
return null;
}
if (propertyID == TsDaProperty.QUALITY)
{
return new TsCDaQuality(System.Convert.ToInt16(input));
}
// convert UTC time in property to local time for the unified DA interface.
if (propertyID == TsDaProperty.TIMESTAMP)
{
if (input.GetType() == typeof(DateTime))
{
var dateTime = (DateTime)input;
if (dateTime != DateTime.MinValue)
{
return dateTime.ToLocalTime();
}
return dateTime;
}
}
}
catch { }
return input;
}
/// <summary>
/// Converts the property value to a type supported by COM-DA interface.
/// </summary>
internal static object MarshalPropertyValue(TsDaPropertyID propertyID, object input)
{
if (input == null) return null;
try
{
if (propertyID == TsDaProperty.DATATYPE)
{
return (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType((Type)input);
}
if (propertyID == TsDaProperty.ACCESSRIGHTS)
{
switch ((TsDaAccessRights)input)
{
case TsDaAccessRights.Readable: return OpcRcw.Da.Constants.OPC_READABLE;
case TsDaAccessRights.Writable: return OpcRcw.Da.Constants.OPC_WRITEABLE;
case TsDaAccessRights.ReadWritable: return OpcRcw.Da.Constants.OPC_READABLE | OpcRcw.Da.Constants.OPC_WRITEABLE;
}
return null;
}
if (propertyID == TsDaProperty.EUTYPE)
{
switch ((TsDaEuType)input)
{
case TsDaEuType.NoEnum: return OpcRcw.Da.OPCEUTYPE.OPC_NOENUM;
case TsDaEuType.Analog: return OpcRcw.Da.OPCEUTYPE.OPC_ANALOG;
case TsDaEuType.Enumerated: return OpcRcw.Da.OPCEUTYPE.OPC_ENUMERATED;
}
return null;
}
if (propertyID == TsDaProperty.QUALITY)
{
return ((TsCDaQuality)input).GetCode();
}
// convert local time in property to UTC time for the COM DA interface.
if (propertyID == TsDaProperty.TIMESTAMP)
{
if (input.GetType() == typeof(DateTime))
{
var dateTime = (DateTime)input;
if (dateTime != DateTime.MinValue)
{
return dateTime.ToUniversalTime();
}
return dateTime;
}
}
}
catch { }
return input;
}
/// <summary>
/// Converts an array of item values to an array of OPCITEMVQT objects.
/// </summary>
internal static OpcRcw.Da.OPCITEMVQT[] GetOPCITEMVQTs(TsCDaItemValue[] input)
{
OpcRcw.Da.OPCITEMVQT[] output = null;
if (input != null)
{
output = new OpcRcw.Da.OPCITEMVQT[input.Length];
for (var ii = 0; ii < input.Length; ii++)
{
output[ii] = new OpcRcw.Da.OPCITEMVQT();
var timestamp = (input[ii].TimestampSpecified) ? input[ii].Timestamp : DateTime.MinValue;
output[ii].vDataValue = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANT(input[ii].Value);
output[ii].bQualitySpecified = (input[ii].QualitySpecified) ? 1 : 0;
output[ii].wQuality = (input[ii].QualitySpecified) ? input[ii].Quality.GetCode() : (short)0;
output[ii].bTimeStampSpecified = (input[ii].TimestampSpecified) ? 1 : 0;
output[ii].ftTimeStamp = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(timestamp));
}
}
return output;
}
/// <summary>
/// Converts an array of item objects to an array of GetOPCITEMDEF objects.
/// </summary>
internal static OpcRcw.Da.OPCITEMDEF[] GetOPCITEMDEFs(TsCDaItem[] input)
{
OpcRcw.Da.OPCITEMDEF[] output = null;
if (input != null)
{
output = new OpcRcw.Da.OPCITEMDEF[input.Length];
for (var ii = 0; ii < input.Length; ii++)
{
output[ii] = new OpcRcw.Da.OPCITEMDEF();
output[ii].szItemID = input[ii].ItemName;
output[ii].szAccessPath = (input[ii].ItemPath == null) ? string.Empty : input[ii].ItemPath;
output[ii].bActive = (input[ii].ActiveSpecified) ? ((input[ii].Active) ? 1 : 0) : 1;
output[ii].vtRequestedDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(input[ii].ReqType);
output[ii].hClient = 0;
output[ii].dwBlobSize = 0;
output[ii].pBlob = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCITEMSTATE structures.
/// </summary>
internal static TsCDaItemValue[] GetItemValues(ref IntPtr pInput, int count, bool deallocate)
{
TsCDaItemValue[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new TsCDaItemValue[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
var result = (OpcRcw.Da.OPCITEMSTATE)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMSTATE));
output[ii] = new TsCDaItemValue();
output[ii].ClientHandle = result.hClient;
output[ii].Value = result.vDataValue;
output[ii].Quality = new TsCDaQuality(result.wQuality);
output[ii].QualitySpecified = true;
output[ii].Timestamp = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(result.ftTimeStamp));
output[ii].TimestampSpecified = output[ii].Timestamp != DateTime.MinValue;
if (deallocate)
{
Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMSTATE));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Unmarshals and deallocates a OPCITEMRESULT structures.
/// </summary>
internal static int[] GetItemResults(ref IntPtr pInput, int count, bool deallocate)
{
int[] output = null;
if (pInput != IntPtr.Zero && count > 0)
{
output = new int[count];
var pos = pInput;
for (var ii = 0; ii < count; ii++)
{
var result = (OpcRcw.Da.OPCITEMRESULT)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMRESULT));
output[ii] = result.hServer;
if (deallocate)
{
Marshal.FreeCoTaskMem(result.pBlob);
result.pBlob = IntPtr.Zero;
Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMRESULT));
}
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMRESULT)));
}
if (deallocate)
{
Marshal.FreeCoTaskMem(pInput);
pInput = IntPtr.Zero;
}
}
return output;
}
/// <summary>
/// Allocates and marshals an array of OPCBROWSEELEMENT structures.
/// </summary>
internal static IntPtr GetItemStates(TsCDaItemValueResult[] input)
{
var output = IntPtr.Zero;
if (input != null && input.Length > 0)
{
output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE)) * input.Length);
var pos = output;
for (var ii = 0; ii < input.Length; ii++)
{
var item = new OpcRcw.Da.OPCITEMSTATE();
item.hClient = System.Convert.ToInt32(input[ii].ClientHandle);
item.vDataValue = input[ii].Value;
item.wQuality = (input[ii].QualitySpecified) ? input[ii].Quality.GetCode() : (short)0;
item.ftTimeStamp = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input[ii].Timestamp));
item.wReserved = 0;
Marshal.StructureToPtr(item, pos, false);
pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE)));
}
}
return output;
}
}
}

View File

@@ -0,0 +1,128 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
namespace Da
{
/// <summary>
/// Defines all well known COM DA HRESULT codes.
/// </summary>
internal struct Result
{
/// <remarks/>
public const int S_OK = +0x00000000; // 0x00000000
/// <remarks/>
public const int S_FALSE = +0x00000001; // 0x00000001
/// <remarks/>
public const int E_NOTIMPL = -0x7FFFBFFF; // 0x80004001
/// <remarks/>
public const int E_OUTOFMEMORY = -0x7FF8FFF2; // 0x8007000E
/// <remarks/>
public const int E_INVALIDARG = -0x7FF8FFA9; // 0x80070057
/// <remarks/>
public const int E_NOINTERFACE = -0x7FFFBFFE; // 0x80004002
/// <remarks/>
public const int E_POINTER = -0x7FFFBFFD; // 0x80004003
/// <remarks/>
public const int E_FAIL = -0x7FFFBFFB; // 0x80004005
/// <remarks/>
public const int CONNECT_E_NOCONNECTION = -0x7FFBFE00; // 0x80040200
/// <remarks/>
public const int CONNECT_E_ADVISELIMIT = -0x7FFBFDFF; // 0x80040201
/// <remarks/>
public const int DISP_E_TYPEMISMATCH = -0x7FFDFFFB; // 0x80020005
/// <remarks/>
public const int DISP_E_OVERFLOW = -0x7FFDFFF6; // 0x8002000A
/// <remarks/>
public const int E_INVALIDHANDLE = -0x3FFBFFFF; // 0xC0040001
/// <remarks/>
public const int E_BADTYPE = -0x3FFBFFFC; // 0xC0040004
/// <remarks/>
public const int E_PUBLIC = -0x3FFBFFFB; // 0xC0040005
/// <remarks/>
public const int E_BADRIGHTS = -0x3FFBFFFA; // 0xC0040006
/// <remarks/>
public const int E_UNKNOWNITEMID = -0x3FFBFFF9; // 0xC0040007
/// <remarks/>
public const int E_INVALIDITEMID = -0x3FFBFFF8; // 0xC0040008
/// <remarks/>
public const int E_INVALIDFILTER = -0x3FFBFFF7; // 0xC0040009
/// <remarks/>
public const int E_UNKNOWNPATH = -0x3FFBFFF6; // 0xC004000A
/// <remarks/>
public const int E_RANGE = -0x3FFBFFF5; // 0xC004000B
/// <remarks/>
public const int E_DUPLICATENAME = -0x3FFBFFF4; // 0xC004000C
/// <remarks/>
public const int S_UNSUPPORTEDRATE = +0x0004000D; // 0x0004000D
/// <remarks/>
public const int S_CLAMP = +0x0004000E; // 0x0004000E
/// <remarks/>
public const int S_INUSE = +0x0004000F; // 0x0004000F
/// <remarks/>
public const int E_INVALIDCONFIGFILE = -0x3FFBFFF0; // 0xC0040010
/// <remarks/>
public const int E_NOTFOUND = -0x3FFBFFEF; // 0xC0040011
/// <remarks/>
public const int E_INVALID_PID = -0x3FFBFDFD; // 0xC0040203
/// <remarks/>
public const int E_DEADBANDNOTSET = -0x3FFBFC00; // 0xC0040400
/// <remarks/>
public const int E_DEADBANDNOTSUPPORTED = -0x3FFBFBFF; // 0xC0040401
/// <remarks/>
public const int E_NOBUFFERING = -0x3FFBFBFE; // 0xC0040402
/// <remarks/>
public const int E_INVALIDCONTINUATIONPOINT = -0x3FFBFBFD; // 0xC0040403
/// <remarks/>
public const int S_DATAQUEUEOVERFLOW = +0x00040404; // 0x00040404
/// <remarks/>
public const int E_RATENOTSET = -0x3FFBFBFB; // 0xC0040405
/// <remarks/>
public const int E_NOTSUPPORTED = -0x3FFBFBFA; // 0xC0040406
}
}
namespace Cpx
{
/// <summary>
/// Defines all well known Complex Data HRESULT codes.
/// </summary>
internal struct Result
{
/// <remarks/>
public const int E_TYPE_CHANGED = -0x3FFBFBF9; // 0xC0040407
/// <remarks/>
public const int E_FILTER_DUPLICATE = -0x3FFBFBF8; // 0xC0040408
/// <remarks/>
public const int E_FILTER_INVALID = -0x3FFBFBF7; // 0xC0040409
/// <remarks/>
public const int E_FILTER_ERROR = -0x3FFBFBF6; // 0xC004040A
/// <remarks/>
public const int S_FILTER_NO_DATA = +0x0004040B; // 0xC004040B
}
}
}

View File

@@ -0,0 +1,981 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using System.Collections;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using Technosoftware.DaAeHdaClient.Com.Utilities;
using Technosoftware.DaAeHdaClient.Da;
using Technosoftware.OpcRcw.Da;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Da
{
/// <summary>
/// A .NET wrapper for a COM server that implements the DA server interfaces.
/// </summary>
internal class Server : Com.Server, ITsDaServer
{
#region Fields
/// <summary>
/// The default result filters for the server.
/// </summary>
private int filters_ = (int)TsCDaResultFilter.All | (int)TsCDaResultFilter.ClientHandle;
/// <summary>
/// A table of active subscriptions for the server.
/// </summary>
private readonly Hashtable subscriptions_ = new Hashtable();
#endregion
#region Constructors
/// <summary>
/// Initializes the object.
/// </summary>
internal Server() { }
/// <summary>
/// Initializes the object with the specified COM server.
/// </summary>
internal Server(OpcUrl url, object server)
{
if (url == null) throw new ArgumentNullException(nameof(url));
url_ = (OpcUrl)url.Clone();
server_ = server;
}
#endregion
#region IDisposable Members
/// <summary>
/// Dispose(bool disposing) executes in two distinct scenarios.
/// If disposing equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.
/// If disposing equals false, the method has been called by the
/// runtime from inside the finalizer and you should not reference
/// other objects. Only unmanaged resources can be disposed.
/// </summary>
/// <param name="disposing">If true managed and unmanaged resources can be disposed. If false only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (!disposed_)
{
lock (this)
{
if (disposing)
{
// Release managed resources.
if (server_ != null)
{
// release all groups.
foreach (Subscription subscription in subscriptions_.Values)
{
var methodName = "IOPCServer.RemoveGroup";
// remove subscription from server.
try
{
var state = subscription.GetState();
if (state != null)
{
var server = BeginComCall<IOPCServer>(methodName, true);
server?.RemoveGroup((int)state.ServerHandle, 0);
}
}
catch
{
// Ignore error during Dispose
}
finally
{
EndComCall(methodName);
}
// dispose of the subscription object (disconnects all subscription connections).
subscription.Dispose();
}
// clear subscription table.
subscriptions_.Clear();
}
}
// Release unmanaged resources.
// Set large fields to null.
if (server_ != null)
{
// release the COM server.
Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(server_);
server_ = null;
}
}
// Call Dispose on your base class.
disposed_ = true;
}
base.Dispose(disposing);
}
private bool disposed_;
#endregion
#region Technosoftware.DaAeHdaClient.Com.Server Overrides
/// <summary>
/// Returns the localized text for the specified result code.
/// </summary>
/// <param name="locale">The locale name in the format "[languagecode]-[country/regioncode]".</param>
/// <param name="resultId">The result code identifier.</param>
/// <returns>A message localized for the best match for the requested locale.</returns>
public override string GetErrorText(string locale, OpcResult resultId)
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCServer.GetErrorString";
// invoke COM method.
try
{
var server = BeginComCall<IOPCServer>(methodName, true);
(server).GetErrorString(
resultId.Code,
Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(locale),
out var errorText);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
return errorText;
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCServer.GetErrorString", e);
}
finally
{
EndComCall(methodName);
}
}
}
#endregion
#region Technosoftware.DaAeHdaClient.IOpcServer Members
/// <summary>
/// Returns the filters applied by the server to any item results returned to the client.
/// </summary>
/// <returns>A bit mask indicating which fields should be returned in any item results.</returns>
public int GetResultFilters()
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
return filters_;
}
}
/// <summary>
/// Sets the filters applied by the server to any item results returned to the client.
/// </summary>
/// <param name="filters">A bit mask indicating which fields should be returned in any item results.</param>
public void SetResultFilters(int filters)
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
filters_ = filters;
}
}
/// <summary>
/// Returns the current server status.
/// </summary>
/// <returns>The current server status.</returns>
public OpcServerStatus GetServerStatus()
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCServer.GetStatus";
// initialize arguments.
IntPtr pStatus;
// invoke COM method.
try
{
var server = BeginComCall<IOPCServer>(methodName, true);
(server).GetStatus(out pStatus);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// return status.
return Interop.GetServerStatus(ref pStatus, true);
}
}
/// <summary>
/// Reads the current values for a set of items.
/// </summary>
/// <param name="items">The set of items to read.</param>
/// <returns>The results of the read operation for each item.</returns>
public virtual TsCDaItemValueResult[] Read(TsCDaItem[] items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
lock (this)
{
var methodName = "IOPCItemIO.Read";
if (server_ == null) throw new NotConnectedException();
var count = items.Length;
if (count == 0) throw new ArgumentOutOfRangeException(nameof(items.Length), @"0");
// initialize arguments.
var itemIDs = new string[count];
var maxAges = new int[count];
for (var ii = 0; ii < count; ii++)
{
itemIDs[ii] = items[ii].ItemName;
maxAges[ii] = (items[ii].MaxAgeSpecified) ? items[ii].MaxAge : 0;
}
var pValues = IntPtr.Zero;
var pQualities = IntPtr.Zero;
var pTimestamps = IntPtr.Zero;
var pErrors = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCItemIO>(methodName, true);
server.Read(
count,
itemIDs,
maxAges,
out pValues,
out pQualities,
out pTimestamps,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var values = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref pValues, count, true);
var qualities = Technosoftware.DaAeHdaClient.Com.Interop.GetInt16s(ref pQualities, count, true);
var timestamps = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIMEs(ref pTimestamps, count, true);
var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, count, true);
// pre-fetch the current locale to use for data conversions.
var locale = GetLocale();
// construct result array.
var results = new TsCDaItemValueResult[count];
for (var ii = 0; ii < results.Length; ii++)
{
results[ii] = new TsCDaItemValueResult(items[ii]);
results[ii].ServerHandle = null;
results[ii].Value = values[ii];
results[ii].Quality = new TsCDaQuality(qualities[ii]);
results[ii].QualitySpecified = true;
results[ii].Timestamp = timestamps[ii];
results[ii].TimestampSpecified = timestamps[ii] != DateTime.MinValue;
results[ii].Result = Utilities.Interop.GetResultId(errors[ii]);
results[ii].DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); }
// convert the data type since the server does not support the feature.
if (results[ii].Value != null && items[ii].ReqType != null)
{
try
{
results[ii].Value = ChangeType(values[ii], items[ii].ReqType, locale);
}
catch (Exception e)
{
results[ii].Value = null;
results[ii].Quality = TsCDaQuality.Bad;
results[ii].QualitySpecified = true;
results[ii].Timestamp = DateTime.MinValue;
results[ii].TimestampSpecified = false;
if (e.GetType() == typeof(OverflowException))
{
results[ii].Result = Utilities.Interop.GetResultId(Result.E_RANGE);
}
else
{
results[ii].Result = Utilities.Interop.GetResultId(Result.E_BADTYPE);
}
}
}
// apply request options.
if ((filters_ & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null;
if ((filters_ & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null;
if ((filters_ & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null;
if ((filters_ & (int)TsCDaResultFilter.ItemTime) == 0)
{
results[ii].Timestamp = DateTime.MinValue;
results[ii].TimestampSpecified = false;
}
}
// return results.
return results;
}
}
/// <summary>
/// Writes the value, quality and timestamp for a set of items.
/// </summary>
/// <param name="items">The set of item values to write.</param>
/// <returns>The results of the write operation for each item.</returns>
public virtual OpcItemResult[] Write(TsCDaItemValue[] items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCItemIO.WriteVQT";
var count = items.Length;
if (count == 0) throw new ArgumentOutOfRangeException("items.Length", "0");
// initialize arguments.
var itemIDs = new string[count];
for (var ii = 0; ii < count; ii++)
{
itemIDs[ii] = items[ii].ItemName;
}
var values = Interop.GetOPCITEMVQTs(items);
var pErrors = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCItemIO>(methodName, true);
server.WriteVQT(
count,
itemIDs,
values,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var errors = Utilities.Interop.GetInt32s(ref pErrors, count, true);
// construct result array.
var results = new OpcItemResult[count];
for (var ii = 0; ii < count; ii++)
{
results[ii] = new OpcItemResult(items[ii]);
results[ii].ServerHandle = null;
results[ii].Result = Utilities.Interop.GetResultId(errors[ii]);
results[ii].DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); }
// apply request options.
if ((filters_ & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null;
if ((filters_ & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null;
if ((filters_ & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null;
}
// return results.
return results;
}
}
/// <summary>
/// Creates a new subscription.
/// </summary>
/// <param name="state">The initial state of the subscription.</param>
/// <returns>The new subscription object.</returns>
public ITsCDaSubscription CreateSubscription(TsCDaSubscriptionState state)
{
if (state == null) throw new ArgumentNullException(nameof(state));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCServer.AddGroup";
// copy the subscription state.
var result = (TsCDaSubscriptionState)state.Clone();
// initialize arguments.
var iid = typeof(IOPCItemMgt).GUID;
object group = null;
var serverHandle = 0;
var revisedUpdateRate = 0;
var hDeadband = GCHandle.Alloc(result.Deadband, GCHandleType.Pinned);
// invoke COM method.
try
{
var server = BeginComCall<IOPCServer>(methodName, true);
server.AddGroup(
(result.Name != null) ? result.Name : "",
(result.Active) ? 1 : 0,
result.UpdateRate,
0,
IntPtr.Zero,
hDeadband.AddrOfPinnedObject(),
Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(result.Locale),
out serverHandle,
out revisedUpdateRate,
ref iid,
out group);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
if (hDeadband.IsAllocated)
{
hDeadband.Free();
}
EndComCall(methodName);
}
if (group == null) throw new OpcResultException(OpcResult.E_FAIL, "The subscription was not created.");
methodName = "IOPCGroupStateMgt2.SetKeepAlive";
// set the keep alive rate if requested.
try
{
var keepAlive = 0;
var comObject = BeginComCall<IOPCGroupStateMgt2>(group, methodName, true);
comObject.SetKeepAlive(result.KeepAlive, out keepAlive);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
result.KeepAlive = keepAlive;
}
catch (Exception e1)
{
result.KeepAlive = 0;
ComCallError(methodName, e1);
}
finally
{
EndComCall(methodName);
}
// save server handle.
result.ServerHandle = serverHandle;
// set the revised update rate.
if (revisedUpdateRate > result.UpdateRate)
{
result.UpdateRate = revisedUpdateRate;
}
// create the subscription object.
var subscription = CreateSubscription(group, result, filters_);
// index by server handle.
subscriptions_[serverHandle] = subscription;
// return subscription.
return subscription;
}
}
/// <summary>
/// Cancels a subscription and releases all resources allocated for it.
/// </summary>
/// <param name="subscription">The subscription to cancel.</param>
public void CancelSubscription(ITsCDaSubscription subscription)
{
if (subscription == null) throw new ArgumentNullException(nameof(subscription));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCServer.RemoveGroup";
// validate argument.
if (!typeof(Subscription).IsInstanceOfType(subscription))
{
throw new ArgumentException("Incorrect object type.", nameof(subscription));
}
// get the subscription state.
var state = subscription.GetState();
if (!subscriptions_.ContainsKey(state.ServerHandle))
{
throw new ArgumentException("Handle not found.", nameof(subscription));
}
subscriptions_.Remove(state.ServerHandle);
// release all subscription resources.
subscription.Dispose();
// invoke COM method.
try
{
var server = BeginComCall<IOPCServer>(methodName, true);
server.RemoveGroup((int)state.ServerHandle, 0);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
}
/// <summary>
/// Fetches the children of a branch that meet the filter criteria.
/// </summary>
/// <param name="itemId">The identifier of branch which is the target of the search.</param>
/// <param name="filters">The filters to use to limit the set of child elements returned.</param>
/// <param name="position">An object used to continue a browse that could not be completed.</param>
/// <returns>The set of elements found.</returns>
public virtual TsCDaBrowseElement[] Browse(
OpcItem itemId,
TsCDaBrowseFilters filters,
out TsCDaBrowsePosition position)
{
if (filters == null) throw new ArgumentNullException(nameof(filters));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCBrowse.Browse";
position = null;
// initialize arguments.
var count = 0;
var moreElements = 0;
var pContinuationPoint = IntPtr.Zero;
var pElements = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCBrowse>(methodName, true);
server.Browse(
(itemId != null && itemId.ItemName != null) ? itemId.ItemName : "",
ref pContinuationPoint,
filters.MaxElementsReturned,
Interop.GetBrowseFilter(filters.BrowseFilter),
(filters.ElementNameFilter != null) ? filters.ElementNameFilter : "",
(filters.VendorFilter != null) ? filters.VendorFilter : "",
(filters.ReturnAllProperties) ? 1 : 0,
(filters.ReturnPropertyValues) ? 1 : 0,
(filters.PropertyIDs != null) ? filters.PropertyIDs.Length : 0,
Interop.GetPropertyIDs(filters.PropertyIDs),
out moreElements,
out count,
out pElements);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var elements = Interop.GetBrowseElements(ref pElements, count, true);
var continuationPoint = Marshal.PtrToStringUni(pContinuationPoint);
Marshal.FreeCoTaskMem(pContinuationPoint);
// check if more results exist.
if (moreElements != 0 || (continuationPoint != null && continuationPoint != ""))
{
// allocate new browse position object.
position = new BrowsePosition(itemId, filters, continuationPoint);
}
// process results.
ProcessResults(elements, filters.PropertyIDs);
return elements;
}
}
/// <summary>
/// Continues a browse operation with previously specified search criteria.
/// </summary>
/// <param name="position">An object containing the browse operation state information.</param>
/// <returns>The set of elements found.</returns>
public virtual TsCDaBrowseElement[] BrowseNext(ref TsCDaBrowsePosition position)
{
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCBrowse.Browse";
// check for valid position object.
if (position == null || position.GetType() != typeof(BrowsePosition))
{
throw new BrowseCannotContinueException();
}
var pos = (BrowsePosition)position;
// check for valid continuation point.
if (pos == null || pos.ContinuationPoint == null || pos.ContinuationPoint == "")
{
throw new BrowseCannotContinueException();
}
// initialize arguments.
var count = 0;
var moreElements = 0;
var itemID = ((BrowsePosition)position).ItemID;
var filters = ((BrowsePosition)position).Filters;
var pContinuationPoint = Marshal.StringToCoTaskMemUni(pos.ContinuationPoint);
var pElements = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCBrowse>(methodName, true);
server.Browse(
(itemID != null && itemID.ItemName != null) ? itemID.ItemName : "",
ref pContinuationPoint,
filters.MaxElementsReturned,
Interop.GetBrowseFilter(filters.BrowseFilter),
(filters.ElementNameFilter != null) ? filters.ElementNameFilter : "",
(filters.VendorFilter != null) ? filters.VendorFilter : "",
(filters.ReturnAllProperties) ? 1 : 0,
(filters.ReturnPropertyValues) ? 1 : 0,
(filters.PropertyIDs != null) ? filters.PropertyIDs.Length : 0,
Interop.GetPropertyIDs(filters.PropertyIDs),
out moreElements,
out count,
out pElements);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var elements = Interop.GetBrowseElements(ref pElements, count, true);
pos.ContinuationPoint = Marshal.PtrToStringUni(pContinuationPoint);
Marshal.FreeCoTaskMem(pContinuationPoint);
// check if more no results exist.
if (moreElements == 0 && (pos.ContinuationPoint == null || pos.ContinuationPoint == ""))
{
position = null;
}
// process results.
ProcessResults(elements, filters.PropertyIDs);
return elements;
}
}
/// <summary>
/// Returns the item properties for a set of items.
/// </summary>
/// <param name="itemIds">A list of item identifiers.</param>
/// <param name="propertyIDs">A list of properties to fetch for each item.</param>
/// <param name="returnValues">Whether the property values should be returned with the properties.</param>
/// <returns>A list of properties for each item.</returns>
public virtual TsCDaItemPropertyCollection[] GetProperties(
OpcItem[] itemIds,
TsDaPropertyID[] propertyIDs,
bool returnValues)
{
if (itemIds == null) throw new ArgumentNullException(nameof(itemIds));
lock (this)
{
if (server_ == null) throw new NotConnectedException();
var methodName = "IOPCBrowse.GetProperties";
// initialize arguments.
var pItemIDs = new string[itemIds.Length];
for (var ii = 0; ii < itemIds.Length; ii++)
{
pItemIDs[ii] = itemIds[ii].ItemName;
}
var pPropertyLists = IntPtr.Zero;
// invoke COM method.
try
{
var server = BeginComCall<IOPCBrowse>(methodName, true);
server.GetProperties(
itemIds.Length,
pItemIDs,
(returnValues) ? 1 : 0,
(propertyIDs != null) ? propertyIDs.Length : 0,
Interop.GetPropertyIDs(propertyIDs),
out pPropertyLists);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var resultLists = Interop.GetItemPropertyCollections(ref pPropertyLists, itemIds.Length, true);
// replace integer codes with qnames passed in.
if (propertyIDs != null && propertyIDs.Length > 0)
{
foreach (var resultList in resultLists)
{
for (var ii = 0; ii < resultList.Count; ii++)
{
resultList[ii].ID = propertyIDs[ii];
}
}
}
// return the results.
return resultLists;
}
}
#endregion
#region Private Methods
/// <summary>
/// Converts a value to the specified type using the specified locale.
/// </summary>
protected object ChangeType(object source, Type type, string locale)
{
var culture = Thread.CurrentThread.CurrentCulture;
// override the current thread culture to ensure conversions happen correctly.
try
{
Thread.CurrentThread.CurrentCulture = new CultureInfo(locale);
}
catch
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
}
try
{
var result = OpcConvert.ChangeType(source, type);
// check for overflow converting to float.
if (typeof(float) == type)
{
if (float.IsInfinity(Convert.ToSingle(result)))
{
throw new OverflowException();
}
}
return result;
}
// restore the current thread culture after conversion.
finally
{
Thread.CurrentThread.CurrentCulture = culture;
}
}
/// <summary>
/// Creates a new instance of a subscription.
/// </summary>
protected virtual Subscription CreateSubscription(
object group,
TsCDaSubscriptionState state,
int filters)
{
return new Subscription(group, state, filters);
}
/// <summary>
/// Updates the properties to convert COM values to OPC .NET API results.
/// </summary>
private void ProcessResults(TsCDaBrowseElement[] elements, TsDaPropertyID[] propertyIds)
{
// check for null.
if (elements == null)
{
return;
}
// process each element.
foreach (var element in elements)
{
// check if no properties.
if (element.Properties == null)
{
continue;
}
// process each property.
foreach (var property in element.Properties)
{
// replace the property ids which on contain the codes with the proper qualified names passed in.
if (propertyIds != null)
{
foreach (var propertyId in propertyIds)
{
if (property.ID.Code == propertyId.Code)
{
property.ID = propertyId;
break;
}
}
}
}
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,583 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using System.Collections;
using Technosoftware.DaAeHdaClient.Com.Da;
using Technosoftware.DaAeHdaClient.Com.Utilities;
using Technosoftware.DaAeHdaClient.Da;
using Technosoftware.OpcRcw.Da;
#endregion
namespace Technosoftware.DaAeHdaClient.Com.Da20
{
/// <summary>
/// An in-process wrapper for a remote OPC Data Access 2.0X subscription.
/// </summary>
internal class Subscription : Technosoftware.DaAeHdaClient.Com.Da.Subscription
{
#region Constructors
/// <summary>
/// Initializes a new instance of a subscription.
/// </summary>
internal Subscription(object subscription, TsCDaSubscriptionState state, int filters) :
base(subscription, state, filters)
{
}
#endregion
#region ISubscription Members
/// <summary>
/// Returns the current state of the subscription.
/// </summary>
/// <returns>The current state of the subscription.</returns>
public override TsCDaSubscriptionState GetState()
{
if (subscription_ == null) throw new NotConnectedException();
lock (lock_)
{
var methodName = "IOPCGroupStateMgt.GetState";
var state = new TsCDaSubscriptionState { ClientHandle = _handle };
string name = null;
try
{
var active = 0;
var updateRate = 0;
float deadband = 0;
var timebias = 0;
var localeID = 0;
var clientHandle = 0;
var serverHandle = 0;
var subscription = BeginComCall<IOPCGroupStateMgt>(methodName, true);
subscription.GetState(
out updateRate,
out active,
out name,
out timebias,
out deadband,
out localeID,
out clientHandle,
out serverHandle);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
state.Name = name;
state.ServerHandle = serverHandle;
state.Active = active != 0;
state.UpdateRate = updateRate;
state.TimeBias = timebias;
state.Deadband = deadband;
state.Locale = Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(localeID);
// cache the name separately.
name_ = state.Name;
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
state.KeepAlive = 0;
return state;
}
}
/// <summary>
/// Tells the server to send an data change update for all subscription items containing the cached values.
/// </summary>
public override void Refresh()
{
if (subscription_ == null) throw new NotConnectedException();
lock (lock_)
{
var methodName = "IOPCAsyncIO2.Refresh2";
try
{
var cancelID = 0;
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.Refresh2(OPCDATASOURCE.OPC_DS_CACHE, ++_counter, out cancelID);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
}
/// <summary>
/// Sets whether data change callbacks are enabled.
/// </summary>
public override void SetEnabled(bool enabled)
{
if (subscription_ == null) throw new NotConnectedException();
lock (lock_)
{
var methodName = "IOPCAsyncIO2.SetEnable";
try
{
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.SetEnable((enabled) ? 1 : 0);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
}
/// <summary>
/// Gets whether data change callbacks are enabled.
/// </summary>
public override bool GetEnabled()
{
if (subscription_ == null) throw new NotConnectedException();
lock (lock_)
{
var methodName = "IOPCAsyncIO2.GetEnable";
try
{
var enabled = 0;
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.GetEnable(out enabled);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
return enabled != 0;
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
}
#endregion
#region Private and Protected Members
/// <summary>
/// Reads a set of items using DA2.0 interfaces.
/// </summary>
protected override TsCDaItemValueResult[] Read(OpcItem[] itemIDs, TsCDaItem[] items)
{
if (subscription_ == null) throw new NotConnectedException();
// create result list.
var results = new TsCDaItemValueResult[itemIDs.Length];
// separate into cache reads and device reads.
var cacheReads = new ArrayList();
var deviceReads = new ArrayList();
for (var ii = 0; ii < itemIDs.Length; ii++)
{
results[ii] = new TsCDaItemValueResult(itemIDs[ii]);
if (items[ii].MaxAgeSpecified && (items[ii].MaxAge < 0 || items[ii].MaxAge == int.MaxValue))
{
cacheReads.Add(results[ii]);
}
else
{
deviceReads.Add(results[ii]);
}
}
// read items from cache.
if (cacheReads.Count > 0)
{
Read((TsCDaItemValueResult[])cacheReads.ToArray(typeof(TsCDaItemValueResult)), true);
}
// read items from device.
if (deviceReads.Count > 0)
{
Read((TsCDaItemValueResult[])deviceReads.ToArray(typeof(TsCDaItemValueResult)), false);
}
// return results.
return results;
}
/// <summary>
/// Reads a set of values.
/// </summary>
private void Read(TsCDaItemValueResult[] items, bool cache)
{
if (items.Length == 0) return;
// marshal input parameters.
var serverHandles = new int[items.Length];
for (var ii = 0; ii < items.Length; ii++)
{
serverHandles[ii] = (int)items[ii].ServerHandle;
}
// initialize output parameters.
var pValues = IntPtr.Zero;
var pErrors = IntPtr.Zero;
var methodName = "IOPCSyncIO.Read";
try
{
var subscription = BeginComCall<IOPCSyncIO>(methodName, true);
subscription.Read(
(cache) ? OPCDATASOURCE.OPC_DS_CACHE : OPCDATASOURCE.OPC_DS_DEVICE,
items.Length,
serverHandles,
out pValues,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal output parameters.
var values = Technosoftware.DaAeHdaClient.Com.Da.Interop.GetItemValues(ref pValues, items.Length, true);
var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true);
// construct results list.
for (var ii = 0; ii < items.Length; ii++)
{
items[ii].Result = Utilities.Interop.GetResultId(errors[ii]);
items[ii].DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { items[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); }
if (items[ii].Result.Succeeded())
{
items[ii].Value = values[ii].Value;
items[ii].Quality = values[ii].Quality;
items[ii].QualitySpecified = values[ii].QualitySpecified;
items[ii].Timestamp = values[ii].Timestamp;
items[ii].TimestampSpecified = values[ii].TimestampSpecified;
}
}
}
/// <summary>
/// Writes a set of items using DA2.0 interfaces.
/// </summary>
protected override OpcItemResult[] Write(OpcItem[] itemIDs, TsCDaItemValue[] items)
{
if (subscription_ == null) throw new NotConnectedException();
// create result list.
var results = new OpcItemResult[itemIDs.Length];
// construct list of valid items to write.
var writeItems = new ArrayList(itemIDs.Length);
var writeValues = new ArrayList(itemIDs.Length);
for (var ii = 0; ii < items.Length; ii++)
{
results[ii] = new OpcItemResult(itemIDs[ii]);
if (items[ii].QualitySpecified || items[ii].TimestampSpecified)
{
results[ii].Result = OpcResult.Da.E_NO_WRITEQT;
results[ii].DiagnosticInfo = null;
continue;
}
writeItems.Add(results[ii]);
writeValues.Add(items[ii]);
}
// check if there is nothing to do.
if (writeItems.Count == 0)
{
return results;
}
// initialize input parameters.
var serverHandles = new int[writeItems.Count];
var values = new object[writeItems.Count];
for (var ii = 0; ii < serverHandles.Length; ii++)
{
serverHandles[ii] = (int)((OpcItemResult)writeItems[ii]).ServerHandle;
values[ii] = Utilities.Interop.GetVARIANT(((TsCDaItemValue)writeValues[ii]).Value);
}
var pErrors = IntPtr.Zero;
// write item values.
var methodName = "IOPCSyncIO.Write";
try
{
var subscription = BeginComCall<IOPCSyncIO>(methodName, true);
subscription.Write(
writeItems.Count,
serverHandles,
values,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// unmarshal results.
var errors = Utilities.Interop.GetInt32s(ref pErrors, writeItems.Count, true);
for (var ii = 0; ii < writeItems.Count; ii++)
{
var result = (OpcItemResult)writeItems[ii];
result.Result = Utilities.Interop.GetResultId(errors[ii]);
result.DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); }
}
// return results.
return results;
}
/// <summary>
/// Begins an asynchronous read of a set of items using DA2.0 interfaces.
/// </summary>
protected override OpcItemResult[] BeginRead(
OpcItem[] itemIDs,
TsCDaItem[] items,
int requestID,
out int cancelID)
{
var methodName = "IOPCAsyncIO2.Read";
try
{
// marshal input parameters.
var serverHandles = new int[itemIDs.Length];
for (var ii = 0; ii < itemIDs.Length; ii++)
{
serverHandles[ii] = (int)itemIDs[ii].ServerHandle;
}
// initialize output parameters.
var pErrors = IntPtr.Zero;
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.Read(
itemIDs.Length,
serverHandles,
requestID,
out cancelID,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
// unmarshal output parameters.
var errors = Utilities.Interop.GetInt32s(ref pErrors, itemIDs.Length, true);
// create item results.
var results = new OpcItemResult[itemIDs.Length];
for (var ii = 0; ii < itemIDs.Length; ii++)
{
results[ii] = new OpcItemResult(itemIDs[ii]);
results[ii].Result = Utilities.Interop.GetResultId(errors[ii]);
results[ii].DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); }
}
// return results.
return results;
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
}
/// <summary>
/// Begins an asynchronous write for a set of items using DA2.0 interfaces.
/// </summary>
protected override OpcItemResult[] BeginWrite(
OpcItem[] itemIDs,
TsCDaItemValue[] items,
int requestID,
out int cancelID)
{
cancelID = 0;
var validItems = new ArrayList();
var validValues = new ArrayList();
// construct initial result list.
var results = new OpcItemResult[itemIDs.Length];
for (var ii = 0; ii < itemIDs.Length; ii++)
{
results[ii] = new OpcItemResult(itemIDs[ii]);
results[ii].Result = OpcResult.S_OK;
results[ii].DiagnosticInfo = null;
if (items[ii].QualitySpecified || items[ii].TimestampSpecified)
{
results[ii].Result = OpcResult.Da.E_NO_WRITEQT;
results[ii].DiagnosticInfo = null;
continue;
}
validItems.Add(results[ii]);
validValues.Add(Utilities.Interop.GetVARIANT(items[ii].Value));
}
// check if any valid items exist.
if (validItems.Count == 0)
{
return results;
}
var methodName = "IOPCAsyncIO2.Write";
try
{
// initialize input parameters.
var serverHandles = new int[validItems.Count];
for (var ii = 0; ii < validItems.Count; ii++)
{
serverHandles[ii] = (int)((OpcItemResult)validItems[ii]).ServerHandle;
}
// write to sever.
var pErrors = IntPtr.Zero;
var subscription = BeginComCall<IOPCAsyncIO2>(methodName, true);
subscription.Write(
validItems.Count,
serverHandles,
(object[])validValues.ToArray(typeof(object)),
requestID,
out cancelID,
out pErrors);
if (DCOMCallWatchdog.IsCancelled)
{
throw new Exception($"{methodName} call was cancelled due to response timeout");
}
// unmarshal results.
var errors = Utilities.Interop.GetInt32s(ref pErrors, validItems.Count, true);
// create result list.
for (var ii = 0; ii < validItems.Count; ii++)
{
var result = (OpcItemResult)validItems[ii];
result.Result = Utilities.Interop.GetResultId(errors[ii]);
result.DiagnosticInfo = null;
// convert COM code to unified DA code.
if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); }
}
}
catch (Exception e)
{
ComCallError(methodName, e);
throw Utilities.Interop.CreateException(methodName, e);
}
finally
{
EndComCall(methodName);
}
// return results.
return results;
}
#endregion
}
}

View File

@@ -0,0 +1,128 @@
#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
//-----------------------------------------------------------------------------
// Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
// Web: https://www.technosoftware.com
//
// The source code in this file is covered under a dual-license scenario:
// - Owner of a purchased license: SCLA 1.0
// - GPL V3: everybody else
//
// SCLA license terms accompanied with this source code.
// See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf
//
// GNU General Public License as published by the Free Software Foundation;
// version 3 of the License are accompanied with this source code.
// See https://technosoftware.com/license/GPLv3License.txt
//
// This source code is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
//-----------------------------------------------------------------------------
#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved
#region Using Directives
using System;
using System.Runtime.InteropServices;
using Technosoftware.OpcRcw.Comn;
#endregion
namespace Technosoftware.DaAeHdaClient.Com
{
/// <summary>
/// A wrapper for the COM IEnumString interface.
/// </summary>
internal class EnumString : IDisposable
{
/// <summary>
/// A reference to the remote COM object.
/// </summary>
private IEnumString m_enumerator = null;
/// <summary>
/// Initializes the object with an enumerator.
/// </summary>
public EnumString(object enumerator)
{
m_enumerator = (IEnumString)enumerator;
}
/// <summary>
/// Releases the remote COM object.
/// </summary>
public void Dispose()
{
Utilities.Interop.ReleaseServer(m_enumerator);
m_enumerator = null;
}
//=====================================================================
// IEnumString
/// <summary>
/// Fetches the next subscription of strings.
/// </summary>
public string[] Next(int count)
{
try
{
// create buffer.
var buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)) * count);
try
{
// fetch next subscription of strings.
var fetched = 0;
m_enumerator.RemoteNext(
count,
buffer,
out fetched);
// return empty array if at end of list.
if (fetched == 0)
{
return new string[0];
}
return Interop.GetUnicodeStrings(ref buffer, fetched, true);
}
finally
{
Marshal.FreeCoTaskMem(buffer);
}
}
// return null on any error.
catch (Exception)
{
return null;
}
}
/// <summary>
/// Skips a number of strings.
/// </summary>
public void Skip(int count)
{
m_enumerator.Skip(count);
}
/// <summary>
/// Sets pointer to the start of the list.
/// </summary>
public void Reset()
{
m_enumerator.Reset();
}
/// <summary>
/// Clones the enumerator.
/// </summary>
public EnumString Clone()
{
IEnumString enumerator;
m_enumerator.Clone(out enumerator);
return new EnumString(enumerator);
}
}
}

Some files were not shown because too many files have changed in this diff Show More