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:
25
Sharp7.Rx.sln
Normal file
25
Sharp7.Rx.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.28010.2041
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharp7.Rx", "Sharp7.Rx\Sharp7.Rx.csproj", "{690A7E0E-BE95-49AC-AF2F-7FEA2F63204A}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{690A7E0E-BE95-49AC-AF2F-7FEA2F63204A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{690A7E0E-BE95-49AC-AF2F-7FEA2F63204A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{690A7E0E-BE95-49AC-AF2F-7FEA2F63204A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{690A7E0E-BE95-49AC-AF2F-7FEA2F63204A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {ABA1FD47-15EE-4588-9BA7-0116C635BFC4}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
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