mirror of
https://github.com/evopro-ag/Sharp7Reactive.git
synced 2025-12-16 11:42:52 +00:00
imported Sharp7.Rx
This commit is contained in:
10
Sharp7.Rx/Enums/ConnectionState.cs
Normal file
10
Sharp7.Rx/Enums/ConnectionState.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Sharp7.Rx.Enums
|
||||
{
|
||||
public enum ConnectionState
|
||||
{
|
||||
Initial,
|
||||
Connected,
|
||||
DisconnectedByUser,
|
||||
ConnectionLost
|
||||
}
|
||||
}
|
||||
10
Sharp7.Rx/Enums/CpuType.cs
Normal file
10
Sharp7.Rx/Enums/CpuType.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Sharp7.Rx.Enums
|
||||
{
|
||||
internal enum CpuType
|
||||
{
|
||||
S7_300,
|
||||
S7_400,
|
||||
S7_1200,
|
||||
S7_1500
|
||||
}
|
||||
}
|
||||
13
Sharp7.Rx/Enums/DbType.cs
Normal file
13
Sharp7.Rx/Enums/DbType.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Sharp7.Rx.Enums
|
||||
{
|
||||
internal enum DbType
|
||||
{
|
||||
Bit,
|
||||
String,
|
||||
Byte,
|
||||
Double,
|
||||
Integer,
|
||||
DInteger,
|
||||
ULong
|
||||
}
|
||||
}
|
||||
10
Sharp7.Rx/Enums/Operand.cs
Normal file
10
Sharp7.Rx/Enums/Operand.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Sharp7.Rx.Enums
|
||||
{
|
||||
internal enum Operand : byte
|
||||
{
|
||||
Input = 69,
|
||||
Output = 65,
|
||||
Marker = 77,
|
||||
Db = 68,
|
||||
}
|
||||
}
|
||||
8
Sharp7.Rx/Enums/TransmissionMode.cs
Normal file
8
Sharp7.Rx/Enums/TransmissionMode.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Sharp7.Rx.Enums
|
||||
{
|
||||
public enum TransmissionMode
|
||||
{
|
||||
Cyclic = 3,
|
||||
OnChange = 4,
|
||||
}
|
||||
}
|
||||
13
Sharp7.Rx/Extensions/DisposableExtensions.cs
Normal file
13
Sharp7.Rx/Extensions/DisposableExtensions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Sharp7.Rx.Extensions
|
||||
{
|
||||
internal static class DisposableExtensions
|
||||
{
|
||||
public static void AddDisposableTo(this IDisposable disposable, CompositeDisposable compositeDisposable)
|
||||
{
|
||||
compositeDisposable.Add(disposable);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Sharp7.Rx/Extensions/ObservableExtensions.cs
Normal file
24
Sharp7.Rx/Extensions/ObservableExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sharp7.Rx.Extensions
|
||||
{
|
||||
internal static class ObservableExtensions
|
||||
{
|
||||
public static IObservable<Unit> Select<TSource>(this IObservable<TSource> source, Func<TSource, Task> selector)
|
||||
{
|
||||
return source
|
||||
.Select(x => Observable.FromAsync(async () => await selector(x)))
|
||||
.Concat();
|
||||
}
|
||||
|
||||
public static IObservable<TResult> Select<TSource, TResult>(this IObservable<TSource> source, Func<TSource, Task<TResult>> selector)
|
||||
{
|
||||
return source
|
||||
.Select(x => Observable.FromAsync(async () => await selector(x)))
|
||||
.Concat();
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Sharp7.Rx/Extensions/PlcExtensions.cs
Normal file
67
Sharp7.Rx/Extensions/PlcExtensions.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Sharp7.Rx.Enums;
|
||||
using Sharp7.Rx.Interfaces;
|
||||
|
||||
namespace Sharp7.Rx.Extensions
|
||||
{
|
||||
public static class PlcExtensions
|
||||
{
|
||||
public static IObservable<TReturn> CreateDatatransferWithHandshake<TReturn>(this IPlc plc, string triggerAddress, string ackTriggerAddress, Func<IPlc, Task<TReturn>> readData, bool initialTransfer)
|
||||
{
|
||||
return Observable.Create<TReturn>(async observer =>
|
||||
{
|
||||
var subscriptions = new CompositeDisposable();
|
||||
|
||||
var notification = plc
|
||||
.CreateNotification<bool>(triggerAddress, TransmissionMode.OnChange, TimeSpan.Zero)
|
||||
.Publish()
|
||||
.RefCount();
|
||||
|
||||
if (initialTransfer)
|
||||
{
|
||||
var initialValue = await ReadData(plc, readData);
|
||||
observer.OnNext(initialValue);
|
||||
}
|
||||
|
||||
notification
|
||||
.Where(trigger => trigger)
|
||||
.Select(_ => ReadDataAndAcknowlodge(plc, readData, ackTriggerAddress))
|
||||
.Subscribe(observer)
|
||||
.AddDisposableTo(subscriptions);
|
||||
|
||||
notification
|
||||
.Where(trigger => !trigger)
|
||||
.Select(_ => plc.SetValue(ackTriggerAddress, false))
|
||||
.Subscribe()
|
||||
.AddDisposableTo(subscriptions);
|
||||
|
||||
return subscriptions;
|
||||
});
|
||||
}
|
||||
|
||||
public static IObservable<TReturn> CreateDatatransferWithHandshake<TReturn>(this IPlc plc, string triggerAddress, string ackTriggerAddress, Func<IPlc, Task<TReturn>> readData)
|
||||
{
|
||||
return CreateDatatransferWithHandshake(plc, triggerAddress, ackTriggerAddress, readData, false);
|
||||
}
|
||||
|
||||
private static async Task<TReturn> ReadData<TReturn>(IPlc plc, Func<IPlc, Task<TReturn>> receiveData)
|
||||
{
|
||||
return await receiveData(plc);
|
||||
}
|
||||
|
||||
private static async Task<TReturn> ReadDataAndAcknowlodge<TReturn>(IPlc plc, Func<IPlc, Task<TReturn>> readData, string ackTriggerAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await ReadData(plc, readData);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await plc.SetValue(ackTriggerAddress, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Sharp7.Rx/Interfaces/IPlc.cs
Normal file
13
Sharp7.Rx/Interfaces/IPlc.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Sharp7.Rx.Enums;
|
||||
|
||||
namespace Sharp7.Rx.Interfaces
|
||||
{
|
||||
public interface IPlc : IDisposable
|
||||
{
|
||||
IObservable<TValue> CreateNotification<TValue>(string variableName, TransmissionMode transmissionMode, TimeSpan cycleSpan);
|
||||
Task SetValue<TValue>(string variableName, TValue value);
|
||||
Task<TValue> GetValue<TValue>(string variableName);
|
||||
}
|
||||
}
|
||||
22
Sharp7.Rx/Interfaces/IS7Connector.cs
Normal file
22
Sharp7.Rx/Interfaces/IS7Connector.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Sharp7.Rx.Enums;
|
||||
|
||||
namespace Sharp7.Rx.Interfaces
|
||||
{
|
||||
internal interface IS7Connector : IDisposable
|
||||
{
|
||||
IObservable<ConnectionState> ConnectionState { get; }
|
||||
Task InitializeAsync();
|
||||
|
||||
Task<bool> Connect();
|
||||
Task Disconnect();
|
||||
|
||||
Task<bool> ReadBit(Operand operand, ushort byteAddress, byte bitAdress, ushort dbNr, CancellationToken token);
|
||||
Task<byte[]> ReadBytes(Operand operand, ushort startByteAddress, ushort bytesToRead, ushort dBNr, CancellationToken token);
|
||||
|
||||
Task<bool> WriteBit(Operand operand, ushort startByteAddress, byte bitAdress, bool value, ushort dbNr, CancellationToken token);
|
||||
Task<ushort> WriteBytes(Operand operand, ushort startByteAdress, byte[] data, ushort dBNr, CancellationToken token);
|
||||
}
|
||||
}
|
||||
117
Sharp7.Rx/Resources/StringResources.Designer.cs
generated
Normal file
117
Sharp7.Rx/Resources/StringResources.Designer.cs
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Sharp7.Rx.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class StringResources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal StringResources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Sharp7.Rx.Resources.StringResources", typeof(StringResources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to S7 driver could not be initialized.
|
||||
/// </summary>
|
||||
internal static string StrErrorS7DriverCouldNotBeInitialized {
|
||||
get {
|
||||
return ResourceManager.GetString("StrErrorS7DriverCouldNotBeInitialized", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to S7 driver is not initialized..
|
||||
/// </summary>
|
||||
internal static string StrErrorS7DriverNotInitialized {
|
||||
get {
|
||||
return ResourceManager.GetString("StrErrorS7DriverNotInitialized", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to TCP/IP connection established..
|
||||
/// </summary>
|
||||
internal static string StrInfoConnectionEstablished {
|
||||
get {
|
||||
return ResourceManager.GetString("StrInfoConnectionEstablished", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Trying to connect to PLC ({2}) '{0}', CPU slot {1}....
|
||||
/// </summary>
|
||||
internal static string StrInfoTryConnecting {
|
||||
get {
|
||||
return ResourceManager.GetString("StrInfoTryConnecting", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error while reading data from plc..
|
||||
/// </summary>
|
||||
internal static string StrLogErrorReadingDataFromPlc {
|
||||
get {
|
||||
return ResourceManager.GetString("StrLogErrorReadingDataFromPlc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Communication error discovered. Reconnect is in progress....
|
||||
/// </summary>
|
||||
internal static string StrLogWarningCommunictionErrorReconnecting {
|
||||
get {
|
||||
return ResourceManager.GetString("StrLogWarningCommunictionErrorReconnecting", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
138
Sharp7.Rx/Resources/StringResources.resx
Normal file
138
Sharp7.Rx/Resources/StringResources.resx
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="StrLogErrorReadingDataFromPlc" xml:space="preserve">
|
||||
<value>Error while reading data from plc.</value>
|
||||
</data>
|
||||
<data name="StrLogWarningCommunictionErrorReconnecting" xml:space="preserve">
|
||||
<value>Communication error discovered. Reconnect is in progress...</value>
|
||||
</data>
|
||||
<data name="StrErrorS7DriverNotInitialized" xml:space="preserve">
|
||||
<value>S7 driver is not initialized.</value>
|
||||
</data>
|
||||
<data name="StrInfoTryConnecting" xml:space="preserve">
|
||||
<value>Trying to connect to PLC ({2}) '{0}', CPU slot {1}...</value>
|
||||
</data>
|
||||
<data name="StrInfoConnectionEstablished" xml:space="preserve">
|
||||
<value>TCP/IP connection established.</value>
|
||||
</data>
|
||||
<data name="StrErrorS7DriverCouldNotBeInitialized" xml:space="preserve">
|
||||
<value>S7 driver could not be initialized</value>
|
||||
</data>
|
||||
</root>
|
||||
14
Sharp7.Rx/S7VariableAddress.cs
Normal file
14
Sharp7.Rx/S7VariableAddress.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Sharp7.Rx.Enums;
|
||||
|
||||
namespace Sharp7.Rx
|
||||
{
|
||||
internal class S7VariableAddress
|
||||
{
|
||||
public Operand Operand { get; set; }
|
||||
public ushort DbNr { get; set; }
|
||||
public ushort Start { get; set; }
|
||||
public ushort Length { get; set; }
|
||||
public byte Bit { get; set; }
|
||||
public DbType Type { get; set; }
|
||||
}
|
||||
}
|
||||
91
Sharp7.Rx/S7VariableNameParser.cs
Normal file
91
Sharp7.Rx/S7VariableNameParser.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Sharp7.Rx.Enums;
|
||||
|
||||
namespace Sharp7.Rx
|
||||
{
|
||||
internal class S7VaraibleNameParser
|
||||
{
|
||||
private readonly Regex regex = new Regex(@"^(?<operand>db{1})(?<dbNr>\d{1,4})\.?(?<type>dbx|x|s|string|b|dbb|d|int|dbw|w|dint|dul|dulint|dulong|){1}(?<start>\d+)(\.(?<bitOrLength>\d+))?$", RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly Dictionary<string, DbType> types = new Dictionary<string, DbType>
|
||||
{
|
||||
{"x", DbType.Bit},
|
||||
{"dbx", DbType.Bit},
|
||||
{"s", DbType.String},
|
||||
{"string", DbType.String},
|
||||
{"b", DbType.Byte},
|
||||
{"dbb", DbType.Byte},
|
||||
{"d", DbType.Double},
|
||||
{"int", DbType.Integer},
|
||||
{"dint", DbType.DInteger},
|
||||
{"w", DbType.Integer},
|
||||
{"dbw", DbType.Integer},
|
||||
{"dul", DbType.ULong },
|
||||
{"dulint", DbType.ULong },
|
||||
{"dulong", DbType.ULong }
|
||||
};
|
||||
|
||||
|
||||
public S7VariableAddress Parse(string input)
|
||||
{
|
||||
var match = regex.Match(input);
|
||||
if (match.Success)
|
||||
{
|
||||
var operand = (Operand)Enum.Parse(typeof(Operand), match.Groups["operand"].Value, true);
|
||||
var dbNr = ushort.Parse(match.Groups["dbNr"].Value, NumberStyles.Integer);
|
||||
var start = ushort.Parse(match.Groups["start"].Value, NumberStyles.Integer);
|
||||
var type = ParseType(match.Groups["type"].Value);
|
||||
|
||||
var s7VariableAddress = new S7VariableAddress
|
||||
{
|
||||
Operand = operand,
|
||||
DbNr = dbNr,
|
||||
Start = start,
|
||||
Type = type,
|
||||
};
|
||||
|
||||
if (type == DbType.Bit)
|
||||
{
|
||||
s7VariableAddress.Length = 1;
|
||||
s7VariableAddress.Bit = byte.Parse(match.Groups["bitOrLength"].Value);
|
||||
}
|
||||
else if (type == DbType.Byte)
|
||||
{
|
||||
s7VariableAddress.Length = match.Groups["bitOrLength"].Success ? ushort.Parse(match.Groups["bitOrLength"].Value) : (ushort)1;
|
||||
}
|
||||
else if (type == DbType.String)
|
||||
{
|
||||
s7VariableAddress.Length = match.Groups["bitOrLength"].Success ? ushort.Parse(match.Groups["bitOrLength"].Value) : (ushort)0;
|
||||
}
|
||||
else if (type == DbType.Integer)
|
||||
{
|
||||
s7VariableAddress.Length = 2;
|
||||
}
|
||||
else if (type == DbType.DInteger)
|
||||
{
|
||||
s7VariableAddress.Length = 4;
|
||||
}
|
||||
else if (type == DbType.ULong)
|
||||
{
|
||||
s7VariableAddress.Length = 8;
|
||||
}
|
||||
|
||||
return s7VariableAddress;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private DbType ParseType(string value)
|
||||
{
|
||||
return types
|
||||
.Where(pair => pair.Key.Equals(value, StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(pair => pair.Value)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Sharp7.Rx/Sharp7.Rx.csproj
Normal file
27
Sharp7.Rx/Sharp7.Rx.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Sharp7" Version="1.0.13" />
|
||||
<PackageReference Include="System.Reactive" Version="4.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\StringResources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>StringResources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\StringResources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>StringResources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
266
Sharp7.Rx/Sharp7Connector.cs
Normal file
266
Sharp7.Rx/Sharp7Connector.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Sharp7.Rx.Enums;
|
||||
using Sharp7.Rx.Interfaces;
|
||||
using Sharp7.Rx.Resources;
|
||||
|
||||
namespace Sharp7.Rx
|
||||
{
|
||||
internal class Sharp7Connector : IS7Connector
|
||||
{
|
||||
private readonly BehaviorSubject<ConnectionState> connectionStateSubject = new BehaviorSubject<ConnectionState>(Enums.ConnectionState.Initial);
|
||||
private readonly CompositeDisposable disposables = new CompositeDisposable();
|
||||
private readonly TaskScheduler scheduler = TaskScheduler.Current;
|
||||
private readonly string ipAddress;
|
||||
private readonly int rackNr;
|
||||
private readonly int cpuSlotNr;
|
||||
|
||||
private S7Client sharp7;
|
||||
private bool disposed;
|
||||
|
||||
public Sharp7Connector(string ipAddress, int rackNr = 0, int cpuSlotNr = 2)
|
||||
{
|
||||
this.ipAddress = ipAddress;
|
||||
this.cpuSlotNr = cpuSlotNr;
|
||||
this.rackNr = rackNr;
|
||||
|
||||
ReconnectDelay = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
public TimeSpan ReconnectDelay { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async Task<bool> Connect()
|
||||
{
|
||||
if (sharp7 == null)
|
||||
throw new InvalidOperationException(StringResources.StrErrorS7DriverNotInitialized);
|
||||
|
||||
try
|
||||
{
|
||||
var errorCode = await Task.Factory.StartNew(() => sharp7.ConnectTo(ipAddress, rackNr, cpuSlotNr), CancellationToken.None, TaskCreationOptions.None, scheduler);
|
||||
var success = await EvaluateErrorCode(errorCode);
|
||||
if (success)
|
||||
{
|
||||
connectionStateSubject.OnNext(Enums.ConnectionState.Connected);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO:
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IObservable<ConnectionState> ConnectionState => connectionStateSubject.DistinctUntilChanged().AsObservable();
|
||||
|
||||
|
||||
public async Task Disconnect()
|
||||
{
|
||||
connectionStateSubject.OnNext(Enums.ConnectionState.DisconnectedByUser);
|
||||
await CloseConnection();
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
sharp7 = new S7Client();
|
||||
|
||||
var subscription =
|
||||
ConnectionState
|
||||
.Where(state => state == Enums.ConnectionState.ConnectionLost)
|
||||
.Take(1)
|
||||
.SelectMany(_ => Reconnect())
|
||||
// TODO: .RepeatAfterDelay(ReconnectDelay)
|
||||
// TODO: .LogAndRetry(logger, "Error while reconnecting to S7.")
|
||||
.Subscribe();
|
||||
|
||||
disposables.Add(subscription);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO:
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
disposables.Dispose();
|
||||
|
||||
if (sharp7 != null)
|
||||
{
|
||||
sharp7.Disconnect();
|
||||
sharp7 = null;
|
||||
}
|
||||
|
||||
connectionStateSubject?.Dispose();
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CloseConnection()
|
||||
{
|
||||
if (sharp7 == null)
|
||||
throw new InvalidOperationException(StringResources.StrErrorS7DriverNotInitialized);
|
||||
|
||||
await Task.Factory.StartNew(() => sharp7.Disconnect(), CancellationToken.None, TaskCreationOptions.None, scheduler);
|
||||
}
|
||||
|
||||
private async Task<bool> EvaluateErrorCode(int errorCode)
|
||||
{
|
||||
if (errorCode == 0)
|
||||
return true;
|
||||
|
||||
if (sharp7 == null)
|
||||
throw new InvalidOperationException(StringResources.StrErrorS7DriverNotInitialized);
|
||||
|
||||
var errorText = sharp7.ErrorText(errorCode);
|
||||
|
||||
await SetConnectionLostState();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> Reconnect()
|
||||
{
|
||||
await CloseConnection();
|
||||
|
||||
return await Connect();
|
||||
}
|
||||
|
||||
private async Task SetConnectionLostState()
|
||||
{
|
||||
var state = await connectionStateSubject.FirstAsync();
|
||||
if (state == Enums.ConnectionState.ConnectionLost) return;
|
||||
|
||||
connectionStateSubject.OnNext(Enums.ConnectionState.ConnectionLost);
|
||||
}
|
||||
|
||||
~Sharp7Connector()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
private bool IsConnected => connectionStateSubject.Value == Enums.ConnectionState.Connected;
|
||||
|
||||
public async Task<byte[]> ReadBytes(Operand operand, ushort startByteAddress, ushort bytesToRead, ushort dBNr, CancellationToken token)
|
||||
{
|
||||
EnsureConnectionValid();
|
||||
|
||||
var buffer = new byte[bytesToRead];
|
||||
|
||||
var area = FromOperand(operand);
|
||||
|
||||
var result =
|
||||
await Task.Factory.StartNew(() => sharp7.ReadArea(area, dBNr, startByteAddress, bytesToRead, S7Consts.S7WLByte, buffer), token, TaskCreationOptions.None, scheduler);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
await EvaluateErrorCode(result);
|
||||
throw new InvalidOperationException($"Error reading {operand}{dBNr}:{startByteAddress}->{bytesToRead}");
|
||||
}
|
||||
|
||||
var retBuffer = new byte[bytesToRead];
|
||||
Array.Copy(buffer, 0, retBuffer, 0, bytesToRead);
|
||||
return (retBuffer);
|
||||
}
|
||||
|
||||
private int FromOperand(Operand operand)
|
||||
{
|
||||
switch (operand)
|
||||
{
|
||||
case Operand.Input:
|
||||
return S7Consts.S7AreaPE;
|
||||
case Operand.Output:
|
||||
return S7Consts.S7AreaPA;
|
||||
case Operand.Marker:
|
||||
return S7Consts.S7AreaMK;
|
||||
case Operand.Db:
|
||||
return S7Consts.S7AreaDB;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(operand), operand, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureConnectionValid()
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException("S7Connector");
|
||||
|
||||
if (sharp7 == null)
|
||||
throw new InvalidOperationException(StringResources.StrErrorS7DriverNotInitialized);
|
||||
|
||||
if (!IsConnected)
|
||||
throw new InvalidOperationException("Plc is not connected");
|
||||
}
|
||||
|
||||
public async Task<ushort> WriteBytes(Operand operand, ushort startByteAdress, byte[] data, ushort dBNr, CancellationToken token)
|
||||
{
|
||||
EnsureConnectionValid();
|
||||
|
||||
var result = await Task.Factory.StartNew(() => sharp7.WriteArea(FromOperand(operand), dBNr, startByteAdress, data.Length, S7Consts.S7WLByte, data), token, TaskCreationOptions.None, scheduler);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
await EvaluateErrorCode(result);
|
||||
return (0);
|
||||
}
|
||||
return (ushort)(data.Length);
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> ReadBit(Operand operand, ushort byteAddress, byte bitAdress, ushort dbNr, CancellationToken token)
|
||||
{
|
||||
EnsureConnectionValid();
|
||||
|
||||
var byteValue = await ReadBytes(operand, byteAddress, 1, dbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (byteValue.Length != 1)
|
||||
throw new InvalidOperationException("Read bytes does not have length 1");
|
||||
|
||||
return Convert.ToBoolean(byteValue[0] & (1 << bitAdress));
|
||||
}
|
||||
|
||||
public async Task<bool> WriteBit(Operand operand, ushort startByteAddress, byte bitAdress, bool value, ushort dbNr, CancellationToken token)
|
||||
{
|
||||
EnsureConnectionValid();
|
||||
|
||||
var buffer = new byte[] { value ? (byte)0xff : (byte)0 };
|
||||
|
||||
var offsetStart = (startByteAddress * 8) + bitAdress;
|
||||
|
||||
var result = await Task.Factory.StartNew(() => sharp7.WriteArea(FromOperand(operand), dbNr, offsetStart, 1, S7Consts.S7WLBit, buffer), token, TaskCreationOptions.None, scheduler);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
await EvaluateErrorCode(result);
|
||||
return (false);
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
301
Sharp7.Rx/Sharp7Plc.cs
Normal file
301
Sharp7.Rx/Sharp7Plc.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Sharp7.Rx.Enums;
|
||||
using Sharp7.Rx.Interfaces;
|
||||
using Sharp7.Rx.Resources;
|
||||
|
||||
namespace Sharp7.Rx
|
||||
{
|
||||
public class Sharp7Plc : IPlc
|
||||
{
|
||||
private readonly string ipAddress;
|
||||
private readonly int rackNumber;
|
||||
private readonly int cpuMpiAddress;
|
||||
private readonly S7VaraibleNameParser varaibleNameParser;
|
||||
private bool disposed;
|
||||
private ISubject<Unit> disposingSubject = new Subject<Unit>();
|
||||
private IS7Connector s7Connector;
|
||||
|
||||
public Sharp7Plc(string ipAddress, int rackNumber, int cpuMpiAddress)
|
||||
{
|
||||
this.ipAddress = ipAddress;
|
||||
this.rackNumber = rackNumber;
|
||||
this.cpuMpiAddress = cpuMpiAddress;
|
||||
|
||||
varaibleNameParser = new S7VaraibleNameParser();
|
||||
}
|
||||
|
||||
public IObservable<ConnectionState> ConnectionState { get; private set; }
|
||||
|
||||
public async Task<bool> InitializeAsync()
|
||||
{
|
||||
s7Connector = new Sharp7Connector(ipAddress, rackNumber, cpuMpiAddress);
|
||||
ConnectionState = s7Connector.ConnectionState;
|
||||
|
||||
await s7Connector.InitializeAsync();
|
||||
|
||||
#pragma warning disable 4014
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await s7Connector.Connect();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
#pragma warning restore 4014
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<TValue> GetValue<TValue>(string variableName)
|
||||
{
|
||||
return GetValue<TValue>(variableName, CancellationToken.None);
|
||||
}
|
||||
|
||||
public async Task<TValue> GetValue<TValue>(string variableName, CancellationToken token)
|
||||
{
|
||||
var address = varaibleNameParser.Parse(variableName);
|
||||
if (address == null) throw new ArgumentException("Input variable name is not valid", nameof(variableName));
|
||||
|
||||
if (typeof(TValue) == typeof(bool))
|
||||
{
|
||||
var b = await s7Connector.ReadBit(address.Operand, address.Start, address.Bit, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
return (TValue)(object)b;
|
||||
}
|
||||
|
||||
if (typeof(TValue) == typeof(int))
|
||||
{
|
||||
var b = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (address.Length == 2)
|
||||
return (TValue)(object)((b[0] << 8) + b[1]);
|
||||
if (address.Length == 4)
|
||||
{
|
||||
Array.Reverse(b);
|
||||
return (TValue)(object)Convert.ToInt32(b);
|
||||
}
|
||||
|
||||
|
||||
throw new InvalidOperationException($"length must be 2 or 4 but is {address.Length}");
|
||||
}
|
||||
|
||||
if (typeof(TValue) == typeof(ulong))
|
||||
{
|
||||
var b = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
Array.Reverse(b);
|
||||
return (TValue)(object)Convert.ToUInt64(b);
|
||||
}
|
||||
|
||||
if (typeof(TValue) == typeof(short))
|
||||
{
|
||||
var b = await s7Connector.ReadBytes(address.Operand, address.Start, 2, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
return (TValue)(object)(short)((b[0] << 8) + b[1]);
|
||||
}
|
||||
|
||||
if (typeof(TValue) == typeof(byte) || typeof(TValue) == typeof(char))
|
||||
{
|
||||
var b = await s7Connector.ReadBytes(address.Operand, address.Start, 1, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
return (TValue)(object)b[0];
|
||||
}
|
||||
|
||||
if (typeof(TValue) == typeof(byte[]))
|
||||
{
|
||||
var b = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
return (TValue)(object)b;
|
||||
}
|
||||
|
||||
if (typeof(TValue) == typeof(double) || typeof(TValue) == typeof(float))
|
||||
{
|
||||
var bytes = await s7Connector.ReadBytes(address.Operand, address.Start, 4, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
var d = Convert.ToSingle(bytes);
|
||||
return (TValue)(object)d;
|
||||
}
|
||||
|
||||
if (typeof(TValue) == typeof(string))
|
||||
{
|
||||
if (address.Type == DbType.String)
|
||||
{
|
||||
var bytes = await s7Connector.ReadBytes(address.Operand, address.Start, 2, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
var stringLength = bytes[1];
|
||||
|
||||
var stringStartAddress = (ushort)(address.Start + 2);
|
||||
var stringInBytes = await s7Connector.ReadBytes(address.Operand, stringStartAddress, stringLength, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
return (TValue)(object)Encoding.ASCII.GetString(stringInBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
var stringInBytes = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
return (TValue)(object)Encoding.ASCII.GetString(stringInBytes).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(string.Format("type '{0}' not supported.", typeof(TValue)));
|
||||
}
|
||||
|
||||
|
||||
public Task SetValue<TValue>(string variableName, TValue value)
|
||||
{
|
||||
return SetValue(variableName, value, CancellationToken.None);
|
||||
}
|
||||
|
||||
public async Task SetValue<TValue>(string variableName, TValue value, CancellationToken token)
|
||||
{
|
||||
var address = varaibleNameParser.Parse(variableName);
|
||||
if (address == null) throw new ArgumentException("Input variable name is not valid", "variableName");
|
||||
|
||||
if (typeof(TValue) == typeof(bool))
|
||||
{
|
||||
await s7Connector.WriteBit(address.Operand, address.Start, address.Bit, (bool)(object)value, address.DbNr, token);
|
||||
}
|
||||
else if (typeof(TValue) == typeof(int) || typeof(TValue) == typeof(short))
|
||||
{
|
||||
byte[] bytes;
|
||||
if (address.Length == 4)
|
||||
bytes = BitConverter.GetBytes((int)(object)value);
|
||||
else
|
||||
bytes = BitConverter.GetBytes((short)(object)value);
|
||||
|
||||
Array.Reverse(bytes);
|
||||
|
||||
await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token);
|
||||
}
|
||||
else if (typeof(TValue) == typeof(byte) || typeof(TValue) == typeof(char))
|
||||
{
|
||||
var bytes = new[] { Convert.ToByte(value) };
|
||||
await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token);
|
||||
}
|
||||
else if (typeof(TValue) == typeof(byte[]))
|
||||
{
|
||||
await s7Connector.WriteBytes(address.Operand, address.Start, (byte[])(object)value, address.DbNr, token);
|
||||
}
|
||||
else if (typeof(TValue) == typeof(float))
|
||||
{
|
||||
var buffer = new byte[sizeof(float)];
|
||||
S7.SetRealAt(buffer, 0, (float)(object)value);
|
||||
await s7Connector.WriteBytes(address.Operand, address.Start, buffer, address.DbNr, token);
|
||||
}
|
||||
else if (typeof(TValue) == typeof(string))
|
||||
{
|
||||
var stringValue = value as string;
|
||||
if (stringValue == null) throw new ArgumentException("Value must be of type string", "value");
|
||||
|
||||
var bytes = Encoding.ASCII.GetBytes(stringValue);
|
||||
Array.Resize(ref bytes, address.Length);
|
||||
|
||||
if (address.Type == DbType.String)
|
||||
{
|
||||
var bytesWritten = await s7Connector.WriteBytes(address.Operand, address.Start, new[] { (byte)address.Length, (byte)bytes.Length }, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (bytesWritten == 2)
|
||||
{
|
||||
var stringStartAddress = (ushort)(address.Start + 2);
|
||||
token.ThrowIfCancellationRequested();
|
||||
await s7Connector.WriteBytes(address.Operand, stringStartAddress, bytes, address.DbNr, token);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await s7Connector.WriteBytes(address.Operand, address.Start, bytes, address.DbNr, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"type '{typeof(TValue)}' not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<TValue> CreateNotification<TValue>(string variableName, TransmissionMode transmissionMode, TimeSpan cycle)
|
||||
{
|
||||
var address = varaibleNameParser.Parse(variableName);
|
||||
if (address == null) throw new ArgumentException("Input variable name is not valid", nameof(variableName));
|
||||
|
||||
if (cycle < TimeSpan.FromMilliseconds(100))
|
||||
cycle = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
var notification = ConnectionState.FirstAsync().Select(states => states == Enums.ConnectionState.Connected)
|
||||
.SelectMany(async connected =>
|
||||
{
|
||||
var value = default(TValue);
|
||||
if (connected)
|
||||
{
|
||||
value = await GetValue<TValue>(variableName, CancellationToken.None);
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
HasValue = connected,
|
||||
Value = value
|
||||
};
|
||||
})
|
||||
// TODO: .RepeatAfterDelay(cycle)
|
||||
// TODO: .LogAndRetryAfterDelay(Logger, cycle, StringResources.StrLogErrorReadingDataFromPlc)
|
||||
.TakeUntil(disposingSubject)
|
||||
.Where(union => union.HasValue)
|
||||
.Select(union => union.Value);
|
||||
|
||||
if (transmissionMode == TransmissionMode.Cyclic)
|
||||
return notification;
|
||||
|
||||
if (transmissionMode == TransmissionMode.OnChange)
|
||||
return notification.DistinctUntilChanged();
|
||||
|
||||
throw new ArgumentException("Transmission mode can either be Cyclic or OnChange", nameof(transmissionMode));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (disposingSubject != null)
|
||||
{
|
||||
disposingSubject.OnNext(Unit.Default);
|
||||
disposingSubject.OnCompleted();
|
||||
var disposable = (disposingSubject as IDisposable);
|
||||
if (disposable != null) disposable.Dispose();
|
||||
disposingSubject = null;
|
||||
}
|
||||
if (s7Connector != null)
|
||||
{
|
||||
s7Connector.Disconnect().Wait();
|
||||
s7Connector.Dispose();
|
||||
s7Connector = null;
|
||||
}
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Sharp7Plc()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user