From 2e136412fe49739d6fae3dd7ce7fc2855659fca7 Mon Sep 17 00:00:00 2001 From: timyhac Date: Sat, 27 Jul 2024 07:32:29 +1000 Subject: [PATCH] Deprecate Mapper system (#407) * Mark PlcMapper / Tag as obsolete, and at it as an example. * Add an alternative implementation (in examples) for code reuse that uses inheritance. * Clean up Examples to reflect this change. * Remove a layer of indirection from Tag / NativeTagWrapper (directly expose NativeTagWrapper as Tag) --- docs/libplctag.md | 86 +- .../CSharp DotNetCore.csproj | 1 - examples/CSharp DotNetCore/ExampleAsync.cs | 196 +--- .../CSharp DotNetCore/ExampleCustomUdt.cs | 45 - .../{ILoggerExample.cs => ExampleILogger.cs} | 6 +- examples/CSharp DotNetCore/ExampleListPlc.cs | 276 +++++ examples/CSharp DotNetCore/ExampleListTags.cs | 51 - .../{LoggingExample.cs => ExampleLogging.cs} | 9 +- examples/CSharp DotNetCore/ExampleRaw.cs | 138 +++ examples/CSharp DotNetCore/ExampleSimple.cs | 39 + .../CSharp DotNetCore/ExampleSimulator.cs | 15 +- .../CSharp DotNetCore/ExampleTagDynamic.cs | 11 +- .../CSharp DotNetCore/Inheritance/BaseTag.cs | 249 ++++ .../Inheritance/Definitions.cs | 55 + examples/CSharp DotNetCore/ListPlc.cs | 132 --- .../CSharp DotNetCore/ListUdtDefinitions.cs | 78 -- .../{ => PlcMapper}/ExampleArray.cs | 5 +- .../ExampleCustomUdt.cs} | 98 +- .../{ => PlcMapper}/ExampleDatatypes.cs | 7 +- .../{ => PlcMapper}/ExampleDatatypesSimple.cs | 7 +- .../PlcMapper/ExampleListPlc.cs | 306 +++++ .../PlcMapper/ExampleMappers.cs | 389 +++++++ .../{ => PlcMapper}/ExampleNotifyChanged.cs | 3 +- .../CSharp DotNetCore/PlcMapper/TagOfT.cs | 344 ++++++ examples/CSharp DotNetCore/Program.cs | 15 +- examples/CSharp DotNetCore/SimpleExample.cs | 47 - examples/CSharp DotNetCore/TestDatatypes.cs | 111 -- .../CSharp DotNetFramework.csproj | 2 - examples/CSharp DotNetFramework/Program.cs | 24 +- examples/LibplctagReadBenchmark/Program.cs | 8 +- .../LibplctagReadBenchmark/ReadExamples.cs | 12 +- .../LibplctagReadBenchmark/TagInstantation.cs | 7 +- examples/VB.NET DotNetCore/Program.vb | 7 +- src/libplctag.Tests/AsyncTests.cs | 16 +- src/libplctag.Tests/DisposeTests.cs | 12 +- src/libplctag.Tests/OtherTests.cs | 12 +- src/libplctag/DataTypes/BoolPlcMapper.cs | 1 + src/libplctag/DataTypes/DintPlcMapper.cs | 3 + .../DataTypes/Extensions/ArrayExtensions.cs | 1 + src/libplctag/DataTypes/IPlcMapper.cs | 3 + src/libplctag/DataTypes/IntPlcMapper.cs | 3 + src/libplctag/DataTypes/LintPlcMapper.cs | 3 + src/libplctag/DataTypes/LrealPlcMapper.cs | 3 + src/libplctag/DataTypes/PlcMapperBase.cs | 1 + src/libplctag/DataTypes/RealPlcMapper.cs | 3 + src/libplctag/DataTypes/Simple/Definitions.cs | 35 + src/libplctag/DataTypes/SintPlcMapper.cs | 3 + src/libplctag/DataTypes/StringPlcMapper.cs | 1 + src/libplctag/DataTypes/TagInfoPlcMapper.cs | 2 + src/libplctag/DataTypes/TimerPlcMapper.cs | 2 + src/libplctag/DataTypes/UdtInfoPlcMapper.cs | 3 + src/libplctag/{INativeTag.cs => INative.cs} | 2 +- src/libplctag/ITag.cs | 1 + src/libplctag/LibPlcTag.cs | 3 +- src/libplctag/LogEventArgs.cs | 2 - src/libplctag/{NativeTag.cs => Native.cs} | 5 +- src/libplctag/NativeTagWrapper.cs | 928 --------------- src/libplctag/Tag.cs | 1016 +++++++++++++---- src/libplctag/TagEventArgs.cs | 2 - src/libplctag/TagOfT.cs | 1 + 60 files changed, 2899 insertions(+), 1947 deletions(-) delete mode 100644 examples/CSharp DotNetCore/ExampleCustomUdt.cs rename examples/CSharp DotNetCore/{ILoggerExample.cs => ExampleILogger.cs} (95%) create mode 100644 examples/CSharp DotNetCore/ExampleListPlc.cs delete mode 100644 examples/CSharp DotNetCore/ExampleListTags.cs rename examples/CSharp DotNetCore/{LoggingExample.cs => ExampleLogging.cs} (90%) create mode 100644 examples/CSharp DotNetCore/ExampleRaw.cs create mode 100644 examples/CSharp DotNetCore/ExampleSimple.cs create mode 100644 examples/CSharp DotNetCore/Inheritance/BaseTag.cs create mode 100644 examples/CSharp DotNetCore/Inheritance/Definitions.cs delete mode 100644 examples/CSharp DotNetCore/ListPlc.cs delete mode 100644 examples/CSharp DotNetCore/ListUdtDefinitions.cs rename examples/CSharp DotNetCore/{ => PlcMapper}/ExampleArray.cs (94%) rename examples/CSharp DotNetCore/{SequencePlcMapper.cs => PlcMapper/ExampleCustomUdt.cs} (71%) rename examples/CSharp DotNetCore/{ => PlcMapper}/ExampleDatatypes.cs (95%) rename examples/CSharp DotNetCore/{ => PlcMapper}/ExampleDatatypesSimple.cs (95%) create mode 100644 examples/CSharp DotNetCore/PlcMapper/ExampleListPlc.cs create mode 100644 examples/CSharp DotNetCore/PlcMapper/ExampleMappers.cs rename examples/CSharp DotNetCore/{ => PlcMapper}/ExampleNotifyChanged.cs (98%) create mode 100644 examples/CSharp DotNetCore/PlcMapper/TagOfT.cs delete mode 100644 examples/CSharp DotNetCore/SimpleExample.cs delete mode 100644 examples/CSharp DotNetCore/TestDatatypes.cs rename src/libplctag/{INativeTag.cs => INative.cs} (99%) rename src/libplctag/{NativeTag.cs => Native.cs} (99%) delete mode 100644 src/libplctag/NativeTagWrapper.cs diff --git a/docs/libplctag.md b/docs/libplctag.md index 1aba462e..20995632 100644 --- a/docs/libplctag.md +++ b/docs/libplctag.md @@ -4,7 +4,7 @@ [libplctag.NET](https://www.nuget.org/packages/libplctag/) provides wrapper packages for libplctag, with an API naturalised to .NET by adding the following features: -* Values are strongly-typed (both Atomic types and User-Defined Types). +* Values are strongly-typed * Errors are thrown as Exceptions * Async/Await * Native resource cleanup @@ -13,7 +13,7 @@ ```csharp // Example tag configuration for a global DINT tag in an Allen-Bradley CompactLogix/ControlLogix PLC -var myTag = new TagDint() +var myTag = new Tag() { Name = "SomeDINT", Gateway = "10.10.10.10", @@ -22,14 +22,16 @@ var myTag = new TagDint() Protocol = Protocol.ab_eip }; -// Read the value from the PLC and output to console -int output = myTag.Read(); -Console.WriteLine($"Original value: {output}"); +// Read the value from the PLC +myTag.Read(); +int originalValue = myTag.GetInt32(0); +Console.WriteLine($"Original value: {originalValue}"); -// Write a new value to the PLC, then read it back, and output to console -myTag.Write(37); -output = myTag.Read(); -Console.WriteLine($"Updated value: {output}"); +// Write a new value to the PLC +int updatedValue = 1234; +myTag.SetInt32(0, updatedValue); +myTag.Write(); +Console.WriteLine($"Updated value: {updatedValue}"); ``` See the examples projects for further detail and usage: @@ -57,7 +59,7 @@ Just tags. Read more on the [libplctag wiki](https://github.com/libplctag/libplctag/wiki/API). -## `Tag` +## `libplctag.Tag` libplctag.NET provides a wrapper for the C API naturalised for .NET. The `Tag` class is intended to be functionally equivalent to the C API. @@ -71,69 +73,17 @@ For example: Some methods are presented slightly differently due to the differences in languages and language idioms. For example, the counterpart to `Initialize(..)` is [`plc_tag_create(..)`](https://github.com/libplctag/libplctag/wiki/API#creating-a-tag-handle) and the tag attributes are specified as properties (e.g. `Tag.Path`). +### Data Accessors -## `Tag` and Mappers +Mapping the raw tag buffer to some typed value (e.g. `int`) and vice-versa can be achieved using the built-in [Data Accessor methods](https://github.com/libplctag/libplctag/wiki/API#tag-data-accessors). +Alternatively, get a copy of the byte array with `GetBuffer(..)` and do the conversion yourself (e.g. with [`BitConverter`](https://learn.microsoft.com/en-us/dotnet/api/system.bitconverter), [`BinaryPrimities`](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.binary.binaryprimitives), [`Encoding`](https://learn.microsoft.com/en-us/dotnet/api/system.text.encoding), or manually). -In your .NET application, you will usually need to convert the raw bytes into a .NET type. -It is possible to use `GetInt32()` and `SetInt32()` (and [others](https://github.com/libplctag/libplctag/wiki/API#tag-data-accessors)) provided by the `Tag` class to perform this conversion, and most of the time, there will only be one sensible way to interpret these bytes for a given tag. - -For example, a `DINT` tag defined in a PLC is a 32bit signed integer, and this would be exposed as a little-endian encoded 4-byte array. -The natural choice for a C# type would be `int` - it would be rare to want to work with this data as a `float` or a 4-byte ASCII string for example. - -To this end, libplctag.NET offers a typed tag class `Tag` that exposes the tag value as a C# type instead of the complete set of Getter/Setter functions. -This class pairs with an [`IPlcMapper`](src/libplctag/DataTypes/IPlcMapper.cs), which encapsulates the mapping between a .NET type (e.g. `int`, `float`) and the PLC type (e.g. `DINT`, `REAL`) by calling the appropriate functions on a Tag (as well as providing other information that libplctag needs for this mapping). - -```csharp -class DintPlcMapper : IPlcMapper -{ - public PlcType PlcType { get; set; } - public int? ElementSize => 4; - public int[] ArrayDimensions { get; set; } - public int? GetElementCount() => 1; - public int Decode(Tag tag, int offset) => tag.GetInt32(offset); - public void Encode(Tag tag, int offset, int value) => tag.SetInt32(offset, value); -} - -var myTag = new Tag(){...configuration...}; -myTag.Initialize(); -myTag.Value = 1234; -myTag.Write(); -``` - -In general, you will need prior knowedge of the structure of the tag data, and you may need to reverse-engineer it. -An example for reverse engineering a UDT can be found [here](../examples/CSharp%20DotNetCore/SequencePlcMapper.cs). - -Because the structure of the data depends on many factors (PLC Make/model, Protocol, and even the tag Name), libplctag.NET does not provide built-in Mappers for all types. +In general, you will need prior knowedge of the binary format of the tag data, and you may need to reverse-engineer it. The manuals provided by your device manufacturer are the best source of information on these details. -## Types - -### `libplctag` namepsace -* `Tag` - A wrapper around the core libplctag library tag with an interface naturalised to .NET. -* `Tag` - A wrapper that exposes a .NET type (generic parameter `T`) instead of Data Accessors. The data access logic is delegated to an `IPlcMapper` (generic parameter `M`). -* `ITag` - an interface that is implemented by `Tag`. -* `Libplctag` - A static class used to access some additional features of the libplctag base library such as global debug levels and logging. -* Enum types such as `DebugLevel`. -* Supporting types such as `TagEventArgs`. - -All types are shipped with XML documentation, so the full API is discoverable in your IDE. - -### `libplctag.DataTypes` namespace - -* [`IPlcMapper`](src/libplctag/DataTypes/IPlcMapper.cs) -* [`DintPlcMapper`](src/libplctag/DataTypes/DintPlcMapper.cs) -* [`LrealPlcMapper`](src/libplctag/DataTypes/LrealPlcMapper.cs) -* ... and so on - -Of note are [TagInfoPlcMapper](src/libplctag/DataTypes/TagInfoPlcMapper.cs) and [UdtInfoMapper](src/libplctag/DataTypes/UdtInfoPlcMapper.cs), which can be used to [list the tags in a ControlLogix PLC](../examples/CSharp%20DotNetCore/ListUdtDefinitions.cs). - -### `libplctag.DataTypes.Simple` namespace - -In simple cases such as atomic tags (e.g.`DINT`) or arrays of atomic tags (e.g. `LREAL[x,y]`), we provide classes that pair a built-in `IPlcMapper` with the natural .NET type: +## `libplctag.LibPlcTag` -* [`TagDint`](src/libplctag/DataTypes/Simple/Definitions.cs#L21) -* [`TagLreal2D`](src/libplctag/DataTypes/Simple/Definitions.cs#L41) -* ... and so on +This is a static class used to access some utility features of the libplctag base library such as global debug levels and logging. ## libplctag.NativeImport diff --git a/examples/CSharp DotNetCore/CSharp DotNetCore.csproj b/examples/CSharp DotNetCore/CSharp DotNetCore.csproj index f6bc2544..bf41ed6f 100644 --- a/examples/CSharp DotNetCore/CSharp DotNetCore.csproj +++ b/examples/CSharp DotNetCore/CSharp DotNetCore.csproj @@ -13,7 +13,6 @@ - diff --git a/examples/CSharp DotNetCore/ExampleAsync.cs b/examples/CSharp DotNetCore/ExampleAsync.cs index eb20eaf4..b8e1a6a9 100644 --- a/examples/CSharp DotNetCore/ExampleAsync.cs +++ b/examples/CSharp DotNetCore/ExampleAsync.cs @@ -6,12 +6,8 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using libplctag; -using libplctag.DataTypes; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; @@ -21,7 +17,7 @@ class ExampleAsync { public static async Task Run() { - var myTag = new Tag() + var myTag = new Tag() { Name = "PROGRAM:SomeProgram.SomeDINT", Gateway = "10.10.10.10", @@ -33,208 +29,24 @@ public static async Task Run() await myTag.InitializeAsync(); - myTag.Value = 3737; + myTag.SetInt32(0, 3737); await myTag.WriteAsync(); await myTag.ReadAsync(); - int myDint = myTag.Value; + int myDint = myTag.GetInt32(0); Console.WriteLine(myDint); } - public static void SyncAsyncComparison() - { - - Console.WriteLine("This method measures the speed of synchronous vs asynchronous reads"); - - List> myTags; - - for (int ii = 0; ii < 10; ii++) - { - myTags = Enumerable.Range(0, 10) - .Select(i => { - var myTag = new Tag() - { - Name = $"MY_DINT_ARRAY_1000[{i}]", - Gateway = "10.10.10.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - }; - myTag.Initialize(); - return myTag; - }) - .ToList(); - - int repetitions = 100; - - //Console.Write($"Running {repetitions} Read() calls..."); - //var syncStopWatch = Stopwatch.StartNew(); - //for (int ii = 0; ii < repetitions; ii++) - //{ - // // We know that it takes less than 1000ms per read, so it will return as soon as it is finished - // myTag.Read(1000); - //} - //syncStopWatch.Stop(); - //Console.WriteLine($"\ttook {(float)syncStopWatch.ElapsedMilliseconds / (float)repetitions}ms on average"); - - - Console.Write($"Running {repetitions} ReadAsync() calls..."); - var asyncStopWatch = Stopwatch.StartNew(); - for (int jj = 0; jj < repetitions; jj++) - { - Task.WaitAll( - myTags[0].ReadAsync(), - myTags[1].ReadAsync(), - myTags[2].ReadAsync(), - myTags[3].ReadAsync(), - myTags[4].ReadAsync(), - myTags[5].ReadAsync(), - myTags[6].ReadAsync(), - myTags[7].ReadAsync(), - myTags[8].ReadAsync(), - myTags[9].ReadAsync() - ); - //await myTag.ReadAsync(); - } - asyncStopWatch.Stop(); - Console.WriteLine($"\ttook {(float)asyncStopWatch.ElapsedMilliseconds / (float)repetitions}ms on average"); - } - - - } - - public static void ParallelBlockingReads() - { - - Console.WriteLine("This method measures the speed of synchronous vs asynchronous reads"); - var myTag = new Tag() - { - Name = "PROGRAM:SomeProgram.SomeDINT", - Gateway = "10.10.10.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - }; - myTag.Initialize(); - - int repetitions = 100; - - Console.Write($"Running {repetitions} calls..."); - var sw = Stopwatch.StartNew(); - for (int ii = 0; ii < repetitions; ii++) - { - Task.WaitAll( - Task.Run(() => myTag.Read()), - Task.Run(() => myTag.Read()) - ); - } - sw.Stop(); - - Console.WriteLine($"\ttook {(float)sw.ElapsedMilliseconds / (float)repetitions}ms on average"); - - } - - public static void SyncAsyncMultipleTagComparison(int repetitions = 1000) - { - Console.WriteLine("This method measures the speed of synchronous vs asynchronous reads for multiple tags simultaneously"); - - SyncAsyncMultipleTagComparisonSingleRun(1, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(2, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(3, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(4, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(5, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(6, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(7, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(8, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(9, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(10, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(11, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(12, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(13, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(14, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(15, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(16, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(17, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(18, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(19, repetitions); - SyncAsyncMultipleTagComparisonSingleRun(20, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(25, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(30, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(35, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(40, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(45, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(50, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(60, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(70, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(80, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(90, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(100, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(200, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(300, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(400, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(500, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(600, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(700, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(800, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(900, repetitions); - //SyncAsyncMultipleTagComparisonSingleRun(1000, repetitions); - } - - private static void SyncAsyncMultipleTagComparisonSingleRun(int maxTags, int repetitions = 10) - { - - Console.Write($"Running {repetitions} ReadAsync() calls on {maxTags} tags simultaneously..."); - - var myTags = Enumerable.Range(0, maxTags) - .Select(i => { - var myTag = new Tag() - { - Name = "PROGRAM:SomeProgram.SomeDINT", - Gateway = "10.10.10.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - Timeout = TimeSpan.FromMilliseconds(1000), - }; - myTag.Initialize(); - return myTag; - }) - .ToList(); - - var asyncStopWatch = Stopwatch.StartNew(); - - Task.WaitAll(myTags.Select(tag => - { - return Task.Run(async () => - { - for (int ii = 0; ii < repetitions; ii++) - { - await tag.ReadAsync(); - } - }); - }).ToArray()); - - asyncStopWatch.Stop(); - Console.WriteLine($"\ttook {(float)asyncStopWatch.ElapsedMilliseconds / (float)repetitions}ms on average"); - - foreach (var tag in myTags) - { - tag.Dispose(); - } - - } - - public static void AsyncParallelCancellation(int maxTags = 20, int repetitions = 100) { var myTags = Enumerable.Range(0, maxTags) .Select(i => { - var myTag = new Tag() + var myTag = new Tag() { Name = $"MY_DINT_ARRAY_1000[{i}]", Gateway = "10.10.10.10", diff --git a/examples/CSharp DotNetCore/ExampleCustomUdt.cs b/examples/CSharp DotNetCore/ExampleCustomUdt.cs deleted file mode 100644 index 9308f336..00000000 --- a/examples/CSharp DotNetCore/ExampleCustomUdt.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) libplctag.NET contributors -// https://github.com/libplctag/libplctag.NET -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -using libplctag; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CSharpDotNetCore -{ - class ExampleCustomUdt - { - public static void Run() - { - const string gateway = "10.10.10.10"; - const string path = "1,0"; - - //This example shows the use of a custom mapper created to match a UDT called "Sequence" - - var sequenceArray = new Tag() - { - Name = "MY_SEQUENCE_3D[0,0,0]", - Gateway = gateway, - Path = path, - Protocol = Protocol.ab_eip, - PlcType = PlcType.ControlLogix, - ArrayDimensions = new int[] { 8 }, - }; - - for (int ii = 0; ii < 8; ii++) - sequenceArray.Value[ii].Command = ii * 2; - - sequenceArray.Write(); - - - Console.WriteLine("DONE! Check values in RsLogix"); - - } - - } -} diff --git a/examples/CSharp DotNetCore/ILoggerExample.cs b/examples/CSharp DotNetCore/ExampleILogger.cs similarity index 95% rename from examples/CSharp DotNetCore/ILoggerExample.cs rename to examples/CSharp DotNetCore/ExampleILogger.cs index 76e4796d..e18f532b 100644 --- a/examples/CSharp DotNetCore/ILoggerExample.cs +++ b/examples/CSharp DotNetCore/ExampleILogger.cs @@ -6,8 +6,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using libplctag; -using libplctag.DataTypes; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -17,7 +15,7 @@ namespace CSharpDotNetCore { - class ILoggerExample + class ExampleILogger { public static void Run() { @@ -40,7 +38,7 @@ class Example : IHostedService { public async Task StartAsync(CancellationToken cancellationToken) { - var myTag = new Tag() + var myTag = new Tag() { Name = "MyTag[0]", Gateway = "127.0.0.1", diff --git a/examples/CSharp DotNetCore/ExampleListPlc.cs b/examples/CSharp DotNetCore/ExampleListPlc.cs new file mode 100644 index 00000000..e65539d1 --- /dev/null +++ b/examples/CSharp DotNetCore/ExampleListPlc.cs @@ -0,0 +1,276 @@ +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using libplctag; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CSharpDotNetCore +{ + + class ExampleListPlc + { + + const int TAG_STRING_SIZE = 200; + static readonly string gateway = "192.168.0.10"; + static readonly string path = "1,0"; + static readonly TimeSpan timeout = TimeSpan.FromSeconds(10); + + static public void Run() + { + + Console.WriteLine("Controller Tags"); + Console.WriteLine("==============="); + + var tags = new Tag() + { + Gateway = gateway, + Path = path, + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + Name = "@tags", + Timeout = timeout, + }; + + tags.Read(); + + var controllerTags = DecodeAllTagInfos(tags); + + foreach (var tag in controllerTags) + Console.WriteLine($"Id={tag.Id} Name={tag.Name} Type={tag.Type} Length={tag.Length}"); + + + + + Console.WriteLine(); + Console.WriteLine("Programs"); + Console.WriteLine("========"); + + foreach (var tag in controllerTags) + { + if (TagIsProgram(tag, out string programTagListingPrefix)) + { + var programsTag = new Tag() + { + Gateway = gateway, + Path = path, + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + Name = $"{programTagListingPrefix}.@tags", + Timeout = TimeSpan.FromSeconds(10) + }; + + programsTag.Read(); + + var programTags = DecodeAllTagInfos(programsTag); + + Console.WriteLine(programTagListingPrefix); + foreach (var program in programTags) + Console.WriteLine($" {program.Name}"); + + } + } + + + Console.WriteLine(); + Console.WriteLine("UDTs"); + Console.WriteLine("===="); + + var uniqueUdtTypeIds = controllerTags + .Where(tagInfo => TagIsUdt(tagInfo)) + .Select(tagInfo => GetUdtId(tagInfo)) + .Distinct(); + + foreach (var udtId in uniqueUdtTypeIds) + { + var udtTag = new Tag() + { + Gateway = gateway, + Path = path, + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + Name = $"@udt/{udtId}", + }; + + udtTag.Read(); + var udt = DecodeUdtInfo(udtTag); + + Console.WriteLine($"Id={udt.Id} Name={udt.Name} NumFields={udt.NumFields} Size={udt.Size}"); + foreach (var f in udt.Fields) + Console.WriteLine($" Name={f.Name} Offset={f.Offset} Metadata={f.Metadata} Type={f.Type}"); + } + + } + + + class TagInfo + { + public uint Id { get; set; } + public ushort Type { get; set; } + public string Name { get; set; } + public ushort Length { get; set; } + public uint[] Dimensions { get; set; } + } + + static TagInfo DecodeOneTagInfo(Tag tag, int offset, out int elementSize) + { + + var tagInstanceId = tag.GetUInt32(offset); + var tagType = tag.GetUInt16(offset + 4); + var tagLength = tag.GetUInt16(offset + 6); + var tagArrayDims = new uint[] + { + tag.GetUInt32(offset + 8), + tag.GetUInt32(offset + 12), + tag.GetUInt32(offset + 16) + }; + + var apparentTagNameLength = (int)tag.GetUInt16(offset + 20); + var actualTagNameLength = Math.Min(apparentTagNameLength, TAG_STRING_SIZE * 2 - 1); + + var tagNameBytes = Enumerable.Range(offset + 22, actualTagNameLength) + .Select(o => tag.GetUInt8(o)) + .Select(Convert.ToByte) + .ToArray(); + + var tagName = Encoding.ASCII.GetString(tagNameBytes); + + elementSize = 22 + actualTagNameLength; + + return new TagInfo() + { + Id = tagInstanceId, + Type = tagType, + Name = tagName, + Length = tagLength, + Dimensions = tagArrayDims + }; + + } + + static TagInfo[] DecodeAllTagInfos(Tag tag) + { + var buffer = new List(); + + var tagSize = tag.GetSize(); + + int offset = 0; + while (offset < tagSize) + { + buffer.Add(DecodeOneTagInfo(tag, offset, out int elementSize)); + offset += elementSize; + } + + return buffer.ToArray(); + } + + class UdtFieldInfo + { + public string Name { get; set; } + public ushort Type { get; set; } + public ushort Metadata { get; set; } + public uint Offset { get; set; } + } + + class UdtInfo + { + public uint Size { get; set; } + public string Name { get; set; } + public ushort Id { get; set; } + public ushort NumFields { get; set; } + public ushort Handle { get; set; } + public UdtFieldInfo[] Fields { get; set; } + } + + static UdtInfo DecodeUdtInfo(Tag tag) + { + + var template_id = tag.GetUInt16(0); + var member_desc_size = tag.GetUInt32(2); + var udt_instance_size = tag.GetUInt32(6); + var num_members = tag.GetUInt16(10); + var struct_handle = tag.GetUInt16(12); + + var udtInfo = new UdtInfo() + { + Fields = new UdtFieldInfo[num_members], + NumFields = num_members, + Handle = struct_handle, + Id = template_id, + Size = udt_instance_size + }; + + var offset = 14; + + for (int field_index = 0; field_index < num_members; field_index++) + { + ushort field_metadata = tag.GetUInt16(offset); + offset += 2; + + ushort field_element_type = tag.GetUInt16(offset); + offset += 2; + + ushort field_offset = tag.GetUInt16(offset); + offset += 4; + + var field = new UdtFieldInfo() + { + Offset = field_offset, + Metadata = field_metadata, + Type = field_element_type, + }; + + udtInfo.Fields[field_index] = field; + } + + var name_str = tag.GetString(offset).Split(';')[0]; + udtInfo.Name = name_str; + + offset += tag.GetStringTotalLength(offset); + + for (int field_index = 0; field_index < num_members; field_index++) + { + udtInfo.Fields[field_index].Name = tag.GetString(offset); + offset += tag.GetStringTotalLength(offset); + } + + return udtInfo; + + } + + static bool TagIsUdt(TagInfo tag) + { + const ushort TYPE_IS_STRUCT = 0x8000; + const ushort TYPE_IS_SYSTEM = 0x1000; + + return ((tag.Type & TYPE_IS_STRUCT) != 0) && !((tag.Type & TYPE_IS_SYSTEM) != 0); + } + + static int GetUdtId(TagInfo tag) + { + const ushort TYPE_UDT_ID_MASK = 0x0FFF; + return tag.Type & TYPE_UDT_ID_MASK; + } + + static bool TagIsProgram(TagInfo tag, out string prefix) + { + if (tag.Name.StartsWith("Program:")) + { + prefix = tag.Name; + return true; + } + else + { + prefix = string.Empty; + return false; + } + } + } + +} diff --git a/examples/CSharp DotNetCore/ExampleListTags.cs b/examples/CSharp DotNetCore/ExampleListTags.cs deleted file mode 100644 index 64c89ff7..00000000 --- a/examples/CSharp DotNetCore/ExampleListTags.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) libplctag.NET contributors -// https://github.com/libplctag/libplctag.NET -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -using System; -using System.Linq; -using System.Net; -using System.Text; -using ConsoleTables; -using libplctag; -using libplctag.DataTypes; - -namespace CSharpDotNetCore -{ - static class ExampleListTags - { - public static void Run() - { - //This example will list all tags at the controller-scoped level - - var tags = new Tag() - { - Gateway = "192.168.0.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - Name = "@tags" - }; - - tags.Read(); - - ConsoleTable - .From(tags.Value.Select(t => new - { - t.Id, - Type = $"0x{t.Type:X}", - t.Name, - t.Length, - Dimensions = string.Join(',', t.Dimensions) - })) - .Configure(o => o.NumberAlignment = Alignment.Right) - .Write(Format.Default); - - } - - } - -} \ No newline at end of file diff --git a/examples/CSharp DotNetCore/LoggingExample.cs b/examples/CSharp DotNetCore/ExampleLogging.cs similarity index 90% rename from examples/CSharp DotNetCore/LoggingExample.cs rename to examples/CSharp DotNetCore/ExampleLogging.cs index 7d0a2978..6a8e1cfe 100644 --- a/examples/CSharp DotNetCore/LoggingExample.cs +++ b/examples/CSharp DotNetCore/ExampleLogging.cs @@ -6,14 +6,13 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using libplctag; -using libplctag.DataTypes; using System; using System.Net; using System.Threading; namespace CSharpDotNetCore { - class LoggingExample + class ExampleLogging { public static void Run() { @@ -21,7 +20,7 @@ public static void Run() LibPlcTag.DebugLevel = DebugLevel.Detail; //Instantiate the tag with the proper mapper and datatype - var myTag = new Tag() + var myTag = new Tag() { Name = "MyTag[0]", Gateway = "127.0.0.1", @@ -37,7 +36,7 @@ public static void Run() myTag.Initialize(); //The value is held locally and only synchronized on Read() or Write() - myTag.Value = 3737; + myTag.SetInt32(0, 3737); //Transfer Value to PLC myTag.Write(); @@ -46,7 +45,7 @@ public static void Run() myTag.Read(); //Write to console - int myDint = myTag.Value; + int myDint = myTag.GetInt32(0); Console.WriteLine(myDint); } diff --git a/examples/CSharp DotNetCore/ExampleRaw.cs b/examples/CSharp DotNetCore/ExampleRaw.cs new file mode 100644 index 00000000..656fea5c --- /dev/null +++ b/examples/CSharp DotNetCore/ExampleRaw.cs @@ -0,0 +1,138 @@ +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using libplctag; + +namespace CSharp_DotNetCore +{ + class ExampleRaw + { + public static void Run() + { + var lister = new LogixTagListing() + { + Gateway = "192.168.0.1", + Path = "1,0", + Timeout = TimeSpan.FromMilliseconds(1000), + }; + + var tags = lister.ListTags(); + + foreach (var tag in tags) + Console.WriteLine($"Id={tag.Id} Name={tag.Name} Type={tag.Type} Length={tag.Length}"); + } + + class LogixTagListing + { + readonly Tag _rawCip = new Tag() + { + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + Name = "@raw", + }; + + public string Gateway { get => _rawCip.Gateway; set => _rawCip.Gateway = value; } + public string Path { get => _rawCip.Path; set => _rawCip.Path = value; } + public TimeSpan Timeout { get => _rawCip.Timeout; set => _rawCip.Timeout = value; } + + public List ListTags() + { + // This payload is taken from https://github.com/libplctag/libplctag/blob/release/src/examples/test_raw_cip.c + // but others can be found by analysing the Rockwell or other manufacturer's documentation + // https://literature.rockwellautomation.com/idc/groups/literature/documents/pm/1756-pm020_-en-p.pdf pg 39 + + var raw_payload = new byte[] { + 0x55, + 0x03, + 0x20, + 0x6b, + 0x25, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x02, + 0x00, + 0x07, + 0x00, + 0x08, + 0x00, + 0x01, + 0x00 + }; + + _rawCip.Initialize(); + _rawCip.SetSize(raw_payload.Length); + _rawCip.SetBuffer(raw_payload); + _rawCip.Write(); + + var responseSize = _rawCip.GetSize(); + + var tagInfos = new List(); + int offset = 0; + while (offset < responseSize) + tagInfos.Add(DecodeOneTagInfo(ref offset)); + + return tagInfos; + } + + public class TagInfo + { + public uint Id { get; set; } + public ushort Type { get; set; } + public string Name { get; set; } + public ushort Length { get; set; } + public uint[] Dimensions { get; set; } + } + + TagInfo DecodeOneTagInfo(ref int offset) + { + + var tagInstanceId = _rawCip.GetUInt32(offset); + var tagType = _rawCip.GetUInt16(offset + 4); + var tagLength = _rawCip.GetUInt16(offset + 6); + var tagArrayDims = new uint[] + { + _rawCip.GetUInt32(offset + 8), + _rawCip.GetUInt32(offset + 12), + _rawCip.GetUInt32(offset + 16) + }; + + var apparentTagNameLength = (int)_rawCip.GetUInt16(offset + 20); + const int TAG_STRING_SIZE = 200; + var actualTagNameLength = Math.Min(apparentTagNameLength, TAG_STRING_SIZE * 2 - 1); + + var tagNameBytes = Enumerable.Range(offset + 22, actualTagNameLength) + .Select(o => _rawCip.GetUInt8(o)) + .Select(Convert.ToByte) + .ToArray(); + + var tagName = Encoding.ASCII.GetString(tagNameBytes); + + offset = 22 + actualTagNameLength; + + return new TagInfo() + { + Id = tagInstanceId, + Type = tagType, + Name = tagName, + Length = tagLength, + Dimensions = tagArrayDims + }; + + } + + } + + } + +} diff --git a/examples/CSharp DotNetCore/ExampleSimple.cs b/examples/CSharp DotNetCore/ExampleSimple.cs new file mode 100644 index 00000000..006dc847 --- /dev/null +++ b/examples/CSharp DotNetCore/ExampleSimple.cs @@ -0,0 +1,39 @@ +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using libplctag; +using System; + +namespace CSharpDotNetCore +{ + class ExampleSimple + { + public static void Run() + { + // Example tag configuration for a global DINT tag in an Allen-Bradley CompactLogix/ControlLogix PLC + var myTag = new Tag() + { + Name = "SomeDINT", + Gateway = "10.10.10.10", + Path = "1,0", + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip + }; + + // Read the value from the PLC and output to console + myTag.Read(); + int originalValue = myTag.GetInt32(0); + Console.WriteLine($"Original value: {originalValue}"); + + // Write a new value to the PLC, then read it back, and output to console + int updatedValue = 1234; + myTag.SetInt32(0, updatedValue); + myTag.Write(); + Console.WriteLine($"Updated value: {updatedValue}"); + } + } +} diff --git a/examples/CSharp DotNetCore/ExampleSimulator.cs b/examples/CSharp DotNetCore/ExampleSimulator.cs index 6041742a..d2894e41 100644 --- a/examples/CSharp DotNetCore/ExampleSimulator.cs +++ b/examples/CSharp DotNetCore/ExampleSimulator.cs @@ -1,5 +1,4 @@ using libplctag; -using libplctag.DataTypes; using System; using System.Threading.Tasks; using System.Timers; @@ -96,11 +95,11 @@ private interface ITurnstileGate /// private class RealImplementation : ITurnstileGate { - private readonly Tag CounterTag; - private readonly Tag ResetTag; + private readonly Tag CounterTag; + private readonly Tag ResetTag; public RealImplementation() { - CounterTag = new Tag() + CounterTag = new Tag() { PlcType = PlcType.ControlLogix, Path = "0,1", @@ -109,7 +108,7 @@ public RealImplementation() Protocol = Protocol.ab_eip, }; - ResetTag = new Tag() + ResetTag = new Tag() { PlcType = PlcType.ControlLogix, Path = "0,1", @@ -124,13 +123,15 @@ public RealImplementation() public async Task ResetCounter() { // The PLC is expected to have some logic that sets the ResetFlag from true back to false after resetting the counter - await ResetTag.WriteAsync(true); + ResetTag.SetUInt8(0, 0x01); + await ResetTag.WriteAsync(); await ReceiveData(); } public async Task ReceiveData() { - Counter = await CounterTag.ReadAsync(); + await CounterTag.ReadAsync(); + Counter = CounterTag.GetInt32(0); } } diff --git a/examples/CSharp DotNetCore/ExampleTagDynamic.cs b/examples/CSharp DotNetCore/ExampleTagDynamic.cs index f51328b8..cd41191d 100644 --- a/examples/CSharp DotNetCore/ExampleTagDynamic.cs +++ b/examples/CSharp DotNetCore/ExampleTagDynamic.cs @@ -1,4 +1,11 @@ -using libplctag; +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using libplctag; using System; namespace CSharp_DotNetCore @@ -37,7 +44,7 @@ public TagDynamic() { _tag = new Tag() { - PlcType = libplctag.PlcType.ControlLogix, // Only certain types of devices support this, so hard-code this + PlcType = PlcType.ControlLogix, // Only certain types of devices support this, so hard-code this Protocol = Protocol.ab_eip, // Only certain types of devices support this, so hard-code this }; diff --git a/examples/CSharp DotNetCore/Inheritance/BaseTag.cs b/examples/CSharp DotNetCore/Inheritance/BaseTag.cs new file mode 100644 index 00000000..9104009a --- /dev/null +++ b/examples/CSharp DotNetCore/Inheritance/BaseTag.cs @@ -0,0 +1,249 @@ +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Threading; +using System.Threading.Tasks; +using libplctag; + +namespace CSharpDotNetCore.Inheritance +{ + + abstract class TagSingle : BaseTag + { + private readonly int elementSize; + public TagSingle(int elementSize) + : base() + { + this.elementSize = elementSize; + tag.ElementSize = elementSize; + } + + protected abstract T Decode(int offset); + protected abstract void Encode(int offset, T value); + + public T Value + { + get => Decode(0); + set => Encode(0, value); + } + + } + + abstract class Tag1D : BaseTag + { + private readonly int elementSize; + public Tag1D(int length, int elementSize) + : base() + { + this.elementSize = elementSize; + tag.ElementSize = elementSize; + Length = length; + } + + protected abstract T Decode(int offset); + protected abstract void Encode(int offset, T value); + + public T GetValue(int index) => Decode(index * elementSize); + public void SetValue(int index, T value) => Encode(index * elementSize, value); + + public int Length + { + get => tag.ElementCount.Value; + set => tag.ElementCount = value; + } + } + + + abstract class Tag2D : BaseTag + { + private readonly int elementSize; + private int lengthDimension1; + private int lengthDimension2; + + public Tag2D(int lengthDimension1, int lengthDimension2, int elementSize) + : base() + { + this.elementSize = elementSize; + tag.ElementSize = elementSize; + LengthDimension1 = lengthDimension1; + LengthDimension2 = lengthDimension1; + } + + protected abstract T Decode(int offset); + protected abstract void Encode(int offset, T value); + + private int GetOffset(int i, int j) => this.elementSize * (i + j * LengthDimension1); + + public T GetValue(int i, int j) => Decode(GetOffset(i, j)); + public void SetValue(int i, int j, T value) => Encode(GetOffset(i, j), value); + + public int LengthDimension1 + { + get => lengthDimension1; + set + { + tag.ElementCount = value * lengthDimension2; + lengthDimension1 = value; + } + } + + public int LengthDimension2 + { + get => lengthDimension2; + set + { + tag.ElementCount = lengthDimension1 * value; + lengthDimension2 = value; + } + } + } + + abstract class Tag3D : BaseTag + { + private readonly int elementSize; + private int lengthDimension1; + private int lengthDimension2; + private int lengthDimension3; + + public Tag3D(int lengthDimension1, int lengthDimension2, int lengthDimension3, int elementSize) + : base() + { + this.elementSize = elementSize; + tag.ElementSize = elementSize; + LengthDimension1 = lengthDimension1; + LengthDimension2 = lengthDimension2; + LengthDimension3 = lengthDimension3; + } + + protected abstract T Decode(int offset); + protected abstract void Encode(int offset, T value); + + private int GetOffset(int i, int j, int k) => elementSize * (i + j * LengthDimension1 + k * LengthDimension1 * LengthDimension2); + + public T GetValue(int i, int j, int k) => Decode(GetOffset(i, j, k)); + public void SetValue(int i, int j, int k, T value) => Encode(GetOffset(i, j, k), value); + + public int LengthDimension1 + { + get => lengthDimension1; + set + { + tag.ElementCount = value * lengthDimension2 * lengthDimension3; + lengthDimension1 = value; + } + } + + public int LengthDimension2 + { + get => lengthDimension2; + set + { + tag.ElementCount = lengthDimension1 * value * lengthDimension3; + lengthDimension2 = value; + } + } + + public int LengthDimension3 + { + get => lengthDimension3; + set + { + tag.ElementCount = lengthDimension1 * lengthDimension2 * value; + lengthDimension3 = value; + } + } + } + + abstract class BaseTag : IDisposable + { + + protected readonly Tag tag = new Tag(); + + public Protocol? Protocol + { + get => tag.Protocol; + set => tag.Protocol = value; + } + + public string Gateway + { + get => tag.Gateway; + set => tag.Gateway = value; + } + + public string Path + { + get => tag.Path; + set => tag.Path = value; + } + + public PlcType? PlcType + { + get => tag.PlcType; + set => tag.PlcType = value; + } + + public string Name + { + get => tag.Name; + set => tag.Name = value; + } + + public bool? UseConnectedMessaging + { + get => tag.UseConnectedMessaging; + set => tag.UseConnectedMessaging = value; + } + + public bool? AllowPacking + { + get => tag.AllowPacking; + set => tag.AllowPacking = value; + } + + public int? ReadCacheMillisecondDuration + { + get => tag.ReadCacheMillisecondDuration; + set => tag.ReadCacheMillisecondDuration = value; + } + + public TimeSpan Timeout + { + get => tag.Timeout; + set => tag.Timeout = value; + } + + public TimeSpan? AutoSyncReadInterval + { + get => tag.AutoSyncReadInterval; + set => tag.AutoSyncReadInterval = value; + } + + public TimeSpan? AutoSyncWriteInterval + { + get => tag.AutoSyncWriteInterval; + set => tag.AutoSyncWriteInterval = value; + } + + public DebugLevel DebugLevel + { + get => tag.DebugLevel; + set => tag.DebugLevel = value; + } + + public void Dispose() => tag.Dispose(); + public Task InitializeAsync(CancellationToken token = default) => tag.InitializeAsync(token); + public Task ReadAsync(CancellationToken token = default) => tag.ReadAsync(token); + public Task WriteAsync(CancellationToken token = default) => tag.WriteAsync(token); + + ~BaseTag() + { + Dispose(); + } + } +} diff --git a/examples/CSharp DotNetCore/Inheritance/Definitions.cs b/examples/CSharp DotNetCore/Inheritance/Definitions.cs new file mode 100644 index 00000000..38c15a61 --- /dev/null +++ b/examples/CSharp DotNetCore/Inheritance/Definitions.cs @@ -0,0 +1,55 @@ +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +namespace CSharpDotNetCore.Inheritance +{ + + class TagDint : TagSingle + { + public TagDint() + : base(elementSize:4) + { + } + + protected override int Decode(int offset) => tag.GetInt32(offset); + protected override void Encode(int offset, int value) => tag.SetInt32(offset, value); + } + + class TagDint1D : Tag1D + { + public TagDint1D(int length) + :base(length, elementSize:4) + { + } + + protected override int Decode(int offset) => tag.GetInt32(offset); + protected override void Encode(int offset, int value) => tag.SetInt32(offset, value); + } + + class TagDint2D : Tag2D + { + public TagDint2D(int dim1, int dim2) + : base(dim1, dim2, elementSize:4) + { + } + + protected override int Decode(int offset) => tag.GetInt32(offset); + protected override void Encode(int offset, int value) => tag.SetInt32(offset, value); + } + + class TagDint3D : Tag3D + { + public TagDint3D(int dim1, int dim2, int dim3) + : base(dim1, dim2, dim3, elementSize:4) + { + } + + protected override int Decode(int offset) => tag.GetInt32(offset); + protected override void Encode(int offset, int value) => tag.SetInt32(offset, value); + } + +} diff --git a/examples/CSharp DotNetCore/ListPlc.cs b/examples/CSharp DotNetCore/ListPlc.cs deleted file mode 100644 index 1ef080fa..00000000 --- a/examples/CSharp DotNetCore/ListPlc.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) libplctag.NET contributors -// https://github.com/libplctag/libplctag.NET -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -using ConsoleTables; -using libplctag; -using libplctag.DataTypes; -using libplctag.NativeImport; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace CSharpDotNetCore -{ - - class ListPlc - { - static public void Run() - { - - Console.WriteLine("Controller Tags"); - Console.WriteLine("==============="); - - var tags = new Tag() - { - Gateway = "192.168.0.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - Name = "@tags", - Timeout = TimeSpan.FromSeconds(10) - }; - - tags.Read(); - - foreach (var tag in tags.Value) - Console.WriteLine($"Id={tag.Id} Name={tag.Name} Type={tag.Type} Length={tag.Length}"); - - - - - Console.WriteLine(); - Console.WriteLine("Programs"); - Console.WriteLine("========"); - - foreach (var tag in tags.Value) - { - if (TagIsProgram(tag, out string programTagListingPrefix)) - { - var programsTag = new Tag() - { - Gateway = "192.168.0.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - Name = $"{programTagListingPrefix}.@tags", - Timeout = TimeSpan.FromSeconds(10) - }; - - programsTag.Read(); - - Console.WriteLine(programTagListingPrefix); - foreach (var program in programsTag.Value) - Console.WriteLine($" {program.Name}"); - - } - } - - - Console.WriteLine(); - Console.WriteLine("UDTs"); - Console.WriteLine("===="); - - var uniqueUdtTypeIds = tags.Value - .Where(tagInfo => TagIsUdt(tagInfo)) - .Select(tagInfo => GetUdtId(tagInfo)) - .Distinct(); - - foreach (var udtId in uniqueUdtTypeIds) - { - var udtTag = new Tag() - { - Gateway = "192.168.0.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - Name = $"@udt/{udtId}", - }; - - udtTag.Read(); - var udt = udtTag.Value; - - Console.WriteLine($"Id={udt.Id} Name={udt.Name} NumFields={udt.NumFields} Size={udt.Size}"); - foreach (var f in udt.Fields) - Console.WriteLine($" Name={f.Name} Offset={f.Offset} Metadata={f.Metadata} Type={f.Type}"); - } - - } - - static bool TagIsUdt(TagInfo tag) - { - const ushort TYPE_IS_STRUCT = 0x8000; - const ushort TYPE_IS_SYSTEM = 0x1000; - - return ((tag.Type & TYPE_IS_STRUCT) != 0) && !((tag.Type & TYPE_IS_SYSTEM) != 0); - } - - static int GetUdtId(TagInfo tag) - { - const ushort TYPE_UDT_ID_MASK = 0x0FFF; - return tag.Type & TYPE_UDT_ID_MASK; - } - - static bool TagIsProgram(TagInfo tag, out string prefix) - { - if (tag.Name.StartsWith("Program:")) - { - prefix = tag.Name; - return true; - } - else - { - prefix = string.Empty; - return false; - } - } - } -} diff --git a/examples/CSharp DotNetCore/ListUdtDefinitions.cs b/examples/CSharp DotNetCore/ListUdtDefinitions.cs deleted file mode 100644 index 9de0b8b7..00000000 --- a/examples/CSharp DotNetCore/ListUdtDefinitions.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) libplctag.NET contributors -// https://github.com/libplctag/libplctag.NET -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -using ConsoleTables; -using libplctag; -using libplctag.DataTypes; -using libplctag.NativeImport; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace CSharpDotNetCore -{ - - class ListUdtDefinitions - { - static public void Run() - { - - var tags = new Tag() - { - Gateway = "192.168.0.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - Name = "@tags" - }; - - tags.Read(); - - - var uniqueUdtTypeIds = tags.Value - .Where(tagInfo => TagIsUdt(tagInfo)) - .Select(tagInfo => GetUdtId(tagInfo)) - .Distinct(); - - - foreach (var udtId in uniqueUdtTypeIds) - { - var udtTag = new Tag() - { - Gateway = "192.168.0.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - Name = $"@udt/{udtId}", - }; - - udtTag.Read(); - var udt = udtTag.Value; - - Console.WriteLine($"Id={udt.Id} Name={udt.Name} NumFields={udt.NumFields} Size={udt.Size}"); - foreach (var f in udt.Fields) - Console.WriteLine($" Name={f.Name} Offset={f.Offset} Metadata={f.Metadata} Type={f.Type}"); - } - - } - - static bool TagIsUdt(TagInfo tag) - { - const ushort TYPE_IS_STRUCT = 0x8000; - const ushort TYPE_IS_SYSTEM = 0x1000; - - return ((tag.Type & TYPE_IS_STRUCT) != 0) && !((tag.Type & TYPE_IS_SYSTEM) != 0); - } - - static int GetUdtId(TagInfo tag) - { - const ushort TYPE_UDT_ID_MASK = 0x0FFF; - return tag.Type & TYPE_UDT_ID_MASK; - } - } -} diff --git a/examples/CSharp DotNetCore/ExampleArray.cs b/examples/CSharp DotNetCore/PlcMapper/ExampleArray.cs similarity index 94% rename from examples/CSharp DotNetCore/ExampleArray.cs rename to examples/CSharp DotNetCore/PlcMapper/ExampleArray.cs index a4f78aac..4d532c74 100644 --- a/examples/CSharp DotNetCore/ExampleArray.cs +++ b/examples/CSharp DotNetCore/PlcMapper/ExampleArray.cs @@ -6,12 +6,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using libplctag; -using libplctag.DataTypes; using System; -using System.Net; -using System.Threading; -namespace CSharpDotNetCore +namespace CSharpDotNetCore.PlcMapper { class ExampleArray { diff --git a/examples/CSharp DotNetCore/SequencePlcMapper.cs b/examples/CSharp DotNetCore/PlcMapper/ExampleCustomUdt.cs similarity index 71% rename from examples/CSharp DotNetCore/SequencePlcMapper.cs rename to examples/CSharp DotNetCore/PlcMapper/ExampleCustomUdt.cs index b6100e96..c969eee2 100644 --- a/examples/CSharp DotNetCore/SequencePlcMapper.cs +++ b/examples/CSharp DotNetCore/PlcMapper/ExampleCustomUdt.cs @@ -6,13 +6,41 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using libplctag; -using libplctag.DataTypes; using System; using System.Collections; -namespace CSharpDotNetCore +namespace CSharpDotNetCore.PlcMapper { + class ExampleCustomUdt + { + public static void Run() + { + const string gateway = "10.10.10.10"; + const string path = "1,0"; + + //This example shows the use of a custom mapper created to match a UDT called "Sequence" + + var sequenceArray = new Tag() + { + Name = "MY_SEQUENCE_3D[0,0,0]", + Gateway = gateway, + Path = path, + Protocol = Protocol.ab_eip, + PlcType = PlcType.ControlLogix, + ArrayDimensions = new int[] { 8 }, + }; + + for (int ii = 0; ii < 8; ii++) + sequenceArray.Value[ii].Command = ii * 2; + + sequenceArray.Write(); + + + Console.WriteLine("DONE! Check values in RsLogix"); + + } + } /// /// This is an example plcMapper for a User Defined Type (UDT) @@ -209,5 +237,71 @@ public class Sequence public AbTimer[] Timer { get; set; } } + public class TimerPlcMapper : PlcMapperBase + { + + public override int? ElementSize => 12; + + public override AbTimer Decode(Tag tag, int offset) + { + + // Needed to look at RsLogix documentation for structure of TIMER + var DINT2 = tag.GetInt32(offset); + var DINT1 = tag.GetInt32(offset + 4); + var DINT0 = tag.GetInt32(offset + 8); + + // The third DINT packs a few BOOLs into it + var bitArray = new BitArray(new int[] { DINT2 }); + + var timer = new AbTimer + { + Accumulated = DINT0, // ACC + Preset = DINT1, // PRE + Done = bitArray[29], // DN + InProgress = bitArray[30], // TT + Enabled = bitArray[31] // EN + }; + + return timer; + } + + public override void Encode(Tag tag, int offset, AbTimer value) + { + var DINT0 = value.Accumulated; + var DINT1 = value.Preset; + + var asdf = new BitArray(32); + asdf[29] = value.Done; + asdf[30] = value.InProgress; + asdf[31] = value.Enabled; + var DINT2 = BitArrayToInt(asdf); + + tag.SetInt32(offset, DINT2); + tag.SetInt32(offset + 4, DINT1); + tag.SetInt32(offset + 8, DINT0); + + } + + static int BitArrayToInt(BitArray binary) + { + if (binary == null) + throw new ArgumentNullException("binary"); + if (binary.Length > 32) + throw new ArgumentException("Must be at most 32 bits long"); + + var result = new int[1]; + binary.CopyTo(result, 0); + return result[0]; + } + } + + public class AbTimer + { + public int Preset { get; set; } + public int Accumulated { get; set; } + public bool Enabled { get; set; } + public bool InProgress { get; set; } + public bool Done { get; set; } + } } diff --git a/examples/CSharp DotNetCore/ExampleDatatypes.cs b/examples/CSharp DotNetCore/PlcMapper/ExampleDatatypes.cs similarity index 95% rename from examples/CSharp DotNetCore/ExampleDatatypes.cs rename to examples/CSharp DotNetCore/PlcMapper/ExampleDatatypes.cs index f1fd40c6..0274477b 100644 --- a/examples/CSharp DotNetCore/ExampleDatatypes.cs +++ b/examples/CSharp DotNetCore/PlcMapper/ExampleDatatypes.cs @@ -6,14 +6,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using libplctag; -using libplctag.DataTypes; using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -namespace CSharpDotNetCore +namespace CSharpDotNetCore.PlcMapper { class ExampleDatatypes { diff --git a/examples/CSharp DotNetCore/ExampleDatatypesSimple.cs b/examples/CSharp DotNetCore/PlcMapper/ExampleDatatypesSimple.cs similarity index 95% rename from examples/CSharp DotNetCore/ExampleDatatypesSimple.cs rename to examples/CSharp DotNetCore/PlcMapper/ExampleDatatypesSimple.cs index dd1bdf9a..4b1952e2 100644 --- a/examples/CSharp DotNetCore/ExampleDatatypesSimple.cs +++ b/examples/CSharp DotNetCore/PlcMapper/ExampleDatatypesSimple.cs @@ -6,14 +6,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using libplctag; -using libplctag.DataTypes.Simple; using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -namespace CSharpDotNetCore +namespace CSharpDotNetCore.PlcMapper { class ExampleDatatypesSimple { diff --git a/examples/CSharp DotNetCore/PlcMapper/ExampleListPlc.cs b/examples/CSharp DotNetCore/PlcMapper/ExampleListPlc.cs new file mode 100644 index 00000000..eff7d21c --- /dev/null +++ b/examples/CSharp DotNetCore/PlcMapper/ExampleListPlc.cs @@ -0,0 +1,306 @@ +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using libplctag; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CSharpDotNetCore.PlcMapper +{ + + class ExampleListPlc + { + static public void Run() + { + + Console.WriteLine("Controller Tags"); + Console.WriteLine("==============="); + + var tags = new Tag() + { + Gateway = "192.168.0.10", + Path = "1,0", + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + Name = "@tags", + Timeout = TimeSpan.FromSeconds(10) + }; + + tags.Read(); + + foreach (var tag in tags.Value) + Console.WriteLine($"Id={tag.Id} Name={tag.Name} Type={tag.Type} Length={tag.Length}"); + + + + + Console.WriteLine(); + Console.WriteLine("Programs"); + Console.WriteLine("========"); + + foreach (var tag in tags.Value) + { + if (TagIsProgram(tag, out string programTagListingPrefix)) + { + var programsTag = new Tag() + { + Gateway = "192.168.0.10", + Path = "1,0", + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + Name = $"{programTagListingPrefix}.@tags", + Timeout = TimeSpan.FromSeconds(10) + }; + + programsTag.Read(); + + Console.WriteLine(programTagListingPrefix); + foreach (var program in programsTag.Value) + Console.WriteLine($" {program.Name}"); + + } + } + + + Console.WriteLine(); + Console.WriteLine("UDTs"); + Console.WriteLine("===="); + + var uniqueUdtTypeIds = tags.Value + .Where(tagInfo => TagIsUdt(tagInfo)) + .Select(tagInfo => GetUdtId(tagInfo)) + .Distinct(); + + foreach (var udtId in uniqueUdtTypeIds) + { + var udtTag = new Tag() + { + Gateway = "192.168.0.10", + Path = "1,0", + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip, + Name = $"@udt/{udtId}", + }; + + udtTag.Read(); + var udt = udtTag.Value; + + Console.WriteLine($"Id={udt.Id} Name={udt.Name} NumFields={udt.NumFields} Size={udt.Size}"); + foreach (var f in udt.Fields) + Console.WriteLine($" Name={f.Name} Offset={f.Offset} Metadata={f.Metadata} Type={f.Type}"); + } + + } + + static bool TagIsUdt(TagInfo tag) + { + const ushort TYPE_IS_STRUCT = 0x8000; + const ushort TYPE_IS_SYSTEM = 0x1000; + + return ((tag.Type & TYPE_IS_STRUCT) != 0) && !((tag.Type & TYPE_IS_SYSTEM) != 0); + } + + static int GetUdtId(TagInfo tag) + { + const ushort TYPE_UDT_ID_MASK = 0x0FFF; + return tag.Type & TYPE_UDT_ID_MASK; + } + + static bool TagIsProgram(TagInfo tag, out string prefix) + { + if (tag.Name.StartsWith("Program:")) + { + prefix = tag.Name; + return true; + } + else + { + prefix = string.Empty; + return false; + } + } + } + + public class TagInfo + { + public uint Id { get; set; } + public ushort Type { get; set; } + public string Name { get; set; } + public ushort Length { get; set; } + public uint[] Dimensions { get; set; } + } + + public class TagInfoPlcMapper : IPlcMapper + { + + const int TAG_STRING_SIZE = 200; + + public PlcType PlcType { get; set; } + + //TODO: Is null appropriate since it's unknown? + public int? ElementSize => null; + public int[] ArrayDimensions { get => null; set => throw new NotImplementedException("This plcMapper can only be used to read Tag Information"); } + + public TagInfo Decode(Tag tag, int offset, out int elementSize) + { + + var tagInstanceId = tag.GetUInt32(offset); + var tagType = tag.GetUInt16(offset + 4); + var tagLength = tag.GetUInt16(offset + 6); + var tagArrayDims = new uint[] + { + tag.GetUInt32(offset + 8), + tag.GetUInt32(offset + 12), + tag.GetUInt32(offset + 16) + }; + + var apparentTagNameLength = (int)tag.GetUInt16(offset + 20); + var actualTagNameLength = Math.Min(apparentTagNameLength, TAG_STRING_SIZE * 2 - 1); + + var tagNameBytes = Enumerable.Range(offset + 22, actualTagNameLength) + .Select(o => tag.GetUInt8(o)) + .Select(Convert.ToByte) + .ToArray(); + + var tagName = Encoding.ASCII.GetString(tagNameBytes); + + elementSize = 22 + actualTagNameLength; + + return new TagInfo() + { + Id = tagInstanceId, + Type = tagType, + Name = tagName, + Length = tagLength, + Dimensions = tagArrayDims + }; + + } + + public TagInfo[] Decode(Tag tag) + { + var buffer = new List(); + + var tagSize = tag.GetSize(); + + int offset = 0; + while (offset < tagSize) + { + buffer.Add(Decode(tag, offset, out int elementSize)); + offset += elementSize; + } + + return buffer.ToArray(); + } + + public void Encode(Tag tag, TagInfo[] value) + { + throw new NotImplementedException("This plcMapper can only be used to read Tag Information"); + } + + public int? GetElementCount() + { + //TODO: We know this value after we decode once. SHould we trigger a decode or cache the value after first decode? + return null; + } + } + + public class UdtFieldInfo + { + public string Name { get; set; } + public ushort Type { get; set; } + public ushort Metadata { get; set; } + public uint Offset { get; set; } + } + + public class UdtInfo + { + public uint Size { get; set; } + public string Name { get; set; } + public ushort Id { get; set; } + public ushort NumFields { get; set; } + public ushort Handle { get; set; } + public UdtFieldInfo[] Fields { get; set; } + } + + public class UdtInfoPlcMapper : IPlcMapper + { + public PlcType PlcType { get; set; } + //TODO: Is null appropriate since it's unknown? + public int? ElementSize => null; + public int[] ArrayDimensions { get => null; set => throw new NotImplementedException("This plcMapper can only be used to read"); } + + public UdtInfo Decode(Tag tag) + { + + var template_id = tag.GetUInt16(0); + var member_desc_size = tag.GetUInt32(2); + var udt_instance_size = tag.GetUInt32(6); + var num_members = tag.GetUInt16(10); + var struct_handle = tag.GetUInt16(12); + + var udtInfo = new UdtInfo() + { + Fields = new UdtFieldInfo[num_members], + NumFields = num_members, + Handle = struct_handle, + Id = template_id, + Size = udt_instance_size + }; + + var offset = 14; + + for (int field_index = 0; field_index < num_members; field_index++) + { + ushort field_metadata = tag.GetUInt16(offset); + offset += 2; + + ushort field_element_type = tag.GetUInt16(offset); + offset += 2; + + ushort field_offset = tag.GetUInt16(offset); + offset += 4; + + var field = new UdtFieldInfo() + { + Offset = field_offset, + Metadata = field_metadata, + Type = field_element_type, + }; + + udtInfo.Fields[field_index] = field; + } + + var name_str = tag.GetString(offset).Split(';')[0]; + udtInfo.Name = name_str; + + offset += tag.GetStringTotalLength(offset); + + for (int field_index = 0; field_index < num_members; field_index++) + { + udtInfo.Fields[field_index].Name = tag.GetString(offset); + offset += tag.GetStringTotalLength(offset); + } + + return udtInfo; + + } + + public void Encode(Tag tag, UdtInfo value) + { + throw new NotImplementedException("This plcMapper can only be used to read"); + } + + public int? GetElementCount() + { + //TODO: We know this value after we decode once. SHould we trigger a decode or cache the value after first decode? + return null; + } + } +} diff --git a/examples/CSharp DotNetCore/PlcMapper/ExampleMappers.cs b/examples/CSharp DotNetCore/PlcMapper/ExampleMappers.cs new file mode 100644 index 00000000..16c4613f --- /dev/null +++ b/examples/CSharp DotNetCore/PlcMapper/ExampleMappers.cs @@ -0,0 +1,389 @@ +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using libplctag; +using System.Linq; +using System; +using System.Collections.Generic; + +namespace CSharpDotNetCore.PlcMapper +{ + + public abstract class PlcMapperBase : IPlcMapper, IPlcMapper, IPlcMapper, IPlcMapper + { + public PlcType PlcType { get; set; } + + abstract public int? ElementSize { get; } + + public int[] ArrayDimensions { get; set; } + + //Multiply all the dimensions to get total elements + virtual public int? GetElementCount() => ArrayDimensions?.Aggregate(1, (x, y) => x * y); + + virtual protected T[] DecodeArray(Tag tag) + { + if (ElementSize is null) + throw new ArgumentNullException($"{nameof(ElementSize)} cannot be null for array decoding"); + + + var buffer = new List(); + + var tagSize = tag.GetSize(); + + int offset = 0; + while (offset < tagSize) + { + buffer.Add(Decode(tag, offset)); + offset += ElementSize.Value; + } + + return buffer.ToArray(); + + } + + virtual protected void EncodeArray(Tag tag, T[] values) + { + if (ElementSize is null) + { + throw new ArgumentNullException($"{nameof(ElementSize)} cannot be null for array encoding"); + } + + int offset = 0; + foreach (var item in values) + { + Encode(tag, offset, item); + offset += ElementSize.Value; + } + } + + virtual public T Decode(Tag tag) => Decode(tag, 0); + public abstract T Decode(Tag tag, int offset); + + + virtual public void Encode(Tag tag, T value) => Encode(tag, 0, value); + public abstract void Encode(Tag tag, int offset, T value); + + virtual public void Encode(Tag tag, T[] value) => EncodeArray(tag, value); + + T[] IPlcMapper.Decode(Tag tag) => DecodeArray(tag); + + + T[,] IPlcMapper.Decode(Tag tag) => DecodeArray(tag).To2DArray(ArrayDimensions[0], ArrayDimensions[1]); + + void IPlcMapper.Encode(Tag tag, T[,] value) => EncodeArray(tag, value.To1DArray()); + + T[,,] IPlcMapper.Decode(Tag tag) => DecodeArray(tag).To3DArray(ArrayDimensions[0], ArrayDimensions[1], ArrayDimensions[2]); + + void IPlcMapper.Encode(Tag tag, T[,,] value) => EncodeArray(tag, value.To1DArray()); + } + + public class BoolPlcMapper : IPlcMapper, IPlcMapper, IPlcMapper, IPlcMapper + { + public int? ElementSize => 1; + + public PlcType PlcType { get; set; } + public int[] ArrayDimensions { get; set; } + + public int? GetElementCount() + { + if (ArrayDimensions == null) + return null; + + //TODO: Test -> I'm not confident that the overall bool count is packed as a 1D array and not packed by dimension. + //Multiply dimensions for total elements + var totalElements = ArrayDimensions.Aggregate(1, (x, y) => x * y); + return (int)Math.Ceiling((double)totalElements / 32.0); + } + + public int? SetArrayLength(int? elementCount) => (int)Math.Ceiling((double)elementCount.Value / 32.0); + + virtual protected bool[] DecodeArray(Tag tag) + { + if (ElementSize is null) + throw new ArgumentNullException($"{nameof(ElementSize)} cannot be null for array decoding"); + + var buffer = new bool[tag.ElementCount.Value * 32]; + for (int ii = 0; ii < tag.ElementCount.Value * 32; ii++) + { + buffer[ii] = tag.GetBit(ii); + } + return buffer; + } + + virtual protected void EncodeArray(Tag tag, bool[] values) + { + for (int ii = 0; ii < tag.ElementCount.Value * 32; ii++) + { + tag.SetBit(ii, values[ii]); + } + } + + bool IPlcMapper.Decode(Tag tag) => tag.GetUInt8(0) != 0; + + void IPlcMapper.Encode(Tag tag, bool value) => tag.SetUInt8(0, value == true ? (byte)255 : (byte)0); + + bool[] IPlcMapper.Decode(Tag tag) => DecodeArray(tag); + + void IPlcMapper.Encode(Tag tag, bool[] value) => EncodeArray(tag, value); + + bool[,] IPlcMapper.Decode(Tag tag) => DecodeArray(tag).To2DArray(ArrayDimensions[0], ArrayDimensions[1]); + + void IPlcMapper.Encode(Tag tag, bool[,] value) => EncodeArray(tag, value.To1DArray()); + + bool[,,] IPlcMapper.Decode(Tag tag) => DecodeArray(tag).To3DArray(ArrayDimensions[0], ArrayDimensions[1], ArrayDimensions[2]); + + void IPlcMapper.Encode(Tag tag, bool[,,] value) => EncodeArray(tag, value.To1DArray()); + + } + + public class DintPlcMapper : PlcMapperBase + { + public override int? ElementSize => 4; + + override public int Decode(Tag tag, int offset) => tag.GetInt32(offset); + + override public void Encode(Tag tag, int offset, int value) => tag.SetInt32(offset, value); + + } + + public class IntPlcMapper : PlcMapperBase + { + public override int? ElementSize => 2; + + override public short Decode(Tag tag, int offset) => tag.GetInt16(offset); + + override public void Encode(Tag tag, int offset, short value) => tag.SetInt16(offset, value); + + } + + public class LintPlcMapper : PlcMapperBase + { + public override int? ElementSize => 8; + + override public long Decode(Tag tag, int offset) => tag.GetInt64(offset); + + override public void Encode(Tag tag, int offset, long value) => tag.SetInt64(offset, value); + + } + + public class LrealPlcMapper : PlcMapperBase + { + + override public int? ElementSize => 8; + + override public double Decode(Tag tag, int offset) => tag.GetFloat64(offset); + + override public void Encode(Tag tag, int offset, double value) => tag.SetFloat64(offset, value); + } + + public class RealPlcMapper : PlcMapperBase + { + + override public int? ElementSize => 4; + + override public float Decode(Tag tag, int offset) => tag.GetFloat32(offset); + + override public void Encode(Tag tag, int offset, float value) => tag.SetFloat32(offset, value); + + } + + public class SintPlcMapper : PlcMapperBase + { + + override public int? ElementSize => 1; + + override public sbyte Decode(Tag tag, int offset) => tag.GetInt8(offset); + + override public void Encode(Tag tag, int offset, sbyte value) => tag.SetInt8(offset, value); + + } + + public class StringPlcMapper : PlcMapperBase + { + + override public int? ElementSize + { + get + { + switch (PlcType) + { + case PlcType.ControlLogix: return 88; + case PlcType.Plc5: return 84; + case PlcType.Slc500: return 84; + case PlcType.LogixPccc: return 84; + case PlcType.Micro800: return 256; //To be Confirmed + case PlcType.MicroLogix: return 84; + default: throw new NotImplementedException(); + } + } + } + + + override public string Decode(Tag tag, int offset) => tag.GetString(offset); + override public void Encode(Tag tag, int offset, string value) => tag.SetString(offset, value); + + } + + public static class ArrayExtensions + { + /// + /// Extension method to flatten a 2D array to a 1D array + /// + /// Array Type + /// 2D array to be flattened + /// 1D array + public static T[] To1DArray(this T[,] input) + { + // Step 1: get total size of 2D array, and allocate 1D array. + int size = input.Length; + T[] result = new T[size]; + + // Step 2: copy 2D array elements into a 1D array. + int write = 0; + for (int i = 0; i <= input.GetUpperBound(0); i++) + { + for (int z = 0; z <= input.GetUpperBound(1); z++) + { + result[write++] = input[i, z]; + } + } + // Step 3: return the new array. + return result; + } + + /// + /// Extension method to flatten a 3D array to a 1D array + /// + /// Array Type + /// 3D array to be flattened + /// 1D array + public static T[] To1DArray(this T[,,] input) + { + // Step 1: get total size of 3D array, and allocate 1D array. + int size = input.Length; + T[] result = new T[size]; + + // Step 2: copy 3D array elements into a 1D array. + int write = 0; + for (int i = 0; i <= input.GetUpperBound(0); i++) + { + for (int j = 0; j <= input.GetUpperBound(1); j++) + { + for (int k = 0; k < input.GetUpperBound(2); k++) + { + result[write++] = input[i, j, k]; + } + } + } + // Step 3: return the new array. + return result; + } + + /// + /// Extension method to reshape a 1D array into a 2D array + /// + /// Array Type + /// 1D array to be reshaped + /// Desired height (first index) of 2D array + /// Desired width (second index) of 2D array + /// 2D array + public static T[,] To2DArray(this T[] input, int height, int width) + { + T[,] output = new T[height, width]; + + for (int i = 0; i < height; i++) + { + for (int j = 0; j < width; j++) + { + output[i, j] = input[i * width + j]; + } + } + return output; + } + + /// + /// Extension method to reshape a 1D array into a 3D array + /// + /// Array Type + /// 1D array to be reshaped + /// Desired height (first index) of 3D array + /// Desired width (second index) of 3D array + /// Desired length (third index) of 3D array + /// #D array + public static T[,,] To3DArray(this T[] input, int height, int width, int length) + { + T[,,] output = new T[height, width, length]; + + for (int i = 0; i < height; i++) + { + for (int j = 0; j < width; j++) + { + for (int k = 0; k < length; k++) + { + output[i, j, k] = input[i * height * width + j * width + k]; + } + } + } + return output; + } + + } + + public class TagBool : Tag { } + public class TagBool1D : Tag { } + public class TagBool2D : Tag { } + public class TagBool3D : Tag { } + + + public class TagDint : Tag { } + public class TagDint1D : Tag { } + public class TagDint2D : Tag { } + public class TagDint3D : Tag { } + + + public class TagInt : Tag { } + public class TagInt1D : Tag { } + public class TagInt2D : Tag { } + public class TagInt3D : Tag { } + + + public class TagLint : Tag { } + public class TagLint1D : Tag { } + public class TagLint2D : Tag { } + public class TagLint3D : Tag { } + + + public class TagLreal : Tag { } + public class TagLreal1D : Tag { } + public class TagLreal2D : Tag { } + public class TagLreal3D : Tag { } + + + public class TagReal : Tag { } + public class TagReal1D : Tag { } + public class TagReal2D : Tag { } + public class TagReal3D : Tag { } + + + public class TagSint : Tag { } + public class TagSint1D : Tag { } + public class TagSint2D : Tag { } + public class TagSint3D : Tag { } + + + public class TagString : Tag { } + public class TagString1D : Tag { } + public class TagString2D : Tag { } + public class TagString3D : Tag { } + + + public class TagTagInfo : Tag { } + + public class TagTimer : Tag { } + + public class TagUdtInfo : Tag { } +} diff --git a/examples/CSharp DotNetCore/ExampleNotifyChanged.cs b/examples/CSharp DotNetCore/PlcMapper/ExampleNotifyChanged.cs similarity index 98% rename from examples/CSharp DotNetCore/ExampleNotifyChanged.cs rename to examples/CSharp DotNetCore/PlcMapper/ExampleNotifyChanged.cs index 673f335a..0607d760 100644 --- a/examples/CSharp DotNetCore/ExampleNotifyChanged.cs +++ b/examples/CSharp DotNetCore/PlcMapper/ExampleNotifyChanged.cs @@ -5,11 +5,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -using libplctag.DataTypes; using libplctag; using System; -namespace CSharp_DotNetCore +namespace CSharpDotNetCore.PlcMapper { internal class ExampleNotifyChanged { diff --git a/examples/CSharp DotNetCore/PlcMapper/TagOfT.cs b/examples/CSharp DotNetCore/PlcMapper/TagOfT.cs new file mode 100644 index 00000000..d4fb28e6 --- /dev/null +++ b/examples/CSharp DotNetCore/PlcMapper/TagOfT.cs @@ -0,0 +1,344 @@ +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using libplctag; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CSharpDotNetCore.PlcMapper +{ + /// + /// A class that allows for strongly-typed objects tied to PLC tags + /// + /// A class that handles data conversion + /// The desired C# type of Tag.Value + public class Tag : IDisposable, ITag where M : IPlcMapper, new() + { + + private readonly Tag _tag; + private readonly IPlcMapper _plcMapper; + + public Tag() + { + _plcMapper = new M(); + _tag = new Tag() + { + ElementSize = _plcMapper.ElementSize, + }; + + _tag.ReadStarted += (s, e) => ReadStarted?.Invoke(this, e); + _tag.ReadCompleted += (s, e) => + { + // If AutoSyncReadInterval is configured, then this event was almost certainly not triggered + // by a call to Read/ReadAsync - and therefore the data needs to be decoded. + if(AutoSyncReadInterval != null) DecodeAll(); + ReadCompleted?.Invoke(this, e); + }; + _tag.WriteStarted += (s, e) => WriteStarted?.Invoke(this, e); + _tag.WriteCompleted += (s, e) => WriteCompleted?.Invoke(this, e); + _tag.Aborted += (s, e) => Aborted?.Invoke(this, e); + _tag.Destroyed += (s, e) => Destroyed?.Invoke(this, e); + } + + /// + public Protocol? Protocol + { + get => _tag.Protocol; + set => _tag.Protocol = value; + } + + /// + public string Gateway + { + get => _tag.Gateway; + set => _tag.Gateway = value; + } + + /// + public string Path + { + get => _tag.Path; + set => _tag.Path = value; + } + + /// + public PlcType? PlcType + { + get => _tag.PlcType; + set + { + _tag.PlcType = value; + if(value.HasValue) + _plcMapper.PlcType = value.Value; + } + } + + /// + public string Name + { + get => _tag.Name; + set => _tag.Name = value; + } + + /// + public bool? UseConnectedMessaging + { + get => _tag.UseConnectedMessaging; + set => _tag.UseConnectedMessaging = value; + } + + /// + public bool? AllowPacking + { + get => _tag.AllowPacking; + set => _tag.AllowPacking = value; + } + + /// + public int? ReadCacheMillisecondDuration + { + get => _tag.ReadCacheMillisecondDuration; + set => _tag.ReadCacheMillisecondDuration = value; + } + + /// + public TimeSpan Timeout + { + get => _tag.Timeout; + set => _tag.Timeout = value; + } + + /// + public TimeSpan? AutoSyncReadInterval + { + get => _tag.AutoSyncReadInterval; + set => _tag.AutoSyncReadInterval = value; + } + + /// + public TimeSpan? AutoSyncWriteInterval + { + get => _tag.AutoSyncWriteInterval; + set => _tag.AutoSyncWriteInterval = value; + } + + /// + public DebugLevel DebugLevel + { + get => _tag.DebugLevel; + set => _tag.DebugLevel = value; + } + + /// + public uint? MaxRequestsInFlight + { + get => _tag.MaxRequestsInFlight; + set => _tag.MaxRequestsInFlight = value; + } + + /// + /// Dimensions of Value if it is an array + /// Ex. {2, 10} for a 2 column, 10 row array + /// Non-arrays can use null (default) + /// + public int[] ArrayDimensions + { + get => _plcMapper.ArrayDimensions; + set + { + _plcMapper.ArrayDimensions = value; + _tag.ElementCount = _plcMapper.GetElementCount(); + } + } + + /// + public void Initialize() + { + _tag.Initialize(); + DecodeAll(); + } + + /// + public async Task InitializeAsync(CancellationToken token = default) + { + await _tag.InitializeAsync(token).ConfigureAwait(false); + DecodeAll(); + } + + /// + public async Task ReadAsync(CancellationToken token = default) + { + await _tag.ReadAsync(token).ConfigureAwait(false); + DecodeAll(); + return Value; + } + + /// + public T Read() + { + _tag.Read(); + DecodeAll(); + return Value; + } + + object ITag.Read() => Read(); + + async Task ITag.ReadAsync(CancellationToken token) => await ReadAsync().ConfigureAwait(false); + + /// + public async Task WriteAsync(CancellationToken token = default) + { + if (!_tag.IsInitialized) + await _tag.InitializeAsync(token).ConfigureAwait(false); + + EncodeAll(); + await _tag.WriteAsync(token).ConfigureAwait(false); + } + + /// + public async Task WriteAsync(T value, CancellationToken token = default) + { + Value = value; + await WriteAsync(token).ConfigureAwait(false); + } + + /// + public void Write() + { + if (!_tag.IsInitialized) + _tag.Initialize(); + + EncodeAll(); + _tag.Write(); + } + + /// + public void Write(T value) + { + Value = value; + Write(); + } + + void DecodeAll() + { + Value = _plcMapper.Decode(_tag); + } + + void EncodeAll() + { + _plcMapper.Encode(_tag, Value); + } + + /// + public Status GetStatus() => _tag.GetStatus(); + + public void Dispose() => _tag.Dispose(); + + ~Tag() + { + Dispose(); + } + + /// + /// The local memory value that can be transferred to/from the PLC + /// + public T Value { get; set; } + object ITag.Value { get => Value; set => Value = (T)value; } + public event EventHandler ReadStarted; + public event EventHandler ReadCompleted; + public event EventHandler WriteStarted; + public event EventHandler WriteCompleted; + public event EventHandler Aborted; + public event EventHandler Destroyed; + + } + + public interface IPlcMapper + { + /// + /// You can define different marshalling behaviour for different types + /// The PlcType is injected during PlcMapper instantiation, and + /// will be available to you in your marshalling logic + /// + PlcType PlcType { get; set; } + + + /// + /// Provide an integer value for ElementSize if you + /// want to pass this into the tag constructor + /// + int? ElementSize { get; } + + /// + /// The dimensions of the array. Null if not an array. + /// + int[] ArrayDimensions { get; set; } + + /// + /// This is used to convert the number of array elements + /// into the raw element count, which is used by the library. + /// Most of the time, this will be the dimensions multiplied, but occasionally + /// it is not (e.g. BOOL arrays). + /// + int? GetElementCount(); + + /// + /// This is the method that reads/unpacks the underlying value of the tag + /// and returns it as a C# type + /// + /// Tag to be Decoded + /// C# value of tag + T Decode(Tag tag); + + /// + /// This is the method that transforms the C# type into the underlying value of the tag + /// + /// Tag to be encoded to + /// C# value to be transformed + void Encode(Tag tag, T value); + } + + /// + /// An interface to represent any generic tag without + /// exposing its value + /// + public interface ITag : IDisposable + { + int[] ArrayDimensions { get; set; } + string Gateway { get; set; } + string Name { get; set; } + string Path { get; set; } + PlcType? PlcType { get; set; } + Protocol? Protocol { get; set; } + int? ReadCacheMillisecondDuration { get; set; } + TimeSpan Timeout { get; set; } + bool? UseConnectedMessaging { get; set; } + bool? AllowPacking { get; set; } + TimeSpan? AutoSyncReadInterval { get; set; } + TimeSpan? AutoSyncWriteInterval { get; set; } + DebugLevel DebugLevel { get; set; } + + event EventHandler ReadStarted; + event EventHandler ReadCompleted; + event EventHandler WriteStarted; + event EventHandler WriteCompleted; + event EventHandler Aborted; + event EventHandler Destroyed; + + Status GetStatus(); + void Initialize(); + Task InitializeAsync(CancellationToken token = default); + object Read(); + Task ReadAsync(CancellationToken token = default); + void Write(); + Task WriteAsync(CancellationToken token = default); + + object Value { get; set; } + } + +} diff --git a/examples/CSharp DotNetCore/Program.cs b/examples/CSharp DotNetCore/Program.cs index e0c5631e..53b4f25a 100644 --- a/examples/CSharp DotNetCore/Program.cs +++ b/examples/CSharp DotNetCore/Program.cs @@ -13,20 +13,7 @@ class Program { static void Main(string[] args) { - ILoggerExample.Run(); - //LoggingExample.Run(); - //TestDatatypes.Run(); - //ExampleGenericTag.UDT_Array(); - //ExampleAsync.SyncAsyncMultipleTagComparison(); - //ExampleAsync.AsyncParallelCancellation(); - //ExampleGenericTag.Run(); - //ExampleRW.Run(); - //ExampleArray.Run(); - //ExampleListTags.Run(); - //ListUdtDefinitions.Run(); - //ListPlc.Run(); - //ExampleRW.Run(); - //ExampleArray.Run(); + ExampleSimple.Run(); Console.ReadKey(); } } diff --git a/examples/CSharp DotNetCore/SimpleExample.cs b/examples/CSharp DotNetCore/SimpleExample.cs deleted file mode 100644 index 7563afa9..00000000 --- a/examples/CSharp DotNetCore/SimpleExample.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) libplctag.NET contributors -// https://github.com/libplctag/libplctag.NET -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -using libplctag; -using libplctag.DataTypes; -using System; -using System.Net; -using System.Threading; - -namespace CSharpDotNetCore -{ - class SimpleExample - { - public static void Run() - { - //This is the absolute most simplified example code - //Please see the other examples for more features/optimizations - - //Instantiate the tag with the proper mapper and datatype - var myTag = new Tag() - { - Name = "PROGRAM:SomeProgram.SomeDINT", - Gateway = "10.10.10.10", - Path = "1,0", - PlcType = PlcType.ControlLogix, - Protocol = Protocol.ab_eip, - Timeout = TimeSpan.FromSeconds(5) - }; - - //Write value to PLC - //This will call Initialize internally since it's the first use of this tag - //myTag.Value will be set to 3737 before being transferred to PLC - myTag.Write(3737); - - //Read value from PLC - //Value will also be accessible at myTag.Value - int myDint = myTag.Read(); - - //Write to console - Console.WriteLine(myDint); - } - } -} diff --git a/examples/CSharp DotNetCore/TestDatatypes.cs b/examples/CSharp DotNetCore/TestDatatypes.cs deleted file mode 100644 index 34c35f67..00000000 --- a/examples/CSharp DotNetCore/TestDatatypes.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) libplctag.NET contributors -// https://github.com/libplctag/libplctag.NET -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -using KellermanSoftware.CompareNetObjects; -using libplctag; -using libplctag.DataTypes; -using RandomTestValues; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading; - -namespace CSharpDotNetCore -{ - class TestDatatypes - { - //This is built for testing the basic functions until unit testing without hardware is feasible - - private const int DEFAULT_TIMEOUT = 1000; - private const string GATEWAY = "10.10.10.10"; - const string PATH = "1,0"; - const PlcType PLC_TYPE = PlcType.ControlLogix; - const Protocol PROTOCOL = Protocol.ab_eip; - - - public static void Run() - { - - //Bool - Test both cases - //Random value would look correct 50% of the time - var boolTag = BuildTag("TestBOOL"); - TestTag(boolTag, true); - TestTag(boolTag, false); - - //Signed Numbers - TestTag(BuildTag("TestSINT")); - TestTag(BuildTag("TestINT")); - TestTag(BuildTag("TestDINT")); - TestTag(BuildTag("TestLINT")); - - //Logix doesn't support unsigned - - //Floating Points - TestTag(BuildTag("TestREAL")); - //TestTag(new GenericTag(gateway, Path, PlcType.Logix, "TestLREAL", timeout)); - - //String - var tagString = BuildTag("TestSTRING"); - TestTag(tagString, RandomValue.String(82)); - - //Arrays - var tagArray = BuildTag("TestDINTArray"); - tagArray.ArrayDimensions = new int[] { 5 }; - TestTag(tagArray, RandomValue.Array(5)); - - } - - - private static bool TestTag(Tag tag) where T : struct where M : IPlcMapper, new() - { - //HACK: RandomValue.Object only supports classes generically - T testValue = RandomValue.List(1).Single(); - return TestTag(tag, testValue); - } - - private static bool TestTag(Tag tag, T testValue) where M : IPlcMapper, new() - { - - Console.WriteLine($"\r\n*** {tag.Name} [{typeof(M)}] {typeof(T)} ***"); - - - tag.Value = testValue; - Console.WriteLine($"Write Value <{typeof(T)}> {testValue} to '{tag.Name}'"); - tag.Write(); - - Console.WriteLine($"Read Value from {tag.Name}"); - tag.Read(); - - T readback = tag.Value; - - CompareLogic compareLogic = new CompareLogic(); - ComparisonResult result = compareLogic.Compare(readback, testValue); - - if (result.AreEqual) Console.WriteLine($"PASS: Read back matched test value"); - else Console.WriteLine($"FAIL: Read back did not match test value - [{readback} != {testValue}]"); - - return result.AreEqual; - } - - private static Tag BuildTag(string name) where M : IPlcMapper, new() - { - var tag = new Tag() - { - Name = name, - Gateway = GATEWAY, - Path = PATH, - PlcType = PLC_TYPE, - Protocol = PROTOCOL, - Timeout = TimeSpan.FromMilliseconds(DEFAULT_TIMEOUT), - }; - return tag; - - } - - } -} diff --git a/examples/CSharp DotNetFramework/CSharp DotNetFramework.csproj b/examples/CSharp DotNetFramework/CSharp DotNetFramework.csproj index 1f259188..c7a6abcc 100644 --- a/examples/CSharp DotNetFramework/CSharp DotNetFramework.csproj +++ b/examples/CSharp DotNetFramework/CSharp DotNetFramework.csproj @@ -47,8 +47,6 @@ - - diff --git a/examples/CSharp DotNetFramework/Program.cs b/examples/CSharp DotNetFramework/Program.cs index 46555dca..5afdc828 100644 --- a/examples/CSharp DotNetFramework/Program.cs +++ b/examples/CSharp DotNetFramework/Program.cs @@ -5,6 +5,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using libplctag; using System; namespace CSharpDotNetFramework @@ -13,10 +14,27 @@ class Program { static void Main(string[] args) { - ExampleRW.Run(); - ExampleArray.Run(); - Console.ReadKey(); + // Example tag configuration for a global DINT tag in an Allen-Bradley CompactLogix/ControlLogix PLC + var myTag = new Tag() + { + Name = "SomeDINT", + Gateway = "10.10.10.10", + Path = "1,0", + PlcType = PlcType.ControlLogix, + Protocol = Protocol.ab_eip + }; + + // Read the value from the PLC and output to console + myTag.Read(); + int originalValue = myTag.GetInt32(0); + Console.WriteLine($"Original value: {originalValue}"); + + // Write a new value to the PLC, then read it back, and output to console + int updatedValue = 1234; + myTag.SetInt32(0, updatedValue); + myTag.Write(); + Console.WriteLine($"Updated value: {updatedValue}"); } } } \ No newline at end of file diff --git a/examples/LibplctagReadBenchmark/Program.cs b/examples/LibplctagReadBenchmark/Program.cs index cd642056..0d271bab 100644 --- a/examples/LibplctagReadBenchmark/Program.cs +++ b/examples/LibplctagReadBenchmark/Program.cs @@ -1,5 +1,9 @@ -// See https://aka.ms/new-console-template for more information - +// Copyright (c) libplctag.NET contributors +// https://github.com/libplctag/libplctag.NET +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. using OptimizedLibplctagReads; using System.Diagnostics; diff --git a/examples/LibplctagReadBenchmark/ReadExamples.cs b/examples/LibplctagReadBenchmark/ReadExamples.cs index c41f3c1c..9d923a81 100644 --- a/examples/LibplctagReadBenchmark/ReadExamples.cs +++ b/examples/LibplctagReadBenchmark/ReadExamples.cs @@ -1,11 +1,11 @@ -using libplctag.DataTypes.Simple; +using libplctag; using System.Diagnostics; namespace OptimizedLibplctagReads { internal static class ReadExamples { - internal static void ReadForEach(List allTags) + internal static void ReadForEach(List allTags) { var stopwatch = new Stopwatch(); @@ -18,7 +18,7 @@ internal static void ReadForEach(List allTags) Console.WriteLine($"Tag ReadForEach = {stopwatch.ElapsedMilliseconds} msec"); } - internal static async Task ReadWhenAllAsync(List allTags) + internal static async Task ReadWhenAllAsync(List allTags) { var stopwatch = new Stopwatch(); @@ -28,7 +28,7 @@ internal static async Task ReadWhenAllAsync(List allTags) Console.WriteLine($"Tag ReadWhenAllAsync = {stopwatch.ElapsedMilliseconds} msec"); } - internal static async Task ReadForEachAsync(List allTags) + internal static async Task ReadForEachAsync(List allTags) { var stopwatch = new Stopwatch(); @@ -41,7 +41,7 @@ internal static async Task ReadForEachAsync(List allTags) Console.WriteLine($"Tag ReadForEachAsync = {stopwatch.ElapsedMilliseconds} msec"); } - internal static void ReadParallelForEach(List allTags) + internal static void ReadParallelForEach(List allTags) { var stopwatch = new Stopwatch(); @@ -70,7 +70,7 @@ internal static void ReadParallelForEach(List allTags) // Console.WriteLine($"Tag ReadAsyncParallelForEach = {stopwatch.ElapsedMilliseconds} msec"); // } - internal static async Task ReadAsyncParallelForEachAsync(List allTags) + internal static async Task ReadAsyncParallelForEachAsync(List allTags) { var stopwatch = new Stopwatch(); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = allTags.Count(), }; diff --git a/examples/LibplctagReadBenchmark/TagInstantation.cs b/examples/LibplctagReadBenchmark/TagInstantation.cs index 24c6b690..9db98811 100644 --- a/examples/LibplctagReadBenchmark/TagInstantation.cs +++ b/examples/LibplctagReadBenchmark/TagInstantation.cs @@ -1,5 +1,4 @@ using libplctag; -using libplctag.DataTypes.Simple; using System; using System.Collections.Generic; using System.Diagnostics; @@ -11,17 +10,17 @@ namespace OptimizedLibplctagReads { internal static class TagInstantation { - internal static async Task> Instantiate(int length, TimeSpan timeout) + internal static async Task> Instantiate(int length, TimeSpan timeout) { var stopwatch = new Stopwatch(); stopwatch.Start(); - var allTags = new List(); + var allTags = new List(); for (int i = 0; i < length; i++) { - var myTag = new TagDint() + var myTag = new Tag() { //Name is the full path to tag. Name = $"TestDINT{i.ToString("0000")}", diff --git a/examples/VB.NET DotNetCore/Program.vb b/examples/VB.NET DotNetCore/Program.vb index 41b4271b..78152200 100644 --- a/examples/VB.NET DotNetCore/Program.vb +++ b/examples/VB.NET DotNetCore/Program.vb @@ -8,13 +8,12 @@ Imports System.Net Imports System.Threading Imports libplctag -Imports libplctag.DataTypes Module Module1 Sub Main() - Dim myTag = New Tag(Of DintPlcMapper, Integer)() With + Dim myTag = New Tag() With { .Name = "PROGRAM:SomeProgram.SomeDINT", .Gateway = "10.10.10.10", @@ -25,11 +24,11 @@ Module Module1 } myTag.Initialize() - myTag.Value = 3737 + myTag.SetInt32(0, 3737) myTag.Write() myTag.Read() - Dim myDint = myTag.Value + Dim myDint = myTag.GetInt32(0) Console.WriteLine(myDint) Console.ReadKey() diff --git a/src/libplctag.Tests/AsyncTests.cs b/src/libplctag.Tests/AsyncTests.cs index 92720a9a..343c12d3 100644 --- a/src/libplctag.Tests/AsyncTests.cs +++ b/src/libplctag.Tests/AsyncTests.cs @@ -24,7 +24,7 @@ public class AsyncTests public async Task Cancelled_cancellation_token_throws_a_TaskCanceledException() { // Arrange - var nativeTag = new Mock(); + var nativeTag = new Mock(); nativeTag // The initial creation of the tag object returns a status, so we return pending .Setup(m => m.plc_tag_create_ex(It.IsAny(), It.IsAny(), It.IsAny(), 0)) @@ -34,7 +34,7 @@ public async Task Cancelled_cancellation_token_throws_a_TaskCanceledException() .Setup(m => m.plc_tag_status(It.IsAny())) .Returns((int)Status.Pending); - var tag = new NativeTagWrapper(nativeTag.Object); + var tag = new Tag(nativeTag.Object); var cts = new CancellationTokenSource(); // Act, Assert @@ -49,7 +49,7 @@ await Assert.ThrowsAsync(async () => { public async Task Timeout_throws_a_LibPlcTagException() { // Arrange - var nativeTag = new Mock(); + var nativeTag = new Mock(); nativeTag // The initial creation of the tag object returns a status, so we return pending .Setup(m => m.plc_tag_create_ex(It.IsAny(), It.IsAny(), It.IsAny(), 0)) @@ -59,7 +59,7 @@ public async Task Timeout_throws_a_LibPlcTagException() .Setup(m => m.plc_tag_status(It.IsAny())) .Returns((int)Status.Pending); - var tag = new NativeTagWrapper(nativeTag.Object) + var tag = new Tag(nativeTag.Object) { Timeout = REALISTIC_TIMEOUT_FOR_ALL_OPERATIONS }; @@ -79,7 +79,7 @@ public async Task Timeout_returns_pending_but_eventually_ok() // Arrange var nativeTag = GetMock(); - var tag = new NativeTagWrapper(nativeTag.Object) + var tag = new Tag(nativeTag.Object) { Timeout = REALISTIC_TIMEOUT_FOR_ALL_OPERATIONS }; @@ -101,7 +101,7 @@ public async Task AsyncRead_completes_within_timeout_period() // Arrange var nativeTag = GetMock(); - var tag = new NativeTagWrapper(nativeTag.Object) + var tag = new Tag(nativeTag.Object) { Timeout = REALISTIC_TIMEOUT_FOR_ALL_OPERATIONS }; @@ -114,14 +114,14 @@ public async Task AsyncRead_completes_within_timeout_period() } - Mock GetMock() + Mock GetMock() { const int tagId = 11; NativeImport.plctag.callback_func_ex callback = null; Status? status = null; - var nativeTag = new Mock(); + var nativeTag = new Mock(); // The NativeTagWrapper should provide the native tag with a callback. // We will store this locally when a create call occurs, and fire it shortly after ... diff --git a/src/libplctag.Tests/DisposeTests.cs b/src/libplctag.Tests/DisposeTests.cs index e7b6f913..3c38b778 100644 --- a/src/libplctag.Tests/DisposeTests.cs +++ b/src/libplctag.Tests/DisposeTests.cs @@ -18,8 +18,8 @@ public class DisposeTests public void Destroy_is_called_if_initialized_and_disposed() { // Arrange - var nativeTag = new Mock(); - var tag = new NativeTagWrapper(nativeTag.Object); + var nativeTag = new Mock(); + var tag = new Tag(nativeTag.Object); // Act tag.Initialize(); @@ -33,8 +33,8 @@ public void Destroy_is_called_if_initialized_and_disposed() public void Can_not_use_if_already_disposed() { // Arrange - var nativeTag = new Mock(); - var tag = new NativeTagWrapper(nativeTag.Object); + var nativeTag = new Mock(); + var tag = new Tag(nativeTag.Object); // Act tag.Dispose(); @@ -51,11 +51,11 @@ public void Finalizer_calls_destroy() // Arrange - var nativeTag = new Mock(); + var nativeTag = new Mock(); Action dispose = () => { // This will go out of scope after dispose() is executed, so the garbage collector will be able to call the finalizer - var tag = new NativeTagWrapper(nativeTag.Object); + var tag = new Tag(nativeTag.Object); tag.Initialize(); }; diff --git a/src/libplctag.Tests/OtherTests.cs b/src/libplctag.Tests/OtherTests.cs index 8ddf15bf..d1b908c5 100644 --- a/src/libplctag.Tests/OtherTests.cs +++ b/src/libplctag.Tests/OtherTests.cs @@ -18,8 +18,8 @@ public class OtherTests public void Status_ok_when_first_created() { // Arrange - var nativeTag = new Mock(); - var tag = new NativeTagWrapper(nativeTag.Object); + var nativeTag = new Mock(); + var tag = new Tag(nativeTag.Object); // Act @@ -32,8 +32,8 @@ public void Status_ok_when_first_created() public void Attribute_string_formatted_correctly() { // Arrange - var nativeTag = new Mock(); - var tag = new NativeTagWrapper(nativeTag.Object) + var nativeTag = new Mock(); + var tag = new Tag(nativeTag.Object) { ElementSize = 4, ElementCount = 10, @@ -56,8 +56,8 @@ public void Attribute_string_formatted_correctly() public void Attribute_string_does_not_contain_unset_properties() { // Arrange - var nativeTag = new Mock(); - var tag = new NativeTagWrapper(nativeTag.Object); + var nativeTag = new Mock(); + var tag = new Tag(nativeTag.Object); // Act tag.Initialize(); diff --git a/src/libplctag/DataTypes/BoolPlcMapper.cs b/src/libplctag/DataTypes/BoolPlcMapper.cs index 44ee4703..e57ba0b1 100644 --- a/src/libplctag/DataTypes/BoolPlcMapper.cs +++ b/src/libplctag/DataTypes/BoolPlcMapper.cs @@ -12,6 +12,7 @@ namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class BoolPlcMapper : IPlcMapper, IPlcMapper, IPlcMapper, IPlcMapper { public int? ElementSize => 1; diff --git a/src/libplctag/DataTypes/DintPlcMapper.cs b/src/libplctag/DataTypes/DintPlcMapper.cs index 529e1ba2..48e7a7e0 100644 --- a/src/libplctag/DataTypes/DintPlcMapper.cs +++ b/src/libplctag/DataTypes/DintPlcMapper.cs @@ -5,8 +5,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System; + namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class DintPlcMapper : PlcMapperBase { public override int? ElementSize => 4; diff --git a/src/libplctag/DataTypes/Extensions/ArrayExtensions.cs b/src/libplctag/DataTypes/Extensions/ArrayExtensions.cs index 2e625c64..4bf8076d 100644 --- a/src/libplctag/DataTypes/Extensions/ArrayExtensions.cs +++ b/src/libplctag/DataTypes/Extensions/ArrayExtensions.cs @@ -11,6 +11,7 @@ namespace libplctag.DataTypes.Extensions { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public static class ArrayExtensions { /// diff --git a/src/libplctag/DataTypes/IPlcMapper.cs b/src/libplctag/DataTypes/IPlcMapper.cs index 5e3f1985..40e4e04f 100644 --- a/src/libplctag/DataTypes/IPlcMapper.cs +++ b/src/libplctag/DataTypes/IPlcMapper.cs @@ -5,8 +5,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System; + namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public interface IPlcMapper { /// diff --git a/src/libplctag/DataTypes/IntPlcMapper.cs b/src/libplctag/DataTypes/IntPlcMapper.cs index 092ff6d2..45256803 100644 --- a/src/libplctag/DataTypes/IntPlcMapper.cs +++ b/src/libplctag/DataTypes/IntPlcMapper.cs @@ -5,8 +5,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System; + namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class IntPlcMapper : PlcMapperBase { public override int? ElementSize => 2; diff --git a/src/libplctag/DataTypes/LintPlcMapper.cs b/src/libplctag/DataTypes/LintPlcMapper.cs index 45917143..49643ed2 100644 --- a/src/libplctag/DataTypes/LintPlcMapper.cs +++ b/src/libplctag/DataTypes/LintPlcMapper.cs @@ -5,8 +5,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System; + namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class LintPlcMapper : PlcMapperBase { public override int? ElementSize => 8; diff --git a/src/libplctag/DataTypes/LrealPlcMapper.cs b/src/libplctag/DataTypes/LrealPlcMapper.cs index d9cda213..cb970210 100644 --- a/src/libplctag/DataTypes/LrealPlcMapper.cs +++ b/src/libplctag/DataTypes/LrealPlcMapper.cs @@ -5,8 +5,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System; + namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class LrealPlcMapper : PlcMapperBase { diff --git a/src/libplctag/DataTypes/PlcMapperBase.cs b/src/libplctag/DataTypes/PlcMapperBase.cs index 70c695a7..ea210a8e 100644 --- a/src/libplctag/DataTypes/PlcMapperBase.cs +++ b/src/libplctag/DataTypes/PlcMapperBase.cs @@ -13,6 +13,7 @@ namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public abstract class PlcMapperBase : IPlcMapper, IPlcMapper, IPlcMapper, IPlcMapper { public PlcType PlcType { get; set; } diff --git a/src/libplctag/DataTypes/RealPlcMapper.cs b/src/libplctag/DataTypes/RealPlcMapper.cs index 575446c0..fe770019 100644 --- a/src/libplctag/DataTypes/RealPlcMapper.cs +++ b/src/libplctag/DataTypes/RealPlcMapper.cs @@ -5,8 +5,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System; + namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class RealPlcMapper : PlcMapperBase { diff --git a/src/libplctag/DataTypes/Simple/Definitions.cs b/src/libplctag/DataTypes/Simple/Definitions.cs index 35c027b1..87426efb 100644 --- a/src/libplctag/DataTypes/Simple/Definitions.cs +++ b/src/libplctag/DataTypes/Simple/Definitions.cs @@ -12,58 +12,93 @@ namespace libplctag.DataTypes.Simple { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagBool : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagBool1D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagBool2D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagBool3D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagDint : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagDint1D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagDint2D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagDint3D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagInt : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagInt1D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagInt2D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagInt3D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagLint : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagLint1D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagLint2D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagLint3D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagLreal : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagLreal1D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagLreal2D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagLreal3D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagReal : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagReal1D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagReal2D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagReal3D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagSint : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagSint1D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagSint2D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagSint3D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagString : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagString1D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagString2D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagString3D : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagTagInfo : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagTimer : Tag { } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagUdtInfo : Tag { } } diff --git a/src/libplctag/DataTypes/SintPlcMapper.cs b/src/libplctag/DataTypes/SintPlcMapper.cs index 14740fd1..ba0a99ef 100644 --- a/src/libplctag/DataTypes/SintPlcMapper.cs +++ b/src/libplctag/DataTypes/SintPlcMapper.cs @@ -5,8 +5,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +using System; + namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class SintPlcMapper : PlcMapperBase { diff --git a/src/libplctag/DataTypes/StringPlcMapper.cs b/src/libplctag/DataTypes/StringPlcMapper.cs index 486737fc..b5da622f 100644 --- a/src/libplctag/DataTypes/StringPlcMapper.cs +++ b/src/libplctag/DataTypes/StringPlcMapper.cs @@ -11,6 +11,7 @@ namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class StringPlcMapper : PlcMapperBase { diff --git a/src/libplctag/DataTypes/TagInfoPlcMapper.cs b/src/libplctag/DataTypes/TagInfoPlcMapper.cs index 3b190f5f..c26049b7 100644 --- a/src/libplctag/DataTypes/TagInfoPlcMapper.cs +++ b/src/libplctag/DataTypes/TagInfoPlcMapper.cs @@ -13,6 +13,7 @@ namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagInfo { public uint Id { get; set; } @@ -22,6 +23,7 @@ public class TagInfo public uint[] Dimensions { get; set; } } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TagInfoPlcMapper : IPlcMapper { diff --git a/src/libplctag/DataTypes/TimerPlcMapper.cs b/src/libplctag/DataTypes/TimerPlcMapper.cs index 20ee9d55..46547ea3 100644 --- a/src/libplctag/DataTypes/TimerPlcMapper.cs +++ b/src/libplctag/DataTypes/TimerPlcMapper.cs @@ -12,6 +12,7 @@ namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class TimerPlcMapper : PlcMapperBase { @@ -71,6 +72,7 @@ static int BitArrayToInt(BitArray binary) } } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class AbTimer { public int Preset { get; set; } diff --git a/src/libplctag/DataTypes/UdtInfoPlcMapper.cs b/src/libplctag/DataTypes/UdtInfoPlcMapper.cs index f8e5ee71..5b82ab96 100644 --- a/src/libplctag/DataTypes/UdtInfoPlcMapper.cs +++ b/src/libplctag/DataTypes/UdtInfoPlcMapper.cs @@ -13,6 +13,7 @@ namespace libplctag.DataTypes { + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class UdtFieldInfo { public string Name { get; set; } @@ -21,6 +22,7 @@ public class UdtFieldInfo public uint Offset { get; set; } } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class UdtInfo { public uint Size { get; set; } @@ -31,6 +33,7 @@ public class UdtInfo public UdtFieldInfo[] Fields { get; set; } } + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class UdtInfoPlcMapper : IPlcMapper { public PlcType PlcType { get; set; } diff --git a/src/libplctag/INativeTag.cs b/src/libplctag/INative.cs similarity index 99% rename from src/libplctag/INativeTag.cs rename to src/libplctag/INative.cs index c50ff706..cda47016 100644 --- a/src/libplctag/INativeTag.cs +++ b/src/libplctag/INative.cs @@ -16,7 +16,7 @@ namespace libplctag { - interface INativeTag + interface INative { int plc_tag_abort(int tag); int plc_tag_check_lib_version(int req_major, int req_minor, int req_patch); diff --git a/src/libplctag/ITag.cs b/src/libplctag/ITag.cs index 5da8bb50..48c14a25 100644 --- a/src/libplctag/ITag.cs +++ b/src/libplctag/ITag.cs @@ -15,6 +15,7 @@ namespace libplctag /// An interface to represent any generic tag without /// exposing its value /// + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public interface ITag : IDisposable { int[] ArrayDimensions { get; set; } diff --git a/src/libplctag/LibPlcTag.cs b/src/libplctag/LibPlcTag.cs index 16bc654f..afc24a7d 100644 --- a/src/libplctag/LibPlcTag.cs +++ b/src/libplctag/LibPlcTag.cs @@ -5,7 +5,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -using libplctag.NativeImport; using System; using static libplctag.NativeImport.plctag; @@ -17,7 +16,7 @@ namespace libplctag public static class LibPlcTag { - static INativeTag _native = new NativeTag(); + static INative _native = new Native(); private const int LIB_ATTRIBUTE_POINTER = 0; diff --git a/src/libplctag/LogEventArgs.cs b/src/libplctag/LogEventArgs.cs index 5cd29506..55565cd4 100644 --- a/src/libplctag/LogEventArgs.cs +++ b/src/libplctag/LogEventArgs.cs @@ -6,8 +6,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System; -using System.Collections.Generic; -using System.Text; namespace libplctag { diff --git a/src/libplctag/NativeTag.cs b/src/libplctag/Native.cs similarity index 99% rename from src/libplctag/NativeTag.cs rename to src/libplctag/Native.cs index 282823d7..0028ca7c 100644 --- a/src/libplctag/NativeTag.cs +++ b/src/libplctag/Native.cs @@ -5,16 +5,15 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -using libplctag.NativeImport; using System; using System.Text; +using libplctag.NativeImport; using static libplctag.NativeImport.plctag; namespace libplctag { - class NativeTag : INativeTag + class Native : INative { - public int plc_tag_check_lib_version(int req_major, int req_minor, int req_patch) => plctag.plc_tag_check_lib_version(req_major, req_minor, req_patch); public Int32 plc_tag_create(string lpString, int timeout) => plctag.plc_tag_create(lpString, timeout); public Int32 plc_tag_create_ex(string lpString, callback_func_ex func, IntPtr userdata, int timeout) => plctag.plc_tag_create_ex(lpString, func, userdata, timeout); diff --git a/src/libplctag/NativeTagWrapper.cs b/src/libplctag/NativeTagWrapper.cs deleted file mode 100644 index 0fe3a825..00000000 --- a/src/libplctag/NativeTagWrapper.cs +++ /dev/null @@ -1,928 +0,0 @@ -// Copyright (c) libplctag.NET contributors -// https://github.com/libplctag/libplctag.NET -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using static libplctag.NativeImport.plctag; - -[assembly: InternalsVisibleTo("libplctag.Tests")] - -namespace libplctag -{ - - class NativeTagWrapper : IDisposable - { - private const int TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION = 0; - private static readonly TimeSpan defaultTimeout = TimeSpan.FromSeconds(10); - private static readonly TimeSpan maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue); - - private int nativeTagHandle; - private callback_func_ex coreLibCallbackFuncExDelegate; - - private bool _isDisposed = false; - private bool _isInitialized = false; - - readonly INativeTag _native; - - public NativeTagWrapper(INativeTag nativeMethods) - { - _native = nativeMethods; - } - - ~NativeTagWrapper() - { - Dispose(); - } - - - // TODO remove. No longer used by Tag but would be a breaking change. - public bool IsInitialized => _isInitialized; - - - private string _name; - public string Name - { - get => GetField(ref _name); - set => SetField(ref _name, value); - } - - private Protocol? _protocol; - public Protocol? Protocol - { - get => GetField(ref _protocol); - set => SetField(ref _protocol, value); - } - - private string _gateway; - public string Gateway - { - get => GetField(ref _gateway); - set => SetField(ref _gateway, value); - } - - private PlcType? _plcType; - public PlcType? PlcType - { - get => GetField(ref _plcType); - set => SetField(ref _plcType, value); - } - - private string _path; - public string Path - { - get => GetField(ref _path); - set => SetField(ref _path, value); - } - - private int? _elementSize; - public int? ElementSize - { - get => GetField(ref _elementSize); - set => SetField(ref _elementSize, value); - } - - private int? _elementCount; - public int? ElementCount - { - get => GetField(ref _elementCount); - set => SetField(ref _elementCount, value); - } - - private bool? _useConnectedMessaging; - public bool? UseConnectedMessaging - { - get => GetField(ref _useConnectedMessaging); - set => SetField(ref _useConnectedMessaging, value); - } - - private bool? _allowPacking; - public bool? AllowPacking - { - get => GetField(ref _allowPacking); - set => SetField(ref _allowPacking, value); - } - - private int? _readCacheMillisecondDuration; - public int? ReadCacheMillisecondDuration - { - get - { - ThrowIfAlreadyDisposed(); - return _readCacheMillisecondDuration; - - } - set - { - ThrowIfAlreadyDisposed(); - - if (_isInitialized) - SetIntAttribute("read_cache_ms", value.Value); - - // Set after writing to underlying tag in case SetIntAttribute fails. - // Ensures the two have the same value. - _readCacheMillisecondDuration = value; - - } - } - - private TimeSpan _timeout = defaultTimeout; - public TimeSpan Timeout - { - get - { - ThrowIfAlreadyDisposed(); - return _timeout; - } - set - { - ThrowIfAlreadyDisposed(); - if (value <= TimeSpan.Zero || value > maxTimeout) - throw new ArgumentOutOfRangeException(nameof(Timeout), value, "Must be greater than 0"); - _timeout = value; - } - } - - private TimeSpan? _autoSyncReadInterval; - public TimeSpan? AutoSyncReadInterval - { - get - { - ThrowIfAlreadyDisposed(); - return _autoSyncReadInterval; - } - set - { - ThrowIfAlreadyDisposed(); - - if (_isInitialized) - { - if (value is null) - SetIntAttribute("auto_sync_read_ms", 0); // 0 is a special value that turns off auto sync - else - SetIntAttribute("auto_sync_read_ms", (int)value.Value.TotalMilliseconds); - } - - // Set after writing to underlying tag in case SetIntAttribute fails. - // Ensures the two have the same value. - _autoSyncReadInterval = value; - } - } - - private TimeSpan? _autoSyncWriteInterval; - public TimeSpan? AutoSyncWriteInterval - { - get - { - ThrowIfAlreadyDisposed(); - return _autoSyncWriteInterval; - } - set - { - ThrowIfAlreadyDisposed(); - - if (_isInitialized) - { - if (value is null) - SetIntAttribute("auto_sync_write_ms", 0); // 0 is a special value that turns off auto sync - else - SetIntAttribute("auto_sync_write_ms", (int)value.Value.TotalMilliseconds); - } - - // Set after writing to underlying tag in case SetIntAttribute fails. - // Ensures the two have the same value. - _autoSyncWriteInterval = value; - } - } - - private DebugLevel _debugLevel = DebugLevel.None; - public DebugLevel DebugLevel - { - get - { - ThrowIfAlreadyDisposed(); - return _debugLevel; - } - set - { - ThrowIfAlreadyDisposed(); - - if (_isInitialized) - SetDebugLevel(value); - - // Set after writing to underlying tag in case SetDebugLevel fails. - // Ensures the two have the same value. - _debugLevel = value; - } - } - - private string _int16ByteOrder; - public string Int16ByteOrder - { - get => GetField(ref _int16ByteOrder); - set => SetField(ref _int16ByteOrder, value); - } - - private string _int32ByteOrder; - public string Int32ByteOrder - { - get => GetField(ref _int32ByteOrder); - set => SetField(ref _int32ByteOrder, value); - } - - private string _int64ByteOrder; - public string Int64ByteOrder - { - get => GetField(ref _int64ByteOrder); - set => SetField(ref _int64ByteOrder, value); - } - - private string _float32ByteOrder; - public string Float32ByteOrder - { - get => GetField(ref _float32ByteOrder); - set => SetField(ref _float32ByteOrder, value); - } - - private string _float64ByteOrder; - public string Float64ByteOrder - { - get => GetField(ref _float64ByteOrder); - set => SetField(ref _float64ByteOrder, value); - } - - - private uint? _stringCountWordBytes; - public uint? StringCountWordBytes - { - get => GetField(ref _stringCountWordBytes); - set => SetField(ref _stringCountWordBytes, value); - } - - private bool? _stringIsByteSwapped; - public bool? StringIsByteSwapped - { - get => GetField(ref _stringIsByteSwapped); - set => SetField(ref _stringIsByteSwapped, value); - } - - private bool? _stringIsCounted; - public bool? StringIsCounted - { - get => GetField(ref _stringIsCounted); - set => SetField(ref _stringIsCounted, value); - } - - private bool? _stringIsFixedLength; - public bool? StringIsFixedLength - { - get => GetField(ref _stringIsFixedLength); - set => SetField(ref _stringIsFixedLength, value); - } - - private bool? _stringIsZeroTerminated; - public bool? StringIsZeroTerminated - { - get => GetField(ref _stringIsZeroTerminated); - set => SetField(ref _stringIsZeroTerminated, value); - } - - private uint? _stringMaxCapacity; - public uint? StringMaxCapacity - { - get => GetField(ref _stringMaxCapacity); - set => SetField(ref _stringMaxCapacity, value); - } - - private uint? _stringPadBytes; - public uint? StringPadBytes - { - get => GetField(ref _stringPadBytes); - set => SetField(ref _stringPadBytes, value); - } - - private uint? _stringTotalLength; - public uint? StringTotalLength - { - get => GetField(ref _stringTotalLength); - set => SetField(ref _stringTotalLength, value); - } - - - private uint? _maxRequetsInFlight; - public uint? MaxRequestsInFlight - { - get => GetField (ref _maxRequetsInFlight); - set => SetField(ref _maxRequetsInFlight, value); - } - - - - public void Dispose() - { - if (_isDisposed) - return; - - if (_isInitialized) - { - RemoveEventsAndRemoveCallback(); - var result = (Status)_native.plc_tag_destroy(nativeTagHandle); - ThrowIfStatusNotOk(result); - } - - _isDisposed = true; - } - - public void Abort() - { - ThrowIfAlreadyDisposed(); - var result = (Status)_native.plc_tag_abort(nativeTagHandle); - ThrowIfStatusNotOk(result); - } - - - - public void Initialize() - { - - ThrowIfAlreadyDisposed(); - ThrowIfAlreadyInitialized(); - - var millisecondTimeout = (int)Timeout.TotalMilliseconds; - - SetUpEvents(); - - var attributeString = GetAttributeString(); - - var result = _native.plc_tag_create_ex(attributeString, coreLibCallbackFuncExDelegate, IntPtr.Zero, millisecondTimeout); - if (result < 0) - throw new LibPlcTagException((Status)result); - else - nativeTagHandle = result; - - - _isInitialized = true; - } - - public async Task InitializeAsync(CancellationToken token = default) - { - - ThrowIfAlreadyDisposed(); - ThrowIfAlreadyInitialized(); - - using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) - { - cts.CancelAfter(Timeout); - - using (cts.Token.Register(() => - { - if (createTasks.TryPop(out var createTask)) - { - Abort(); - RemoveEventsAndRemoveCallback(); - - if (token.IsCancellationRequested) - createTask.SetCanceled(); - else - createTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); - } - })) - { - SetUpEvents(); - - var createTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - createTasks.Push(createTask); - - var attributeString = GetAttributeString(); - - var result = _native.plc_tag_create_ex(attributeString, coreLibCallbackFuncExDelegate, IntPtr.Zero, TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION); - if (result < 0) - throw new LibPlcTagException((Status)result); - else - nativeTagHandle = result; - - if(GetStatus() == Status.Pending) - await createTask.Task.ConfigureAwait(false); - - ThrowIfStatusNotOk(createTask.Task.Result); - - _isInitialized = true; - } - } - } - - public void Read() - { - ThrowIfAlreadyDisposed(); - InitializeIfRequired(); - - var millisecondTimeout = (int)Timeout.TotalMilliseconds; - - var result = (Status)_native.plc_tag_read(nativeTagHandle, millisecondTimeout); - ThrowIfStatusNotOk(result); - } - - public async Task ReadAsync(CancellationToken token = default) - { - ThrowIfAlreadyDisposed(); - await InitializeAsyncIfRequired(token).ConfigureAwait(false); - - using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) - { - cts.CancelAfter(Timeout); - - using (cts.Token.Register(() => - { - if (readTasks.TryPop(out var readTask)) - { - Abort(); - - if (token.IsCancellationRequested) - readTask.SetCanceled(); - else - readTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); - } - })) - { - var readTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - readTasks.Push(readTask); - _native.plc_tag_read(nativeTagHandle, TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION); - await readTask.Task.ConfigureAwait(false); - ThrowIfStatusNotOk(readTask.Task.Result); - } - } - } - - public void Write() - { - ThrowIfAlreadyDisposed(); - InitializeIfRequired(); - - var millisecondTimeout = (int)Timeout.TotalMilliseconds; - - var result = (Status)_native.plc_tag_write(nativeTagHandle, millisecondTimeout); - ThrowIfStatusNotOk(result); - } - - public async Task WriteAsync(CancellationToken token = default) - { - ThrowIfAlreadyDisposed(); - await InitializeAsyncIfRequired(token).ConfigureAwait(false); - - using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) - { - cts.CancelAfter(Timeout); - - using (cts.Token.Register(() => - { - if (writeTasks.TryPop(out var writeTask)) - { - Abort(); - - if (token.IsCancellationRequested) - writeTask.SetCanceled(); - else - writeTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); - } - })) - { - var writeTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - writeTasks.Push(writeTask); - _native.plc_tag_write(nativeTagHandle, TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION); - await writeTask.Task.ConfigureAwait(false); - ThrowIfStatusNotOk(writeTask.Task.Result); - } - } - } - - public int GetSize() - { - ThrowIfAlreadyDisposed(); - - var result = _native.plc_tag_get_size(nativeTagHandle); - if (result < 0) - throw new LibPlcTagException((Status)result); - else - return result; - } - - public int SetSize(int newSize) - { - ThrowIfAlreadyDisposed(); - var result = _native.plc_tag_set_size(nativeTagHandle, newSize); - if (result < 0) - throw new LibPlcTagException((Status)result); - else - return result; - } - - public Status GetStatus() - { - ThrowIfAlreadyDisposed(); - return (Status)_native.plc_tag_status(nativeTagHandle); - } - - public void GetBuffer(byte[] buffer) - { - GetBuffer(0, buffer, buffer.Length); - } - - public void GetBuffer(int offset, byte[] buffer, int length) - { - ThrowIfAlreadyDisposed(); - var result = (Status)_native.plc_tag_get_raw_bytes(nativeTagHandle, offset, buffer, length); - ThrowIfStatusNotOk(result); - } - - public byte[] GetBuffer() - { - ThrowIfAlreadyDisposed(); - - var tagSize = GetSize(); - var temp = new byte[tagSize]; - - var result = (Status)_native.plc_tag_get_raw_bytes(nativeTagHandle, 0, temp, temp.Length); - ThrowIfStatusNotOk(result); - - return temp; - } - - public void SetBuffer(byte[] buffer) - { - SetBuffer(0, buffer, buffer.Length); - } - - public void SetBuffer(int start_offset, byte[] buffer, int length) - { - ThrowIfAlreadyDisposed(); - - GetNativeValueAndThrowOnNegativeResult(_native.plc_tag_set_size, buffer.Length); - var result = (Status)_native.plc_tag_set_raw_bytes(nativeTagHandle, start_offset, buffer, length); - ThrowIfStatusNotOk(result); - } - - - - private int GetIntAttribute(string attributeName) - { - var result = _native.plc_tag_get_int_attribute(nativeTagHandle, attributeName, int.MinValue); - if (result == int.MinValue) - ThrowIfStatusNotOk(); - - return result; - } - - private void SetIntAttribute(string attributeName, int value) - { - var result = (Status)_native.plc_tag_set_int_attribute(nativeTagHandle, attributeName, value); - ThrowIfStatusNotOk(result); - } - - public byte[] GetByteArrayAttribute(string attributeName) - { - ThrowIfAlreadyDisposed(); - - var bufferLengthAttributeName = attributeName + ".length"; - var bufferLength = GetIntAttribute(bufferLengthAttributeName); - var buffer = new byte[bufferLength]; - - var result = (Status)_native.plc_tag_get_byte_array_attribute(nativeTagHandle, attributeName, buffer, buffer.Length); - ThrowIfStatusNotOk(result); - - return buffer; - } - - private void SetDebugLevel(DebugLevel level) - { - _native.plc_tag_set_debug_level((int)level); - } - - public bool GetBit(int offset) - { - ThrowIfAlreadyDisposed(); - - var result = _native.plc_tag_get_bit(nativeTagHandle, offset); - if (result == 0) - return false; - else if (result == 1) - return true; - else - throw new LibPlcTagException((Status)result); - } - - public void SetBit(int offset, bool value) => SetNativeTagValue(_native.plc_tag_set_bit, offset, value == true ? 1 : 0); - - public ulong GetUInt64(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_uint64, offset, ulong.MaxValue); - public void SetUInt64(int offset, ulong value) => SetNativeTagValue(_native.plc_tag_set_uint64, offset, value); - - public long GetInt64(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_int64, offset, long.MinValue); - public void SetInt64(int offset, long value) => SetNativeTagValue(_native.plc_tag_set_int64, offset, value); - - public uint GetUInt32(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_uint32, offset, uint.MaxValue); - public void SetUInt32(int offset, uint value) => SetNativeTagValue(_native.plc_tag_set_uint32, offset, value); - - public int GetInt32(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_int32, offset, int.MinValue); - public void SetInt32(int offset, int value) => SetNativeTagValue(_native.plc_tag_set_int32, offset, value); - - public ushort GetUInt16(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_uint16, offset, ushort.MaxValue); - public void SetUInt16(int offset, ushort value) => SetNativeTagValue(_native.plc_tag_set_uint16, offset, value); - - public short GetInt16(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_int16, offset, short.MinValue); - public void SetInt16(int offset, short value) => SetNativeTagValue(_native.plc_tag_set_int16, offset, value); - - public byte GetUInt8(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_uint8, offset, byte.MaxValue); - public void SetUInt8(int offset, byte value) => SetNativeTagValue(_native.plc_tag_set_uint8, offset, value); - - public sbyte GetInt8(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_int8, offset, sbyte.MinValue); - public void SetInt8(int offset, sbyte value) => SetNativeTagValue(_native.plc_tag_set_int8, offset, value); - - public double GetFloat64(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_float64, offset, double.MinValue); - public void SetFloat64(int offset, double value) => SetNativeTagValue(_native.plc_tag_set_float64, offset, value); - - public float GetFloat32(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_float32, offset, float.MinValue); - public void SetFloat32(int offset, float value) => SetNativeTagValue(_native.plc_tag_set_float32, offset, value); - - - - public void SetString(int offset, string value) => SetNativeTagValue(_native.plc_tag_set_string, offset, value); - public int GetStringLength(int offset) => GetNativeValueAndThrowOnNegativeResult(_native.plc_tag_get_string_length, offset); - public int GetStringCapacity(int offset) => GetNativeValueAndThrowOnNegativeResult(_native.plc_tag_get_string_capacity, offset); - public int GetStringTotalLength(int offset) => GetNativeValueAndThrowOnNegativeResult(_native.plc_tag_get_string_total_length, offset); - public string GetString(int offset) - { - ThrowIfAlreadyDisposed(); - var stringLength = GetStringLength(offset); - var sb = new StringBuilder(stringLength); - var status = (Status)_native.plc_tag_get_string(nativeTagHandle, offset, sb, stringLength); - ThrowIfStatusNotOk(status); - return sb.ToString().Substring(0, stringLength < sb.Length ? stringLength : sb.Length); - } - - - private void ThrowIfAlreadyDisposed() - { - if (_isDisposed) - throw new ObjectDisposedException(GetType().FullName); - } - - private void InitializeIfRequired() - { - if (!_isInitialized) - Initialize(); - } - - private Task InitializeAsyncIfRequired(CancellationToken token) - { - if (!_isInitialized) - return InitializeAsync(token); - else - return Task.CompletedTask; - } - - private void ThrowIfAlreadyInitialized() - { - if (_isInitialized) - throw new InvalidOperationException("Already initialized"); - } - - private void ThrowIfStatusNotOk(Status? status = null) - { - var statusToCheck = status ?? GetStatus(); - if (statusToCheck != Status.Ok) - throw new LibPlcTagException(statusToCheck); - } - - - - private void SetNativeTagValue(Func nativeMethod, int offset, T value) - { - ThrowIfAlreadyDisposed(); - var result = (Status)nativeMethod(nativeTagHandle, offset, value); - ThrowIfStatusNotOk(result); - } - - private int GetNativeValueAndThrowOnNegativeResult(Func nativeMethod, int offset) - { - ThrowIfAlreadyDisposed(); - var result = nativeMethod(nativeTagHandle, offset); - if (result < 0) - throw new LibPlcTagException((Status)result); - return result; - } - - private T GetNativeValueAndThrowOnSpecificResult(Func nativeMethod, int offset, T valueIndicatingPossibleError) - where T : struct - { - ThrowIfAlreadyDisposed(); - var result = nativeMethod(nativeTagHandle, offset); - if (result.Equals(valueIndicatingPossibleError)) - ThrowIfStatusNotOk(); - return result; - } - - private T GetField(ref T field) - { - ThrowIfAlreadyDisposed(); - return field; - } - - private void SetField(ref T field, T value) - { - ThrowIfAlreadyDisposed(); - ThrowIfAlreadyInitialized(); - field = value; - } - - private string GetAttributeString() - { - - string FormatNullableBoolean(bool? value) - => value.HasValue ? (value.Value ? "1" : "0") : null; - - string FormatPlcType(PlcType? type) - { - if (type == libplctag.PlcType.Omron) - return "omron-njnx"; - else - return type?.ToString().ToLowerInvariant(); - } - - string FormatTimeSpan(TimeSpan? timespan) - { - if(timespan.HasValue) - return ((int)timespan.Value.TotalMilliseconds).ToString(); - else - return null; - } - - var attributes = new Dictionary - { - { "protocol", Protocol?.ToString() }, - { "gateway", Gateway }, - { "path", Path }, - { "plc", FormatPlcType(PlcType) }, - { "elem_size", ElementSize?.ToString() }, - { "elem_count", ElementCount?.ToString() }, - { "name", Name }, - { "read_cache_ms", ReadCacheMillisecondDuration?.ToString() }, - { "use_connected_msg", FormatNullableBoolean(UseConnectedMessaging) }, - { "allow_packing", FormatNullableBoolean(AllowPacking) }, - { "auto_sync_read_ms", FormatTimeSpan(AutoSyncReadInterval) }, - { "auto_sync_write_ms", FormatTimeSpan(AutoSyncWriteInterval) }, - { "debug", DebugLevel == DebugLevel.None ? null : ((int)DebugLevel).ToString() }, - { "int16_byte_order", Int16ByteOrder }, - { "int32_byte_order", Int32ByteOrder }, - { "int64_byte_order", Int64ByteOrder }, - { "float32_byte_order", Float32ByteOrder }, - { "float64_byte_order", Float64ByteOrder }, - { "str_count_word_bytes", StringCountWordBytes?.ToString() }, - { "str_is_byte_swapped", FormatNullableBoolean(StringIsByteSwapped) }, - { "str_is_counted", FormatNullableBoolean(StringIsCounted) }, - { "str_is_fixed_length", FormatNullableBoolean(StringIsFixedLength) }, - { "str_is_zero_terminated", FormatNullableBoolean(StringIsFixedLength) }, - { "str_max_capacity", StringMaxCapacity?.ToString() }, - { "str_pad_bytes", StringPadBytes?.ToString() }, - { "str_total_length", StringTotalLength?.ToString() }, - { "max_requests_in_flight", MaxRequestsInFlight?.ToString() }, - }; - - string separator = "&"; - return string.Join(separator, attributes.Where(attr => attr.Value != null).Select(attr => $"{attr.Key}={attr.Value}")); - - } - - - - - void SetUpEvents() - { - - // Used to finalize the asynchronous read/write task completion sources - ReadCompleted += ReadTaskCompleter; - WriteCompleted += WriteTaskCompleter; - Created += CreatedTaskCompleter; - - // Need to keep a reference to the delegate in memory so it doesn't get garbage collected - coreLibCallbackFuncExDelegate = new callback_func_ex(coreLibEventCallback); - - } - - void RemoveEventsAndRemoveCallback() - { - - // Used to finalize the read/write task completion sources - ReadCompleted -= ReadTaskCompleter; - WriteCompleted -= WriteTaskCompleter; - Created -= CreatedTaskCompleter; - - var callbackRemovalResult = (Status)_native.plc_tag_unregister_callback(nativeTagHandle); - ThrowIfStatusNotOk(callbackRemovalResult); - - } - - private readonly ConcurrentStack> createTasks = new ConcurrentStack>(); - void CreatedTaskCompleter(object sender, TagEventArgs e) - { - if (createTasks.TryPop(out var createTask)) - { - switch (e.Status) - { - case Status.Pending: - // Do nothing, wait for another ReadCompleted callback - break; - default: - createTask?.SetResult(e.Status); - break; - } - } - } - - private readonly ConcurrentStack> readTasks = new ConcurrentStack>(); - void ReadTaskCompleter(object sender, TagEventArgs e) - { - if (readTasks.TryPop(out var readTask)) - { - switch (e.Status) - { - case Status.Pending: - // Do nothing, wait for another ReadCompleted callback - break; - default: - readTask?.SetResult(e.Status); - break; - } - } - } - - private readonly ConcurrentStack> writeTasks = new ConcurrentStack>(); - void WriteTaskCompleter(object sender, TagEventArgs e) - { - if (writeTasks.TryPop(out var writeTask)) - { - switch (e.Status) - { - case Status.Pending: - // Do nothing, wait for another WriteCompleted callback - break; - default: - writeTask?.SetResult(e.Status); - break; - - } - } - } - - public event EventHandler ReadStarted; - public event EventHandler ReadCompleted; - public event EventHandler WriteStarted; - public event EventHandler WriteCompleted; - public event EventHandler Aborted; - public event EventHandler Destroyed; - public event EventHandler Created; - - void coreLibEventCallback(int eventTagHandle, int eventCode, int statusCode, IntPtr userdata) - { - var @event = (Event)eventCode; - var status = (Status)statusCode; - var eventArgs = new TagEventArgs() { Status = status }; - - switch (@event) - { - case Event.ReadCompleted: - ReadCompleted?.Invoke(this, eventArgs); - break; - case Event.ReadStarted: - ReadStarted?.Invoke(this, eventArgs); - break; - case Event.WriteStarted: - WriteStarted?.Invoke(this, eventArgs); - break; - case Event.WriteCompleted: - WriteCompleted?.Invoke(this, eventArgs); - break; - case Event.Aborted: - Aborted?.Invoke(this, eventArgs); - break; - case Event.Destroyed: - Destroyed?.Invoke(this, eventArgs); - break; - case Event.Created: - Created?.Invoke(this, eventArgs); - break; - default: - throw new NotImplementedException(); - } - } - - } - -} diff --git a/src/libplctag/Tag.cs b/src/libplctag/Tag.cs index 088bf89d..c645cd42 100644 --- a/src/libplctag/Tag.cs +++ b/src/libplctag/Tag.cs @@ -6,92 +6,81 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; using System.Threading; using System.Threading.Tasks; +using static libplctag.NativeImport.plctag; + +[assembly: InternalsVisibleTo("libplctag.Tests")] namespace libplctag { public sealed class Tag : IDisposable { - - readonly NativeTagWrapper _tag; + private const int TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION = 0; + private static readonly TimeSpan defaultTimeout = TimeSpan.FromSeconds(10); + private static readonly TimeSpan maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue); + + private readonly INative _native; + + private int nativeTagHandle; + private callback_func_ex coreLibCallbackFuncExDelegate; + + private bool _isDisposed = false; + private bool _isInitialized = false; + + private string _name; + private Protocol? _protocol; + private string _gateway; + private PlcType? _plcType; + private string _path; + private int? _elementSize; + private int? _elementCount; + private bool? _useConnectedMessaging; + private bool? _allowPacking; + private int? _readCacheMillisecondDuration; + private uint? _maxRequestsInFlight; + private TimeSpan _timeout = defaultTimeout; + private TimeSpan? _autoSyncReadInterval; + private TimeSpan? _autoSyncWriteInterval; + private DebugLevel _debugLevel = DebugLevel.None; + private string _int16ByteOrder; + private string _int32ByteOrder; + private string _int64ByteOrder; + private string _float32ByteOrder; + private string _float64ByteOrder; + private uint? _stringCountWordBytes; + private bool? _stringIsByteSwapped; + private bool? _stringIsCounted; + private bool? _stringIsFixedLength; + private bool? _stringIsZeroTerminated; + private uint? _stringPadBytes; + private uint? _stringTotalLength; public Tag() { - _tag = new NativeTagWrapper(new NativeTag()); - - _tag.ReadStarted += (s, e) => ReadStarted?.Invoke(this, e); - _tag.ReadCompleted += (s, e) => ReadCompleted?.Invoke(this, e); - _tag.WriteStarted += (s, e) => WriteStarted?.Invoke(this, e); - _tag.WriteCompleted += (s, e) => WriteCompleted?.Invoke(this, e); - _tag.Aborted += (s, e) => Aborted?.Invoke(this, e); - _tag.Destroyed += (s, e) => Destroyed?.Invoke(this, e); + _native = new Native(); } - - /// - /// True if or has been called. - /// - public bool IsInitialized => _tag.IsInitialized; - - - /// - /// [OPTIONAL] - /// An integer number of elements per tag. - /// - /// - /// - /// All tags are treated as arrays. - /// Tags that are not arrays are considered to have a length of one element. - /// This attribute determines how many elements are in the tag. - /// Defaults to one (1) if not found. - /// - public int? ElementCount + internal Tag(INative nativeMethods) { - get => _tag.ElementCount; - set => _tag.ElementCount = value; + _native = nativeMethods; } - - /// - /// [REQUIRED/OPTIONAL] - /// An integer number of bytes per element - /// - /// - /// - /// This attribute determines the size of a single element of the tag. - /// Ignored for Modbus and for Allen-Bradley PLCs. - /// - public int? ElementSize + ~Tag() { - get => _tag.ElementSize; - set => _tag.ElementSize = value; + Dispose(); } - /// - /// [REQUIRED] - /// IP address or host name + /// True if or has been called. /// - /// - /// - /// - /// This tells the library what host name or IP address to use for the PLC or the gateway to the PLC (in the case that the PLC is remote). - /// - /// - /// [AB-SPECIFIC] - /// Only for PLCs with additional routing. - /// This attribute is required for CompactLogix/ControlLogix tags and for tags using a DH+ protocol bridge (i.e. a DHRIO module) to get to a PLC/5, SLC 500, or MicroLogix PLC on a remote DH+ link. The attribute is ignored if it is not a DH+ bridge route, but will generate a warning if debugging is active. - /// Note that Micro800 connections must not have a path attribute. - /// - /// - public string Gateway - { - get => _tag.Gateway; - set => _tag.Gateway = value; - } - + public bool IsInitialized => _isInitialized; /// /// @@ -123,10 +112,51 @@ public string Gateway /// public string Name { - get => _tag.Name; - set => _tag.Name = value; + get => GetField(ref _name); + set => SetField(ref _name, value); } + /// + /// [REQUIRED] + /// Determines the type of the PLC Protocol. + /// + public Protocol? Protocol + { + get => GetField(ref _protocol); + set => SetField(ref _protocol, value); + } + + /// + /// [REQUIRED] + /// IP address or host name + /// + /// + /// + /// + /// This tells the library what host name or IP address to use for the PLC or the gateway to the PLC (in the case that the PLC is remote). + /// + /// + /// [AB-SPECIFIC] + /// Only for PLCs with additional routing. + /// This attribute is required for CompactLogix/ControlLogix tags and for tags using a DH+ protocol bridge (i.e. a DHRIO module) to get to a PLC/5, SLC 500, or MicroLogix PLC on a remote DH+ link. The attribute is ignored if it is not a DH+ bridge route, but will generate a warning if debugging is active. + /// Note that Micro800 connections must not have a path attribute. + /// + /// + public string Gateway + { + get => GetField(ref _gateway); + set => SetField(ref _gateway, value); + } + + /// + /// [REQUIRED | AB-SPECIFIC] + /// Determines the type of the PLC. + /// + public PlcType? PlcType + { + get => GetField(ref _plcType); + set => SetField(ref _plcType, value); + } /// /// @@ -155,44 +185,41 @@ public string Name /// public string Path { - get => _tag.Path; - set => _tag.Path = value; + get => GetField(ref _path); + set => SetField(ref _path, value); } - + /// - /// [REQUIRED | AB-SPECIFIC] - /// Determines the type of the PLC. + /// [OPTIONAL] + /// An integer number of elements per tag. /// - public PlcType? PlcType + /// + /// + /// All tags are treated as arrays. + /// Tags that are not arrays are considered to have a length of one element. + /// This attribute determines how many elements are in the tag. + /// Defaults to one (1) if not found. + /// + public int? ElementCount { - get => _tag.PlcType; - set => _tag.PlcType = value; + get => GetField(ref _elementCount); + set => SetField(ref _elementCount, value); } /// - /// [REQUIRED] - /// Determines the type of the PLC Protocol. - /// - public Protocol? Protocol - { - get => _tag.Protocol; - set => _tag.Protocol = value; - } - - /// - /// [OPTIONAL] - /// Use this attribute to cause the tag read operations to cache data the requested number of milliseconds. + /// [REQUIRED/OPTIONAL] + /// An integer number of bytes per element /// /// /// - /// This can be used to lower the actual number of requests against the PLC. - /// Example read_cache_ms=100 will result in read operations no more often than once every 100 milliseconds. + /// This attribute determines the size of a single element of the tag. + /// Ignored for Modbus and for Allen-Bradley PLCs. /// - public int? ReadCacheMillisecondDuration + public int? ElementSize { - get => _tag.ReadCacheMillisecondDuration; - set => _tag.ReadCacheMillisecondDuration = value; + get => GetField(ref _elementSize); + set => SetField(ref _elementSize, value); } /// @@ -201,8 +228,8 @@ public int? ReadCacheMillisecondDuration /// public TimeSpan Timeout { - get => _tag.Timeout; - set => _tag.Timeout = value; + get => GetField(ref _timeout); + set => SetField(ref _timeout, value); } /// @@ -219,8 +246,8 @@ public TimeSpan Timeout /// public bool? UseConnectedMessaging { - get => _tag.UseConnectedMessaging; - set => _tag.UseConnectedMessaging = value; + get => GetField(ref _useConnectedMessaging); + set => SetField(ref _useConnectedMessaging, value); } /// @@ -235,8 +262,39 @@ public bool? UseConnectedMessaging /// public bool? AllowPacking { - get => _tag.AllowPacking; - set => _tag.AllowPacking = value; + get => GetField(ref _allowPacking); + set => SetField(ref _allowPacking, value); + } + + /// + /// [OPTIONAL] + /// Use this attribute to cause the tag read operations to cache data the requested number of milliseconds. + /// + /// + /// + /// This can be used to lower the actual number of requests against the PLC. + /// Example read_cache_ms=100 will result in read operations no more often than once every 100 milliseconds. + /// + public int? ReadCacheMillisecondDuration + { + get + { + ThrowIfAlreadyDisposed(); + return _readCacheMillisecondDuration; + + } + set + { + ThrowIfAlreadyDisposed(); + + if (_isInitialized) + SetIntAttribute("read_cache_ms", value.Value); + + // Set after writing to underlying tag in case SetIntAttribute fails. + // Ensures the two have the same value. + _readCacheMillisecondDuration = value; + + } } /// @@ -250,8 +308,27 @@ public bool? AllowPacking /// public TimeSpan? AutoSyncReadInterval { - get => _tag.AutoSyncReadInterval; - set => _tag.AutoSyncReadInterval = value; + get + { + ThrowIfAlreadyDisposed(); + return _autoSyncReadInterval; + } + set + { + ThrowIfAlreadyDisposed(); + + if (_isInitialized) + { + if (value is null) + SetIntAttribute("auto_sync_read_ms", 0); // 0 is a special value that turns off auto sync + else + SetIntAttribute("auto_sync_read_ms", (int)value.Value.TotalMilliseconds); + } + + // Set after writing to underlying tag in case SetIntAttribute fails. + // Ensures the two have the same value. + _autoSyncReadInterval = value; + } } /// @@ -266,14 +343,47 @@ public TimeSpan? AutoSyncReadInterval /// public TimeSpan? AutoSyncWriteInterval { - get => _tag.AutoSyncWriteInterval; - set => _tag.AutoSyncWriteInterval = value; + get + { + ThrowIfAlreadyDisposed(); + return _autoSyncWriteInterval; + } + set + { + ThrowIfAlreadyDisposed(); + + if (_isInitialized) + { + if (value is null) + SetIntAttribute("auto_sync_write_ms", 0); // 0 is a special value that turns off auto sync + else + SetIntAttribute("auto_sync_write_ms", (int)value.Value.TotalMilliseconds); + } + + // Set after writing to underlying tag in case SetIntAttribute fails. + // Ensures the two have the same value. + _autoSyncWriteInterval = value; + } } public DebugLevel DebugLevel { - get => _tag.DebugLevel; - set => _tag.DebugLevel = value; + get + { + ThrowIfAlreadyDisposed(); + return _debugLevel; + } + set + { + ThrowIfAlreadyDisposed(); + + if (_isInitialized) + SetDebugLevel(value); + + // Set after writing to underlying tag in case SetDebugLevel fails. + // Ensures the two have the same value. + _debugLevel = value; + } } /// @@ -287,8 +397,8 @@ public DebugLevel DebugLevel /// public string Int16ByteOrder { - get => _tag.Int16ByteOrder; - set => _tag.Int16ByteOrder = value; + get => GetField(ref _int16ByteOrder); + set => SetField(ref _int16ByteOrder, value); } /// @@ -301,8 +411,8 @@ public string Int16ByteOrder /// public string Int32ByteOrder { - get => _tag.Int32ByteOrder; - set => _tag.Int32ByteOrder = value; + get => GetField(ref _int32ByteOrder); + set => SetField(ref _int32ByteOrder, value); } /// @@ -315,8 +425,8 @@ public string Int32ByteOrder /// public string Int64ByteOrder { - get => _tag.Int64ByteOrder; - set => _tag.Int64ByteOrder = value; + get => GetField(ref _int64ByteOrder); + set => SetField(ref _int64ByteOrder, value); } /// @@ -329,8 +439,8 @@ public string Int64ByteOrder /// public string Float32ByteOrder { - get => _tag.Float32ByteOrder; - set => _tag.Float32ByteOrder = value; + get => GetField(ref _float32ByteOrder); + set => SetField(ref _float32ByteOrder, value); } /// @@ -343,10 +453,11 @@ public string Float32ByteOrder /// public string Float64ByteOrder { - get => _tag.Float64ByteOrder; - set => _tag.Float64ByteOrder = value; + get => GetField(ref _float64ByteOrder); + set => SetField(ref _float64ByteOrder, value); } + /// /// [OPTIONAL] /// A positive integer value of 1, 2, 4, or 8 determining how big the leading count word is in a string. @@ -360,8 +471,8 @@ public string Float64ByteOrder /// public uint? StringCountWordBytes { - get => _tag.StringCountWordBytes; - set => _tag.StringCountWordBytes = value; + get => GetField(ref _stringCountWordBytes); + set => SetField(ref _stringCountWordBytes, value); } /// @@ -375,8 +486,8 @@ public uint? StringCountWordBytes /// public bool? StringIsByteSwapped { - get => _tag.StringIsByteSwapped; - set => _tag.StringIsByteSwapped = value; + get => GetField(ref _stringIsByteSwapped); + set => SetField(ref _stringIsByteSwapped, value); } /// @@ -391,8 +502,8 @@ public bool? StringIsByteSwapped /// public bool? StringIsCounted { - get => _tag.StringIsCounted; - set => _tag.StringIsCounted = value; + get => GetField(ref _stringIsCounted); + set => SetField(ref _stringIsCounted, value); } /// @@ -407,8 +518,8 @@ public bool? StringIsCounted /// public bool? StringIsFixedLength { - get => _tag.StringIsFixedLength; - set => _tag.StringIsFixedLength = value; + get => GetField(ref _stringIsFixedLength); + set => SetField(ref _stringIsFixedLength, value); } /// @@ -422,10 +533,11 @@ public bool? StringIsFixedLength /// public bool? StringIsZeroTerminated { - get => _tag.StringIsZeroTerminated; - set => _tag.StringIsZeroTerminated = value; + get => GetField(ref _stringIsZeroTerminated); + set => SetField(ref _stringIsZeroTerminated, value); } + private uint? _stringMaxCapacity; /// /// [OPTIONAL] /// Determines the maximum number of character bytes in a string. @@ -437,8 +549,8 @@ public bool? StringIsZeroTerminated /// public uint? StringMaxCapacity { - get => _tag.StringMaxCapacity; - set => _tag.StringMaxCapacity = value; + get => GetField(ref _stringMaxCapacity); + set => SetField(ref _stringMaxCapacity, value); } /// @@ -453,8 +565,8 @@ public uint? StringMaxCapacity /// public uint? StringPadBytes { - get => _tag.StringPadBytes; - set => _tag.StringPadBytes = value; + get => GetField(ref _stringPadBytes); + set => SetField(ref _stringPadBytes, value); } /// @@ -470,8 +582,66 @@ public uint? StringPadBytes /// public uint? StringTotalLength { - get => _tag.StringTotalLength; - set => _tag.StringTotalLength = value; + get => GetField(ref _stringTotalLength); + set => SetField(ref _stringTotalLength, value); + } + + + + + public void Dispose() + { + if (_isDisposed) + return; + + if (_isInitialized) + { + RemoveEventsAndRemoveCallback(); + var result = (Status)_native.plc_tag_destroy(nativeTagHandle); + ThrowIfStatusNotOk(result); + } + + _isDisposed = true; + } + + public void Abort() + { + ThrowIfAlreadyDisposed(); + var result = (Status)_native.plc_tag_abort(nativeTagHandle); + ThrowIfStatusNotOk(result); + } + + + + /// + /// Creates the underlying data structures and references required before tag operations. + /// + /// + /// + /// Initializes the tag by establishing necessary connections. + /// Can only be called once per instance. + /// Timeout is controlled via class property. + /// + public void Initialize() + { + + ThrowIfAlreadyDisposed(); + ThrowIfAlreadyInitialized(); + + var millisecondTimeout = (int)Timeout.TotalMilliseconds; + + SetUpEvents(); + + var attributeString = GetAttributeString(); + + var result = _native.plc_tag_create_ex(attributeString, coreLibCallbackFuncExDelegate, IntPtr.Zero, millisecondTimeout); + if (result < 0) + throw new LibPlcTagException((Status)result); + else + nativeTagHandle = result; + + + _isInitialized = true; } /// @@ -486,8 +656,8 @@ public uint? StringTotalLength /// public uint? MaxRequestsInFlight { - get => _tag.MaxRequestsInFlight; - set => _tag.MaxRequestsInFlight = value; + get => GetField(ref _maxRequestsInFlight); + set => SetField(ref _maxRequestsInFlight, value); } /// @@ -499,18 +669,52 @@ public uint? MaxRequestsInFlight /// Can only be called once per instance. /// Timeout is controlled via class property. /// - public void Initialize() => _tag.Initialize(); + public async Task InitializeAsync(CancellationToken token = default) + { - /// - /// Creates the underlying data structures and references required before tag operations. - /// - /// - /// - /// Initializes the tag by establishing necessary connections. - /// Can only be called once per instance. - /// Timeout is controlled via class property. - /// - public Task InitializeAsync(CancellationToken token = default) => _tag.InitializeAsync(token); + ThrowIfAlreadyDisposed(); + ThrowIfAlreadyInitialized(); + + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) + { + cts.CancelAfter(Timeout); + + using (cts.Token.Register(() => + { + if (createTasks.TryPop(out var createTask)) + { + Abort(); + RemoveEventsAndRemoveCallback(); + + if (token.IsCancellationRequested) + createTask.SetCanceled(); + else + createTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); + } + })) + { + SetUpEvents(); + + var createTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + createTasks.Push(createTask); + + var attributeString = GetAttributeString(); + + var result = _native.plc_tag_create_ex(attributeString, coreLibCallbackFuncExDelegate, IntPtr.Zero, TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION); + if (result < 0) + throw new LibPlcTagException((Status)result); + else + nativeTagHandle = result; + + if(GetStatus() == Status.Pending) + await createTask.Task.ConfigureAwait(false); + + ThrowIfStatusNotOk(createTask.Task.Result); + + _isInitialized = true; + } + } + } /// /// Executes a synchronous read on a tag. @@ -522,7 +726,16 @@ public uint? MaxRequestsInFlight /// The data is not automatically kept up to date. /// If you need to find out the data periodically, you need to read the tag periodically. /// - public void Read() => _tag.Read(); + public void Read() + { + ThrowIfAlreadyDisposed(); + InitializeIfRequired(); + + var millisecondTimeout = (int)Timeout.TotalMilliseconds; + + var result = (Status)_native.plc_tag_read(nativeTagHandle, millisecondTimeout); + ThrowIfStatusNotOk(result); + } /// /// Executes an asynchronous read on a tag. @@ -534,7 +747,36 @@ public uint? MaxRequestsInFlight /// The data is not automatically kept up to date. /// If you need to find out the data periodically, you need to read the tag periodically. /// - public Task ReadAsync(CancellationToken token = default) => _tag.ReadAsync(token); + public async Task ReadAsync(CancellationToken token = default) + { + ThrowIfAlreadyDisposed(); + await InitializeAsyncIfRequired(token).ConfigureAwait(false); + + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) + { + cts.CancelAfter(Timeout); + + using (cts.Token.Register(() => + { + if (readTasks.TryPop(out var readTask)) + { + Abort(); + + if (token.IsCancellationRequested) + readTask.SetCanceled(); + else + readTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); + } + })) + { + var readTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + readTasks.Push(readTask); + _native.plc_tag_read(nativeTagHandle, TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION); + await readTask.Task.ConfigureAwait(false); + ThrowIfStatusNotOk(readTask.Task.Result); + } + } + } /// /// Executes a synchronous write on a tag. @@ -544,7 +786,16 @@ public uint? MaxRequestsInFlight /// /// Writing a tag sends the data from local memory to the target PLC. /// - public void Write() => _tag.Write(); + public void Write() + { + ThrowIfAlreadyDisposed(); + InitializeIfRequired(); + + var millisecondTimeout = (int)Timeout.TotalMilliseconds; + + var result = (Status)_native.plc_tag_write(nativeTagHandle, millisecondTimeout); + ThrowIfStatusNotOk(result); + } /// /// Executes an asynchronous write on a tag. @@ -554,10 +805,67 @@ public uint? MaxRequestsInFlight /// /// Writing a tag sends the data from local memory to the target PLC. /// - public Task WriteAsync(CancellationToken token = default) => _tag.WriteAsync(token); + public async Task WriteAsync(CancellationToken token = default) + { + ThrowIfAlreadyDisposed(); + await InitializeAsyncIfRequired(token).ConfigureAwait(false); + + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) + { + cts.CancelAfter(Timeout); + + using (cts.Token.Register(() => + { + if (writeTasks.TryPop(out var writeTask)) + { + Abort(); + + if (token.IsCancellationRequested) + writeTask.SetCanceled(); + else + writeTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); + } + })) + { + var writeTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + writeTasks.Push(writeTask); + _native.plc_tag_write(nativeTagHandle, TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION); + await writeTask.Task.ConfigureAwait(false); + ThrowIfStatusNotOk(writeTask.Task.Result); + } + } + } + + public int GetSize() + { + ThrowIfAlreadyDisposed(); + + var result = _native.plc_tag_get_size(nativeTagHandle); + if (result < 0) + throw new LibPlcTagException((Status)result); + else + return result; + } + + public int SetSize(int newSize) + { + ThrowIfAlreadyDisposed(); + var result = _native.plc_tag_set_size(nativeTagHandle, newSize); + if (result < 0) + throw new LibPlcTagException((Status)result); + else + return result; + } - public void Abort() => _tag.Abort(); - public void Dispose() => _tag.Dispose(); + /// + /// Check the operational status of the tag + /// + /// Tag's current status + public Status GetStatus() + { + ThrowIfAlreadyDisposed(); + return (Status)_native.plc_tag_status(nativeTagHandle); + } /// /// This function retrieves a segment of raw, unprocessed bytes from the tag buffer. @@ -566,7 +874,18 @@ public uint? MaxRequestsInFlight /// Note; allocates a new block of memory. /// If this is problematic, use instead. /// - public byte[] GetBuffer() => _tag.GetBuffer(); + public byte[] GetBuffer() + { + ThrowIfAlreadyDisposed(); + + var tagSize = GetSize(); + var temp = new byte[tagSize]; + + var result = (Status)_native.plc_tag_get_raw_bytes(nativeTagHandle, 0, temp, temp.Length); + ThrowIfStatusNotOk(result); + + return temp; + } /// /// Fills the supplied buffer with the raw, unprocessed bytes from the tag buffer. @@ -574,7 +893,10 @@ public uint? MaxRequestsInFlight /// /// Use this instead of to avoid creating a new block of memory. /// - public void GetBuffer(byte[] buffer) => _tag.GetBuffer(buffer); + public void GetBuffer(byte[] buffer) + { + GetBuffer(0, buffer, buffer.Length); + } /// /// Fills the supplied buffer with the raw, unprocessed bytes from the tag buffer. @@ -582,63 +904,344 @@ public uint? MaxRequestsInFlight /// /// Use this instead of to avoid creating a new block of memory. /// - public void GetBuffer(int offset, byte[] buffer, int length) => _tag.GetBuffer(offset, buffer, length); + public void GetBuffer(int offset, byte[] buffer, int length) + { + ThrowIfAlreadyDisposed(); + + var tagSize = GetSize(); + var temp = new byte[tagSize]; + + var result = (Status)_native.plc_tag_get_raw_bytes(nativeTagHandle, offset, temp, length); + ThrowIfStatusNotOk(result); + } + + public void SetBuffer(byte[] buffer) + { + SetBuffer(0, buffer, buffer.Length); + } + + public void SetBuffer(int offset, byte[] buffer, int length) + { + ThrowIfAlreadyDisposed(); + + GetNativeValueAndThrowOnNegativeResult(_native.plc_tag_set_size, buffer.Length); + var result = (Status)_native.plc_tag_set_raw_bytes(nativeTagHandle, 0, buffer, buffer.Length); + ThrowIfStatusNotOk(result); + } + + + private int GetIntAttribute(string attributeName) + { + var result = _native.plc_tag_get_int_attribute(nativeTagHandle, attributeName, int.MinValue); + if (result == int.MinValue) + ThrowIfStatusNotOk(); + + return result; + } + + private void SetIntAttribute(string attributeName, int value) + { + var result = (Status)_native.plc_tag_set_int_attribute(nativeTagHandle, attributeName, value); + ThrowIfStatusNotOk(result); + } - public void SetBuffer(byte[] newBuffer) => _tag.SetBuffer(newBuffer); - public void SetBuffer(int offset, byte[] buffer, int length) => _tag.SetBuffer(offset, buffer, length); /// /// This function retrieves an attribute of the raw tag byte array. /// - public byte[] GetByteArrayAttribute(string attributeName) => _tag.GetByteArrayAttribute(attributeName); - public int GetSize() => _tag.GetSize(); - public int SetSize(int newSize) => _tag.SetSize(newSize); + public byte[] GetByteArrayAttribute(string attributeName) + { + ThrowIfAlreadyDisposed(); - /// - /// Check the operational status of the tag - /// - /// Tag's current status - public Status GetStatus() => _tag.GetStatus(); + var bufferLengthAttributeName = attributeName + ".length"; + var bufferLength = GetIntAttribute(bufferLengthAttributeName); + var buffer = new byte[bufferLength]; - public bool GetBit(int offset) => _tag.GetBit(offset); - public void SetBit(int offset, bool value) => _tag.SetBit(offset, value); + var result = (Status)_native.plc_tag_get_byte_array_attribute(nativeTagHandle, attributeName, buffer, buffer.Length); + ThrowIfStatusNotOk(result); - public float GetFloat32(int offset) => _tag.GetFloat32(offset); - public void SetFloat32(int offset, float value) => _tag.SetFloat32(offset, value); + return buffer; + } + + private void SetDebugLevel(DebugLevel level) + { + _native.plc_tag_set_debug_level((int)level); + } + + public bool GetBit(int offset) + { + ThrowIfAlreadyDisposed(); + + var result = _native.plc_tag_get_bit(nativeTagHandle, offset); + if (result == 0) + return false; + else if (result == 1) + return true; + else + throw new LibPlcTagException((Status)result); + } + + public void SetBit(int offset, bool value) => SetNativeTagValue(_native.plc_tag_set_bit, offset, value == true ? 1 : 0); + + public ulong GetUInt64(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_uint64, offset, ulong.MaxValue); + public void SetUInt64(int offset, ulong value) => SetNativeTagValue(_native.plc_tag_set_uint64, offset, value); + + public long GetInt64(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_int64, offset, long.MinValue); + public void SetInt64(int offset, long value) => SetNativeTagValue(_native.plc_tag_set_int64, offset, value); - public double GetFloat64(int offset) => _tag.GetFloat64(offset); - public void SetFloat64(int offset, double value) => _tag.SetFloat64(offset, value); + public uint GetUInt32(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_uint32, offset, uint.MaxValue); + public void SetUInt32(int offset, uint value) => SetNativeTagValue(_native.plc_tag_set_uint32, offset, value); - public sbyte GetInt8(int offset) => _tag.GetInt8(offset); - public void SetInt8(int offset, sbyte value) => _tag.SetInt8(offset, value); + public int GetInt32(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_int32, offset, int.MinValue); + public void SetInt32(int offset, int value) => SetNativeTagValue(_native.plc_tag_set_int32, offset, value); - public short GetInt16(int offset) => _tag.GetInt16(offset); - public void SetInt16(int offset, short value) => _tag.SetInt16(offset, value); + public ushort GetUInt16(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_uint16, offset, ushort.MaxValue); + public void SetUInt16(int offset, ushort value) => SetNativeTagValue(_native.plc_tag_set_uint16, offset, value); - public int GetInt32(int offset) => _tag.GetInt32(offset); - public void SetInt32(int offset, int value) => _tag.SetInt32(offset, value); + public short GetInt16(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_int16, offset, short.MinValue); + public void SetInt16(int offset, short value) => SetNativeTagValue(_native.plc_tag_set_int16, offset, value); - public long GetInt64(int offset) => _tag.GetInt64(offset); - public void SetInt64(int offset, long value) => _tag.SetInt64(offset, value); + public byte GetUInt8(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_uint8, offset, byte.MaxValue); + public void SetUInt8(int offset, byte value) => SetNativeTagValue(_native.plc_tag_set_uint8, offset, value); - public byte GetUInt8(int offset) => _tag.GetUInt8(offset); - public void SetUInt8(int offset, byte value) => _tag.SetUInt8(offset, value); + public sbyte GetInt8(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_int8, offset, sbyte.MinValue); + public void SetInt8(int offset, sbyte value) => SetNativeTagValue(_native.plc_tag_set_int8, offset, value); - public ushort GetUInt16(int offset) => _tag.GetUInt16(offset); - public void SetUInt16(int offset, ushort value) => _tag.SetUInt16(offset, value); + public double GetFloat64(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_float64, offset, double.MinValue); + public void SetFloat64(int offset, double value) => SetNativeTagValue(_native.plc_tag_set_float64, offset, value); - public uint GetUInt32(int offset) => _tag.GetUInt32(offset); - public void SetUInt32(int offset, uint value) => _tag.SetUInt32(offset, value); + public float GetFloat32(int offset) => GetNativeValueAndThrowOnSpecificResult(_native.plc_tag_get_float32, offset, float.MinValue); + public void SetFloat32(int offset, float value) => SetNativeTagValue(_native.plc_tag_set_float32, offset, value); - public ulong GetUInt64(int offset) => _tag.GetUInt64(offset); - public void SetUInt64(int offset, ulong value) => _tag.SetUInt64(offset, value); - public void SetString(int offset, string value) => _tag.SetString(offset, value); - public int GetStringLength(int offset) => _tag.GetStringLength(offset); - public int GetStringTotalLength(int offset) => _tag.GetStringTotalLength(offset); - public int GetStringCapacity(int offset) => _tag.GetStringCapacity(offset); - public string GetString(int offset) => _tag.GetString(offset); + public void SetString(int offset, string value) => SetNativeTagValue(_native.plc_tag_set_string, offset, value); + public int GetStringLength(int offset) => GetNativeValueAndThrowOnNegativeResult(_native.plc_tag_get_string_length, offset); + public int GetStringCapacity(int offset) => GetNativeValueAndThrowOnNegativeResult(_native.plc_tag_get_string_capacity, offset); + public int GetStringTotalLength(int offset) => GetNativeValueAndThrowOnNegativeResult(_native.plc_tag_get_string_total_length, offset); + public string GetString(int offset) + { + ThrowIfAlreadyDisposed(); + var stringLength = GetStringLength(offset); + var sb = new StringBuilder(stringLength); + var status = (Status)_native.plc_tag_get_string(nativeTagHandle, offset, sb, stringLength); + ThrowIfStatusNotOk(status); + return sb.ToString().Substring(0, stringLength < sb.Length ? stringLength : sb.Length); + } + + + private void ThrowIfAlreadyDisposed() + { + if (_isDisposed) + throw new ObjectDisposedException(GetType().FullName); + } + + private void InitializeIfRequired() + { + if (!_isInitialized) + Initialize(); + } + + private Task InitializeAsyncIfRequired(CancellationToken token) + { + if (!_isInitialized) + return InitializeAsync(token); + else + return Task.CompletedTask; + } + + private void ThrowIfAlreadyInitialized() + { + if (_isInitialized) + throw new InvalidOperationException("Already initialized"); + } + + private void ThrowIfStatusNotOk(Status? status = null) + { + var statusToCheck = status ?? GetStatus(); + if (statusToCheck != Status.Ok) + throw new LibPlcTagException(statusToCheck); + } + + + + private void SetNativeTagValue(Func nativeMethod, int offset, T value) + { + ThrowIfAlreadyDisposed(); + var result = (Status)nativeMethod(nativeTagHandle, offset, value); + ThrowIfStatusNotOk(result); + } + + private int GetNativeValueAndThrowOnNegativeResult(Func nativeMethod, int offset) + { + ThrowIfAlreadyDisposed(); + var result = nativeMethod(nativeTagHandle, offset); + if (result < 0) + throw new LibPlcTagException((Status)result); + return result; + } + + private T GetNativeValueAndThrowOnSpecificResult(Func nativeMethod, int offset, T valueIndicatingPossibleError) + where T : struct + { + ThrowIfAlreadyDisposed(); + var result = nativeMethod(nativeTagHandle, offset); + if (result.Equals(valueIndicatingPossibleError)) + ThrowIfStatusNotOk(); + return result; + } + + private T GetField(ref T field) + { + ThrowIfAlreadyDisposed(); + return field; + } + + private void SetField(ref T field, T value) + { + ThrowIfAlreadyDisposed(); + ThrowIfAlreadyInitialized(); + field = value; + } + + private string GetAttributeString() + { + + string FormatNullableBoolean(bool? value) + => value.HasValue ? (value.Value ? "1" : "0") : null; + + string FormatPlcType(PlcType? type) + { + if (type == libplctag.PlcType.Omron) + return "omron-njnx"; + else + return type?.ToString().ToLowerInvariant(); + } + + string FormatTimeSpan(TimeSpan? timespan) + { + if(timespan.HasValue) + return ((int)timespan.Value.TotalMilliseconds).ToString(); + else + return null; + } + + var attributes = new Dictionary + { + { "protocol", Protocol?.ToString() }, + { "gateway", Gateway }, + { "path", Path }, + { "plc", FormatPlcType(PlcType) }, + { "elem_size", ElementSize?.ToString() }, + { "elem_count", ElementCount?.ToString() }, + { "name", Name }, + { "read_cache_ms", ReadCacheMillisecondDuration?.ToString() }, + { "use_connected_msg", FormatNullableBoolean(UseConnectedMessaging) }, + { "allow_packing", FormatNullableBoolean(AllowPacking) }, + { "auto_sync_read_ms", FormatTimeSpan(AutoSyncReadInterval) }, + { "auto_sync_write_ms", FormatTimeSpan(AutoSyncWriteInterval) }, + { "debug", DebugLevel == DebugLevel.None ? null : ((int)DebugLevel).ToString() }, + { "int16_byte_order", Int16ByteOrder }, + { "int32_byte_order", Int32ByteOrder }, + { "int64_byte_order", Int64ByteOrder }, + { "float32_byte_order", Float32ByteOrder }, + { "float64_byte_order", Float64ByteOrder }, + { "str_count_word_bytes", StringCountWordBytes?.ToString() }, + { "str_is_byte_swapped", FormatNullableBoolean(StringIsByteSwapped) }, + { "str_is_counted", FormatNullableBoolean(StringIsCounted) }, + { "str_is_fixed_length", FormatNullableBoolean(StringIsFixedLength) }, + { "str_is_zero_terminated", FormatNullableBoolean(StringIsFixedLength) }, + { "str_max_capacity", StringMaxCapacity?.ToString() }, + { "str_pad_bytes", StringPadBytes?.ToString() }, + { "str_total_length", StringTotalLength?.ToString() }, + { "max_requests_in_flight", MaxRequestsInFlight?.ToString() }, + }; + + string separator = "&"; + return string.Join(separator, attributes.Where(attr => attr.Value != null).Select(attr => $"{attr.Key}={attr.Value}")); + + } + + + + + void SetUpEvents() + { + + // Used to finalize the asynchronous read/write task completion sources + ReadCompleted += ReadTaskCompleter; + WriteCompleted += WriteTaskCompleter; + Created += CreatedTaskCompleter; + + // Need to keep a reference to the delegate in memory so it doesn't get garbage collected + coreLibCallbackFuncExDelegate = new callback_func_ex(coreLibEventCallback); + + } + + void RemoveEventsAndRemoveCallback() + { + + // Used to finalize the read/write task completion sources + ReadCompleted -= ReadTaskCompleter; + WriteCompleted -= WriteTaskCompleter; + Created -= CreatedTaskCompleter; + + var callbackRemovalResult = (Status)_native.plc_tag_unregister_callback(nativeTagHandle); + ThrowIfStatusNotOk(callbackRemovalResult); + + } + + private readonly ConcurrentStack> createTasks = new ConcurrentStack>(); + void CreatedTaskCompleter(object sender, TagEventArgs e) + { + if (createTasks.TryPop(out var createTask)) + { + switch (e.Status) + { + case Status.Pending: + // Do nothing, wait for another ReadCompleted callback + break; + default: + createTask?.SetResult(e.Status); + break; + } + } + } + + private readonly ConcurrentStack> readTasks = new ConcurrentStack>(); + void ReadTaskCompleter(object sender, TagEventArgs e) + { + if (readTasks.TryPop(out var readTask)) + { + switch (e.Status) + { + case Status.Pending: + // Do nothing, wait for another ReadCompleted callback + break; + default: + readTask?.SetResult(e.Status); + break; + } + } + } + + private readonly ConcurrentStack> writeTasks = new ConcurrentStack>(); + void WriteTaskCompleter(object sender, TagEventArgs e) + { + if (writeTasks.TryPop(out var writeTask)) + { + switch (e.Status) + { + case Status.Pending: + // Do nothing, wait for another WriteCompleted callback + break; + default: + writeTask?.SetResult(e.Status); + break; + + } + } + } public event EventHandler ReadStarted; public event EventHandler ReadCompleted; @@ -646,11 +1249,42 @@ public uint? MaxRequestsInFlight public event EventHandler WriteCompleted; public event EventHandler Aborted; public event EventHandler Destroyed; + public event EventHandler Created; - ~Tag() + void coreLibEventCallback(int eventTagHandle, int eventCode, int statusCode, IntPtr userdata) { - Dispose(); + var @event = (Event)eventCode; + var status = (Status)statusCode; + var eventArgs = new TagEventArgs() { Status = status }; + + switch (@event) + { + case Event.ReadCompleted: + ReadCompleted?.Invoke(this, eventArgs); + break; + case Event.ReadStarted: + ReadStarted?.Invoke(this, eventArgs); + break; + case Event.WriteStarted: + WriteStarted?.Invoke(this, eventArgs); + break; + case Event.WriteCompleted: + WriteCompleted?.Invoke(this, eventArgs); + break; + case Event.Aborted: + Aborted?.Invoke(this, eventArgs); + break; + case Event.Destroyed: + Destroyed?.Invoke(this, eventArgs); + break; + case Event.Created: + Created?.Invoke(this, eventArgs); + break; + default: + throw new NotImplementedException(); + } } } + } diff --git a/src/libplctag/TagEventArgs.cs b/src/libplctag/TagEventArgs.cs index c10c78ac..c053d122 100644 --- a/src/libplctag/TagEventArgs.cs +++ b/src/libplctag/TagEventArgs.cs @@ -6,8 +6,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System; -using System.Collections.Generic; -using System.Text; namespace libplctag { diff --git a/src/libplctag/TagOfT.cs b/src/libplctag/TagOfT.cs index 88390465..40214523 100644 --- a/src/libplctag/TagOfT.cs +++ b/src/libplctag/TagOfT.cs @@ -17,6 +17,7 @@ namespace libplctag /// /// A class that handles data conversion /// The desired C# type of Tag.Value + [Obsolete("see - https://github.com/libplctag/libplctag.NET/issues/406")] public class Tag : IDisposable, ITag where M : IPlcMapper, new() {