From bf3079f2476dbf36acb1a3e72f34c129b3408409 Mon Sep 17 00:00:00 2001 From: Luca Domenichini Date: Tue, 12 Dec 2023 12:33:14 +0100 Subject: [PATCH 1/6] FIX typo --- Core/SmartIOT.Connector.Core/Model/Device.cs | 2 +- Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/SmartIOT.Connector.Core/Model/Device.cs b/Core/SmartIOT.Connector.Core/Model/Device.cs index 2046a3a..e892640 100644 --- a/Core/SmartIOT.Connector.Core/Model/Device.cs +++ b/Core/SmartIOT.Connector.Core/Model/Device.cs @@ -142,7 +142,7 @@ public bool UpdateTag(TagConfiguration tagConfiguration) } } - internal void IncrementOrReseDeviceErrorCode(int err) + internal void IncrementOrResetDeviceErrorCode(int err) { ErrorCode = err; if (err != 0) diff --git a/Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs b/Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs index 5903b71..dd04a8b 100644 --- a/Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs +++ b/Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs @@ -580,14 +580,14 @@ public void RestartDriver() success = false; message = description; - device.IncrementOrReseDeviceErrorCode(err); + device.IncrementOrResetDeviceErrorCode(err); OnDeviceStatusEvent(new DeviceStatusEvent(device, device.DeviceStatus, err, description)); } else { (err, description) = TryWithDeviceDriver(x => x.Connect(device)); - device.IncrementOrReseDeviceErrorCode(err); + device.IncrementOrResetDeviceErrorCode(err); if (err != 0) { From 9e2f7a235f88ff69504e09999404f3ea95b36dbb Mon Sep 17 00:00:00 2001 From: Luca Domenichini Date: Tue, 12 Dec 2023 14:49:01 +0100 Subject: [PATCH 2/6] UPDATE to NET8.0 Core projects --- .../AspNetExtensions.cs | 4 +++- Apps/SmartIOT.Connector.ConsoleApp/Program.cs | 2 +- .../SmartIOT.Connector.ConsoleApp.csproj | 12 ++++++------ .../SmartIOT.Connector.Mqtt.csproj | 6 +++--- .../SmartIOT.Connector.Tcp.csproj | 2 +- .../SmartIOT.Connector.Core.csproj | 2 +- .../SmartIOT.Connector.Messages.csproj | 6 +++--- .../SmartIOT.Connector.Prometheus.csproj | 4 ++-- .../AspNetCoreExtensions.cs | 16 +++++++++------- .../Controllers/V1/ConfigurationController.cs | 3 ++- .../Controllers/V1/ConnectorController.cs | 3 ++- .../Controllers/V1/DeviceController.cs | 3 ++- .../Controllers/V1/SchedulerController.cs | 3 ++- .../SmartIOT.Connector.RestApi.csproj | 11 ++++++----- .../SwaggerVersioningOptions.cs | 4 ++-- .../SmartIOT.Connector.Plc.S7Net.csproj | 2 +- .../SmartIOT.Connector.Plc.Snap7.csproj | 2 +- .../MainWindow.xaml.cs | 2 +- .../SmartIOT.Connector.MqttClient.Tester.csproj | 2 +- .../MainWindow.xaml.cs | 4 ++-- .../SmartIOT.Connector.MqttServer.Tester.csproj | 2 +- .../SmartIOT.Connector.Core.Tests.csproj | 8 ++++---- .../SmartIOT.Connector.Mocks.csproj | 2 +- .../SmartIOT.Connector.Mqtt.Tests.csproj | 8 ++++---- .../SmartIOT.Connector.Prometheus.Tests.csproj | 8 ++++---- .../SmartIOT.Connector.RestApi.Tests.csproj | 8 ++++---- .../SmartIOT.Connector.Tcp.Tests.csproj | 8 ++++---- 27 files changed, 73 insertions(+), 64 deletions(-) diff --git a/Apps/SmartIOT.Connector.ConsoleApp/AspNetExtensions.cs b/Apps/SmartIOT.Connector.ConsoleApp/AspNetExtensions.cs index 7367ffa..3f16e92 100644 --- a/Apps/SmartIOT.Connector.ConsoleApp/AspNetExtensions.cs +++ b/Apps/SmartIOT.Connector.ConsoleApp/AspNetExtensions.cs @@ -15,7 +15,9 @@ public static IServiceCollection AddSmartIotConnectorRunner(this IServiceCollect services.AddSingleton(s => s.GetRequiredService().SchedulerFactory); services.AddSingleton(s => s.GetRequiredService().TimeService); - services.AddHostedService(); + // add custom Runner as a singleton and as a hosted service + services.AddSingleton(); + services.AddHostedService(sp => sp.GetRequiredService()); return services; } diff --git a/Apps/SmartIOT.Connector.ConsoleApp/Program.cs b/Apps/SmartIOT.Connector.ConsoleApp/Program.cs index 70fb016..2f36728 100644 --- a/Apps/SmartIOT.Connector.ConsoleApp/Program.cs +++ b/Apps/SmartIOT.Connector.ConsoleApp/Program.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Asp.Versioning.ApiExplorer; using Serilog; using SmartIOT.Connector.RestApi; using System.Diagnostics; diff --git a/Apps/SmartIOT.Connector.ConsoleApp/SmartIOT.Connector.ConsoleApp.csproj b/Apps/SmartIOT.Connector.ConsoleApp/SmartIOT.Connector.ConsoleApp.csproj index 5e030b5..fda75bd 100644 --- a/Apps/SmartIOT.Connector.ConsoleApp/SmartIOT.Connector.ConsoleApp.csproj +++ b/Apps/SmartIOT.Connector.ConsoleApp/SmartIOT.Connector.ConsoleApp.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable Linux @@ -16,16 +16,16 @@ MIT Luca Domenichini 1.0.0.0 - test + false - + - - - + + + diff --git a/Connectors/SmartIOT.Connector.Mqtt/SmartIOT.Connector.Mqtt.csproj b/Connectors/SmartIOT.Connector.Mqtt/SmartIOT.Connector.Mqtt.csproj index 1246654..7a6f18e 100644 --- a/Connectors/SmartIOT.Connector.Mqtt/SmartIOT.Connector.Mqtt.csproj +++ b/Connectors/SmartIOT.Connector.Mqtt/SmartIOT.Connector.Mqtt.csproj @@ -1,7 +1,7 @@  - net6.0 + net6.0;net8.0 enable enable Luca Domenichini @@ -17,8 +17,8 @@ - - + + diff --git a/Connectors/SmartIOT.Connector.Tcp/SmartIOT.Connector.Tcp.csproj b/Connectors/SmartIOT.Connector.Tcp/SmartIOT.Connector.Tcp.csproj index d14f761..db85b15 100644 --- a/Connectors/SmartIOT.Connector.Tcp/SmartIOT.Connector.Tcp.csproj +++ b/Connectors/SmartIOT.Connector.Tcp/SmartIOT.Connector.Tcp.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable enable Luca Domenichini diff --git a/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj b/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj index 13609f2..3e83512 100644 --- a/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj +++ b/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj @@ -1,7 +1,7 @@  - net6.0 + net6.0;net8.0 enable enable Luca Domenichini diff --git a/Core/SmartIOT.Connector.Messages/SmartIOT.Connector.Messages.csproj b/Core/SmartIOT.Connector.Messages/SmartIOT.Connector.Messages.csproj index 8af131b..d63e34a 100644 --- a/Core/SmartIOT.Connector.Messages/SmartIOT.Connector.Messages.csproj +++ b/Core/SmartIOT.Connector.Messages/SmartIOT.Connector.Messages.csproj @@ -1,7 +1,7 @@ - + - net6.0 + net6.0;net8.0 enable enable Luca Domenichini @@ -17,7 +17,7 @@ - + diff --git a/Core/SmartIOT.Connector.Prometheus/SmartIOT.Connector.Prometheus.csproj b/Core/SmartIOT.Connector.Prometheus/SmartIOT.Connector.Prometheus.csproj index 044916e..fbeb3b2 100644 --- a/Core/SmartIOT.Connector.Prometheus/SmartIOT.Connector.Prometheus.csproj +++ b/Core/SmartIOT.Connector.Prometheus/SmartIOT.Connector.Prometheus.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable enable Luca Domenichini @@ -17,7 +17,7 @@ - + diff --git a/Core/SmartIOT.Connector.RestApi/AspNetCoreExtensions.cs b/Core/SmartIOT.Connector.RestApi/AspNetCoreExtensions.cs index 4e00039..3a5fdf6 100644 --- a/Core/SmartIOT.Connector.RestApi/AspNetCoreExtensions.cs +++ b/Core/SmartIOT.Connector.RestApi/AspNetCoreExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Asp.Versioning; +using Asp.Versioning.ApiExplorer; using Microsoft.Extensions.DependencyInjection; using SmartIOT.Connector.Core; using SmartIOT.Connector.RestApi.Services; @@ -15,13 +16,14 @@ public static IServiceCollection AddSmartIotConnectorRestApi(this IServiceCollec config.DefaultApiVersion = new ApiVersion(1, 0); config.AssumeDefaultVersionWhenUnspecified = true; config.ReportApiVersions = true; - }); + }) + .AddApiExplorer(options => + { + options.GroupNameFormat = "'v'VVV"; + options.SubstituteApiVersionInUrl = true; + }); - services.AddVersionedApiExplorer(setup => - { - setup.GroupNameFormat = "'v'VVV"; - setup.SubstituteApiVersionInUrl = true; - }); + services.AddTransient(); services.ConfigureOptions(); diff --git a/Core/SmartIOT.Connector.RestApi/Controllers/V1/ConfigurationController.cs b/Core/SmartIOT.Connector.RestApi/Controllers/V1/ConfigurationController.cs index bbc6d86..9affd6b 100644 --- a/Core/SmartIOT.Connector.RestApi/Controllers/V1/ConfigurationController.cs +++ b/Core/SmartIOT.Connector.RestApi/Controllers/V1/ConfigurationController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using Asp.Versioning; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using SmartIOT.Connector.Core; using SmartIOT.Connector.RestApi.Services; diff --git a/Core/SmartIOT.Connector.RestApi/Controllers/V1/ConnectorController.cs b/Core/SmartIOT.Connector.RestApi/Controllers/V1/ConnectorController.cs index a7ed8b8..ea58a1b 100644 --- a/Core/SmartIOT.Connector.RestApi/Controllers/V1/ConnectorController.cs +++ b/Core/SmartIOT.Connector.RestApi/Controllers/V1/ConnectorController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using Asp.Versioning; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using SmartIOT.Connector.Core; using SmartIOT.Connector.RestApi.Services; diff --git a/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs b/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs index 545f040..8219589 100644 --- a/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs +++ b/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using Asp.Versioning; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using SmartIOT.Connector.Core; using SmartIOT.Connector.Core.Conf; diff --git a/Core/SmartIOT.Connector.RestApi/Controllers/V1/SchedulerController.cs b/Core/SmartIOT.Connector.RestApi/Controllers/V1/SchedulerController.cs index d08fd9f..a984834 100644 --- a/Core/SmartIOT.Connector.RestApi/Controllers/V1/SchedulerController.cs +++ b/Core/SmartIOT.Connector.RestApi/Controllers/V1/SchedulerController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using Asp.Versioning; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using SmartIOT.Connector.Core; using SmartIOT.Connector.Core.Conf; diff --git a/Core/SmartIOT.Connector.RestApi/SmartIOT.Connector.RestApi.csproj b/Core/SmartIOT.Connector.RestApi/SmartIOT.Connector.RestApi.csproj index 4db1e46..b18422a 100644 --- a/Core/SmartIOT.Connector.RestApi/SmartIOT.Connector.RestApi.csproj +++ b/Core/SmartIOT.Connector.RestApi/SmartIOT.Connector.RestApi.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable enable true @@ -26,11 +26,12 @@ - - - - + + + + + diff --git a/Core/SmartIOT.Connector.RestApi/SwaggerVersioningOptions.cs b/Core/SmartIOT.Connector.RestApi/SwaggerVersioningOptions.cs index 7372c8b..ca19eb6 100644 --- a/Core/SmartIOT.Connector.RestApi/SwaggerVersioningOptions.cs +++ b/Core/SmartIOT.Connector.RestApi/SwaggerVersioningOptions.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Asp.Versioning.ApiExplorer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; @@ -27,7 +27,7 @@ public void Configure(SwaggerGenOptions options) } } - public void Configure(string name, SwaggerGenOptions options) + public void Configure(string? name, SwaggerGenOptions options) { Configure(options); } diff --git a/Devices/SmartIOT.Connector.Plc.S7Net/SmartIOT.Connector.Plc.S7Net.csproj b/Devices/SmartIOT.Connector.Plc.S7Net/SmartIOT.Connector.Plc.S7Net.csproj index 0607958..9ca4519 100644 --- a/Devices/SmartIOT.Connector.Plc.S7Net/SmartIOT.Connector.Plc.S7Net.csproj +++ b/Devices/SmartIOT.Connector.Plc.S7Net/SmartIOT.Connector.Plc.S7Net.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable enable Luca Domenichini diff --git a/Devices/SmartIOT.Connector.Plc.Snap7/SmartIOT.Connector.Plc.Snap7.csproj b/Devices/SmartIOT.Connector.Plc.Snap7/SmartIOT.Connector.Plc.Snap7.csproj index 015f36a..bf0f632 100644 --- a/Devices/SmartIOT.Connector.Plc.Snap7/SmartIOT.Connector.Plc.Snap7.csproj +++ b/Devices/SmartIOT.Connector.Plc.Snap7/SmartIOT.Connector.Plc.Snap7.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable enable Luca Domenichini diff --git a/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml.cs b/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml.cs index 6819314..90e795e 100644 --- a/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml.cs +++ b/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml.cs @@ -203,7 +203,7 @@ private void DoWriteData(string deviceId, string tagId, string topic, int offset { TagWriteRequestCommand msg = new TagWriteRequestCommand(deviceId, tagId, offset, data); - _mqttClient.PublishAsync(new MqttApplicationMessageBuilder() + _mqttClient!.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic(topic) .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce) .WithPayload(_messageSerializer!.SerializeMessage(msg)) diff --git a/Testers/SmartIOT.Connector.MqttClient.Tester/SmartIOT.Connector.MqttClient.Tester.csproj b/Testers/SmartIOT.Connector.MqttClient.Tester/SmartIOT.Connector.MqttClient.Tester.csproj index 3ca0d05..748323c 100644 --- a/Testers/SmartIOT.Connector.MqttClient.Tester/SmartIOT.Connector.MqttClient.Tester.csproj +++ b/Testers/SmartIOT.Connector.MqttClient.Tester/SmartIOT.Connector.MqttClient.Tester.csproj @@ -10,7 +10,7 @@ - + diff --git a/Testers/SmartIOT.Connector.MqttServer.Tester/MainWindow.xaml.cs b/Testers/SmartIOT.Connector.MqttServer.Tester/MainWindow.xaml.cs index 5e6b17a..21b8d6d 100644 --- a/Testers/SmartIOT.Connector.MqttServer.Tester/MainWindow.xaml.cs +++ b/Testers/SmartIOT.Connector.MqttServer.Tester/MainWindow.xaml.cs @@ -16,7 +16,7 @@ namespace SmartIOT.Connector.MqttServer.Tester; /// public partial class MainWindow : Window { - private MQTTnet.Server.MqttServer _mqttServer; + private MQTTnet.Server.MqttServer? _mqttServer; private ISingleMessageSerializer? _messageSerializer; private string? _deviceStatusTopic; private string? _tagReadTopic; @@ -214,7 +214,7 @@ private void DoWriteData(string deviceId, string tagId, string topic, int offset { TagWriteRequestCommand msg = new TagWriteRequestCommand(deviceId, tagId, offset, data); - _mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage( + _mqttServer!.InjectApplicationMessage(new InjectedMqttApplicationMessage( new MqttApplicationMessageBuilder() .WithTopic(topic) .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce) diff --git a/Testers/SmartIOT.Connector.MqttServer.Tester/SmartIOT.Connector.MqttServer.Tester.csproj b/Testers/SmartIOT.Connector.MqttServer.Tester/SmartIOT.Connector.MqttServer.Tester.csproj index 867a788..852e2b7 100644 --- a/Testers/SmartIOT.Connector.MqttServer.Tester/SmartIOT.Connector.MqttServer.Tester.csproj +++ b/Testers/SmartIOT.Connector.MqttServer.Tester/SmartIOT.Connector.MqttServer.Tester.csproj @@ -10,7 +10,7 @@ - + diff --git a/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj b/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj index 53b2788..c9e5ad1 100644 --- a/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj +++ b/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable false 1.0.0.0 @@ -9,10 +9,10 @@ - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/SmartIOT.Connector.Mocks/SmartIOT.Connector.Mocks.csproj b/Tests/SmartIOT.Connector.Mocks/SmartIOT.Connector.Mocks.csproj index cd42a83..2294582 100644 --- a/Tests/SmartIOT.Connector.Mocks/SmartIOT.Connector.Mocks.csproj +++ b/Tests/SmartIOT.Connector.Mocks/SmartIOT.Connector.Mocks.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable enable 1.0.0.0 diff --git a/Tests/SmartIOT.Connector.Mqtt.Tests/SmartIOT.Connector.Mqtt.Tests.csproj b/Tests/SmartIOT.Connector.Mqtt.Tests/SmartIOT.Connector.Mqtt.Tests.csproj index fdd5779..ce80396 100644 --- a/Tests/SmartIOT.Connector.Mqtt.Tests/SmartIOT.Connector.Mqtt.Tests.csproj +++ b/Tests/SmartIOT.Connector.Mqtt.Tests/SmartIOT.Connector.Mqtt.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable false 1.0.0.0 @@ -9,9 +9,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/SmartIOT.Connector.Prometheus.Tests/SmartIOT.Connector.Prometheus.Tests.csproj b/Tests/SmartIOT.Connector.Prometheus.Tests/SmartIOT.Connector.Prometheus.Tests.csproj index ac36bad..0822bd0 100644 --- a/Tests/SmartIOT.Connector.Prometheus.Tests/SmartIOT.Connector.Prometheus.Tests.csproj +++ b/Tests/SmartIOT.Connector.Prometheus.Tests/SmartIOT.Connector.Prometheus.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable false 1.0.0.0 @@ -9,9 +9,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/SmartIOT.Connector.RestApi.Tests/SmartIOT.Connector.RestApi.Tests.csproj b/Tests/SmartIOT.Connector.RestApi.Tests/SmartIOT.Connector.RestApi.Tests.csproj index 1ae70ac..9674f13 100644 --- a/Tests/SmartIOT.Connector.RestApi.Tests/SmartIOT.Connector.RestApi.Tests.csproj +++ b/Tests/SmartIOT.Connector.RestApi.Tests/SmartIOT.Connector.RestApi.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable @@ -9,9 +9,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/SmartIOT.Connector.Tcp.Tests/SmartIOT.Connector.Tcp.Tests.csproj b/Tests/SmartIOT.Connector.Tcp.Tests/SmartIOT.Connector.Tcp.Tests.csproj index 7da7590..0800427 100644 --- a/Tests/SmartIOT.Connector.Tcp.Tests/SmartIOT.Connector.Tcp.Tests.csproj +++ b/Tests/SmartIOT.Connector.Tcp.Tests/SmartIOT.Connector.Tcp.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable enable false @@ -10,9 +10,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all From 59e488adcca5a522949ba165beb398aca018d885 Mon Sep 17 00:00:00 2001 From: Luca Domenichini Date: Tue, 12 Dec 2023 14:51:18 +0100 Subject: [PATCH 3/6] UPD github workflow to net8 --- .github/workflows/dotnet-develop.yml | 2 +- .github/workflows/dotnet-master.yml | 2 +- .github/workflows/dotnet-prerelease.yml | 2 +- .github/workflows/dotnet-release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dotnet-develop.yml b/.github/workflows/dotnet-develop.yml index 1bfe827..f9f3497 100644 --- a/.github/workflows/dotnet-develop.yml +++ b/.github/workflows/dotnet-develop.yml @@ -14,7 +14,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v2 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore SmartIOT.Connector.sln - name: Build diff --git a/.github/workflows/dotnet-master.yml b/.github/workflows/dotnet-master.yml index 325b713..b363c3b 100644 --- a/.github/workflows/dotnet-master.yml +++ b/.github/workflows/dotnet-master.yml @@ -14,7 +14,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v2 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore SmartIOT.Connector.sln - name: Build diff --git a/.github/workflows/dotnet-prerelease.yml b/.github/workflows/dotnet-prerelease.yml index 843b230..66c2b57 100644 --- a/.github/workflows/dotnet-prerelease.yml +++ b/.github/workflows/dotnet-prerelease.yml @@ -25,7 +25,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v2 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore SmartIOT.Connector.sln - name: Build diff --git a/.github/workflows/dotnet-release.yml b/.github/workflows/dotnet-release.yml index cb4f3b9..1810ed9 100644 --- a/.github/workflows/dotnet-release.yml +++ b/.github/workflows/dotnet-release.yml @@ -25,7 +25,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v2 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore SmartIOT.Connector.sln - name: Build From f50e4f06ab9ac9c0c1218628fdf97edfdf18571f Mon Sep 17 00:00:00 2001 From: Luca Domenichini Date: Wed, 13 Dec 2023 16:13:16 +0100 Subject: [PATCH 4/6] ADD SnapModBus driver --- .github/workflows/dotnet-prerelease.yml | 3 +- .github/workflows/dotnet-release.yml | 4 +- .../ConfigurationPersister.cs | 12 +- Apps/SmartIOT.Connector.ConsoleApp/Runner.cs | 2 +- .../SmartIOT.Connector.ConsoleApp.csproj | 1 + .../smartiot-config.json | 2 +- .../SmartIOT.Connector.Core.csproj | 2 +- .../S7NetDriver.cs | 40 +- .../S7NetPlcConfiguration.cs | 10 +- .../Snap7Driver.cs | 70 +- .../SmartIOT.Connector.Plc.Snap7/Snap7Plc.cs | 2 +- .../Snap7PlcConfiguration.cs | 8 +- .../README.md | 13 + .../SmartIOT.Connector.Plc.SnapModBus.csproj | 29 + .../SnapMB.net.cs | 872 ++++++++++++++++++ .../SnapModBusDriver.cs | 149 +++ .../SnapModBusDriverFactory.cs | 21 + .../SnapModBusNode.cs | 50 + .../SnapModBusNodeConfiguration.cs | 35 + .../snapmb.dll | Bin 0 -> 264704 bytes README.md | 168 ++-- SmartIOT.Connector.sln | 6 + .../MainWindow.xaml | 66 +- .../MainWindow.xaml.cs | 44 + .../DeviceControllerTests.cs | 6 +- 25 files changed, 1423 insertions(+), 192 deletions(-) create mode 100644 Devices/SmartIOT.Connector.Plc.SnapModBus/README.md create mode 100644 Devices/SmartIOT.Connector.Plc.SnapModBus/SmartIOT.Connector.Plc.SnapModBus.csproj create mode 100644 Devices/SmartIOT.Connector.Plc.SnapModBus/SnapMB.net.cs create mode 100644 Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs create mode 100644 Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriverFactory.cs create mode 100644 Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNode.cs create mode 100644 Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNodeConfiguration.cs create mode 100644 Devices/SmartIOT.Connector.Plc.SnapModBus/snapmb.dll diff --git a/.github/workflows/dotnet-prerelease.yml b/.github/workflows/dotnet-prerelease.yml index 66c2b57..600f4cc 100644 --- a/.github/workflows/dotnet-prerelease.yml +++ b/.github/workflows/dotnet-prerelease.yml @@ -6,7 +6,7 @@ on: env: VERSION_MAJOR: 0 - VERSION_MINOR: 3 + VERSION_MINOR: 4 VERSION_SUFFIX: "-beta" jobs: @@ -44,4 +44,5 @@ jobs: dotnet nuget push Core\SmartIOT.Connector.RestApi\bin\Release\SmartIOT.Connector.RestApi.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Devices\SmartIOT.Connector.Plc.S7Net\bin\Release\SmartIOT.Connector.Plc.S7Net.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Devices\SmartIOT.Connector.Plc.Snap7\bin\Release\SmartIOT.Connector.Plc.Snap7.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} + dotnet nuget push Devices\SmartIOT.Connector.Plc.SnapModbus\bin\Release\SmartIOT.Connector.Plc.SnapModbus.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} diff --git a/.github/workflows/dotnet-release.yml b/.github/workflows/dotnet-release.yml index 1810ed9..96c5006 100644 --- a/.github/workflows/dotnet-release.yml +++ b/.github/workflows/dotnet-release.yml @@ -6,7 +6,7 @@ on: env: VERSION_MAJOR: 0 - VERSION_MINOR: 3 + VERSION_MINOR: 4 VERSION_SUFFIX: "" jobs: @@ -44,4 +44,4 @@ jobs: dotnet nuget push Core\SmartIOT.Connector.RestApi\bin\Release\SmartIOT.Connector.RestApi.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Devices\SmartIOT.Connector.Plc.S7Net\bin\Release\SmartIOT.Connector.Plc.S7Net.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Devices\SmartIOT.Connector.Plc.Snap7\bin\Release\SmartIOT.Connector.Plc.Snap7.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} - + dotnet nuget push Devices\SmartIOT.Connector.Plc.SnapModbus\bin\Release\SmartIOT.Connector.Plc.SnapModbus.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} diff --git a/Apps/SmartIOT.Connector.ConsoleApp/ConfigurationPersister.cs b/Apps/SmartIOT.Connector.ConsoleApp/ConfigurationPersister.cs index b69f5fc..b1207fe 100644 --- a/Apps/SmartIOT.Connector.ConsoleApp/ConfigurationPersister.cs +++ b/Apps/SmartIOT.Connector.ConsoleApp/ConfigurationPersister.cs @@ -6,22 +6,24 @@ namespace SmartIOT.Connector.ConsoleApp; internal class ConfigurationPersister : IConfigurationPersister { - private AppConfiguration _appConfiguration; + private readonly AppConfiguration _appConfiguration; + private readonly JsonSerializerOptions _options; private readonly string _configFilePath; public ConfigurationPersister(AppConfiguration appConfiguration, string configFilePath) { _appConfiguration = appConfiguration; _configFilePath = configFilePath; + _options = new JsonSerializerOptions() + { + WriteIndented = true + }; } public void PersistConfiguration(SmartIotConnectorConfiguration configuration) { _appConfiguration.Configuration = configuration; - File.WriteAllText(_configFilePath, JsonSerializer.Serialize(_appConfiguration, new JsonSerializerOptions() - { - WriteIndented = true - })); + File.WriteAllText(_configFilePath, JsonSerializer.Serialize(_appConfiguration, _options)); } } diff --git a/Apps/SmartIOT.Connector.ConsoleApp/Runner.cs b/Apps/SmartIOT.Connector.ConsoleApp/Runner.cs index 583996f..1eb1c50 100644 --- a/Apps/SmartIOT.Connector.ConsoleApp/Runner.cs +++ b/Apps/SmartIOT.Connector.ConsoleApp/Runner.cs @@ -63,7 +63,7 @@ public Runner(AppConfiguration configuration, ILogger logger) SmartIotConnector.TagWriteEvent += (s, e) => { }; - SmartIotConnector.ExceptionHandler += (s, e) => _logger.LogError(e.Exception, $"Exception caught: {e.Exception.Message}"); + SmartIotConnector.ExceptionHandler += (s, e) => _logger.LogError(e.Exception, "Exception caught: {message}", e.Exception.Message); SmartIotConnector.Starting += (s, e) => _logger.LogInformation("SmartIOT.Connector starting.."); SmartIotConnector.Started += (s, e) => _logger.LogInformation("SmartIOT.Connector started. Press Ctrl-C for graceful stop."); SmartIotConnector.Stopping += (s, e) => _logger.LogInformation("SmartIOT.Connector stopping.."); diff --git a/Apps/SmartIOT.Connector.ConsoleApp/SmartIOT.Connector.ConsoleApp.csproj b/Apps/SmartIOT.Connector.ConsoleApp/SmartIOT.Connector.ConsoleApp.csproj index fda75bd..50fb3a8 100644 --- a/Apps/SmartIOT.Connector.ConsoleApp/SmartIOT.Connector.ConsoleApp.csproj +++ b/Apps/SmartIOT.Connector.ConsoleApp/SmartIOT.Connector.ConsoleApp.csproj @@ -38,6 +38,7 @@ + diff --git a/Apps/SmartIOT.Connector.ConsoleApp/smartiot-config.json b/Apps/SmartIOT.Connector.ConsoleApp/smartiot-config.json index 9224147..f00f7da 100644 --- a/Apps/SmartIOT.Connector.ConsoleApp/smartiot-config.json +++ b/Apps/SmartIOT.Connector.ConsoleApp/smartiot-config.json @@ -40,7 +40,7 @@ } }, "PrometheusConfiguration": { - "HostName": "\u002B", + "HostName": "+", "Port": 0, "Url": "metrics/", "MetricsPrefix": "smartiot_connector", diff --git a/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj b/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj index 3e83512..5d02069 100644 --- a/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj +++ b/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj @@ -10,7 +10,7 @@ https://github.com/luca-domenichini/SmartIOT.Connector https://github.com/luca-domenichini/SmartIOT.Connector.git git - iot;mqtt;scheduler;connector;automation;snap7;s7net;azure;automation;siemens;plc;s7300;s71200;s71500 + iot;mqtt;scheduler;connector;automation;snap7;s7net;azure;automation;siemens;plc;s7300;s71200;s71500;modbus MIT Luca Domenichini 1.0.0.0 diff --git a/Devices/SmartIOT.Connector.Plc.S7Net/S7NetDriver.cs b/Devices/SmartIOT.Connector.Plc.S7Net/S7NetDriver.cs index 4615dc1..1d10ac6 100644 --- a/Devices/SmartIOT.Connector.Plc.S7Net/S7NetDriver.cs +++ b/Devices/SmartIOT.Connector.Plc.S7Net/S7NetDriver.cs @@ -14,6 +14,16 @@ public S7NetDriver(S7NetPlc plc) Device = plc; } + public int StartInterface() + { + return 0; + } + + public int StopInterface() + { + return 0; + } + public int Connect(Device device) { try @@ -50,16 +60,6 @@ public int Disconnect(Device device) } } - public string GetErrorMessage(int errorNumber) - { - return $"Error {errorNumber}"; - } - - public string GetDeviceDescription(Device device) - { - return device.Name; - } - public int ReadTag(Device device, Tag tag, byte[] data, int startOffset, int length) { try @@ -80,16 +80,6 @@ public int ReadTag(Device device, Tag tag, byte[] data, int startOffset, int len } } - public int StartInterface() - { - return 0; - } - - public int StopInterface() - { - return 0; - } - public int WriteTag(Device device, Tag tag, byte[] data, int startOffset, int length) { try @@ -110,4 +100,14 @@ public int WriteTag(Device device, Tag tag, byte[] data, int startOffset, int le throw new DeviceDriverException(ex.GetErrorMessage(), ex); } } + + public string GetErrorMessage(int errorNumber) + { + return $"Error {errorNumber}"; + } + + public string GetDeviceDescription(Device device) + { + return device.Name; + } } diff --git a/Devices/SmartIOT.Connector.Plc.S7Net/S7NetPlcConfiguration.cs b/Devices/SmartIOT.Connector.Plc.S7Net/S7NetPlcConfiguration.cs index 53ee1eb..647b958 100644 --- a/Devices/SmartIOT.Connector.Plc.S7Net/S7NetPlcConfiguration.cs +++ b/Devices/SmartIOT.Connector.Plc.S7Net/S7NetPlcConfiguration.cs @@ -6,11 +6,11 @@ namespace SmartIOT.Connector.Plc.S7Net; public class S7NetPlcConfiguration : DeviceConfiguration { - internal CpuType CpuType { get; init; } - internal string IpAddress { get; init; } - internal int? Port { get; init; } - internal short Rack { get; init; } - internal short Slot { get; init; } + public CpuType CpuType { get; } + public string IpAddress { get; } + public int? Port { get; } + public short Rack { get; } + public short Slot { get; } public S7NetPlcConfiguration(DeviceConfiguration configuration) : base(configuration) { diff --git a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs index e1161c3..b04bf11 100644 --- a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs +++ b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs @@ -16,6 +16,16 @@ public Snap7Driver(Snap7Plc plc) Device = plc; } + public int StartInterface() + { + return 0; + } + + public int StopInterface() + { + return 0; + } + public int Connect(Device device) { lock (device) @@ -34,6 +44,31 @@ public int Disconnect(Device device) } } + public int ReadTag(Device device, Tag tag, byte[] data, int startOffset, int length) + { + Snap7Plc p = (Snap7Plc)device; + + var bytes = new byte[length]; + + int ret = p.ReadBytes(tag.TagId, startOffset, bytes, length); + if (ret != 0) + return ret; + + Array.Copy(bytes, 0, data, startOffset - tag.ByteOffset, length); + + return 0; + } + + public int WriteTag(Device device, Tag tag, byte[] data, int startOffset, int length) + { + byte[] bytes = new byte[length]; + Array.Copy(data, startOffset - tag.ByteOffset, bytes, 0, length); + + Snap7Plc p = (Snap7Plc)device; + + return p.WriteBytes(tag.TagId, startOffset, bytes); + } + public string GetErrorMessage(int errorNumber) { return S7Client.ErrorText(errorNumber); @@ -69,39 +104,4 @@ public string GetDeviceDescription(Device device) return "PLC not connected"; } } - - public int ReadTag(Device device, Tag tag, byte[] data, int startOffset, int length) - { - Snap7Plc p = (Snap7Plc)device; - - var bytes = new byte[length]; - - int ret = p.ReadBytes(tag.TagId, startOffset, bytes, length); - if (ret != 0) - return ret; - - Array.Copy(bytes, 0, data, startOffset - tag.ByteOffset, length); - - return 0; - } - - public int StartInterface() - { - return 0; - } - - public int StopInterface() - { - return 0; - } - - public int WriteTag(Device device, Tag tag, byte[] data, int startOffset, int length) - { - byte[] bytes = new byte[length]; - Array.Copy(data, startOffset - tag.ByteOffset, bytes, 0, length); - - Snap7Plc p = (Snap7Plc)device; - - return p.WriteBytes(tag.TagId, startOffset, bytes); - } } diff --git a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Plc.cs b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Plc.cs index c4d77cd..eecbad3 100644 --- a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Plc.cs +++ b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Plc.cs @@ -7,7 +7,7 @@ public class Snap7Plc : Core.Model.Device { private static readonly Regex RegexDB = new Regex(@"^DB(?[0-9]*)$"); - public S7Client S7Client { get; init; } + public S7Client S7Client { get; } public new Snap7PlcConfiguration Configuration => (Snap7PlcConfiguration)base.Configuration; public bool IsConnected => S7Client.Connected; diff --git a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7PlcConfiguration.cs b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7PlcConfiguration.cs index 98ebe33..6095bde 100644 --- a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7PlcConfiguration.cs +++ b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7PlcConfiguration.cs @@ -5,10 +5,10 @@ namespace SmartIOT.Connector.Plc.Snap7; public class Snap7PlcConfiguration : DeviceConfiguration { - internal string IpAddress { get; init; } - internal short Rack { get; init; } - internal short Slot { get; init; } - internal S7ConnectionType ConnectionType { get; init; } + public string IpAddress { get; } + public short Rack { get; } + public short Slot { get; } + public S7ConnectionType ConnectionType { get; } public Snap7PlcConfiguration(DeviceConfiguration configuration) : base(configuration) { diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/README.md b/Devices/SmartIOT.Connector.Plc.SnapModBus/README.md new file mode 100644 index 0000000..495e462 --- /dev/null +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/README.md @@ -0,0 +1,13 @@ +# SnapModBus Device configuration + +This project provides a bridge to make SmartIOT.Connector able to use the [SnapModBus library](https://snapmodbus.sourceforge.io/) by Dave Nardella to connect to a device using ModBus protocol, and to exchange data defined in its registers. + +The supported connection string is as follows (square brackets for optional parameters): + +```text +snapmodbus://Ip=;Port=;NodeId=;swapBytes= +``` + +Paramters `Port`, `SwapBytes` and `NodeId` are optional. + +The `TagId`s are currently ignored, since only the registers are read and written. diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SmartIOT.Connector.Plc.SnapModBus.csproj b/Devices/SmartIOT.Connector.Plc.SnapModBus/SmartIOT.Connector.Plc.SnapModBus.csproj new file mode 100644 index 0000000..8d94dd2 --- /dev/null +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SmartIOT.Connector.Plc.SnapModBus.csproj @@ -0,0 +1,29 @@ + + + + net6.0;net8.0 + enable + enable + Luca Domenichini + SnapModbud device library for SmartIOT.Connector Core Library + This package implements the device bridge to connect to plcs using ModBus protocol + https://github.com/luca-domenichini/SmartIOT.Connector + https://github.com/luca-domenichini/SmartIOT.Connector.git + git + iot;mqtt;scheduler;connector;automation;snap7;s7net;azure;automation;siemens;plc;s7300;s71200;s71500;modbus + MIT + Luca Domenichini + 1.0.0.0 + + + + + + + + + Always + + + + diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapMB.net.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapMB.net.cs new file mode 100644 index 0000000..a4d4cdc --- /dev/null +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapMB.net.cs @@ -0,0 +1,872 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; +using System.Net; +using System.Reflection.Metadata; + +namespace SnapModbus +{ + #region [SnapModbus Constants and types] + + public class MBConsts + { + public static readonly int ProtoTCP = 0; // Modbus/TCP + public static readonly int ProtoUDP = 1; // Modbus/TCP using UDP as transport + public static readonly int ProtoRTUOverTCP = 2; // Modbus RTU wrapped in a TCP Packet + public static readonly int ProtoRTUOverUDP = 3; // Modbus RTU wrapped in a UDP Packet + + public static readonly int FormatRTU = 0; // Serial RTU + public static readonly int FormatASC = 1; // Serial ASCII + + public static readonly int FlowNONE = 0; // No control flow + public static readonly int FlowRTSCTS = 1; // RTS/CTS control flow + + // Callbacks Actions + public static readonly int cbActionRead = 0; + + public static readonly int cbActionWrite = 1; + + public static readonly int PacketLog_NONE = 0; + public static readonly int PacketLog_IN = 1; + public static readonly int PacketLog_OUT = 2; + public static readonly int PacketLog_BOTH = 3; + + public static readonly int bkSnd = 0; + public static readonly int bkRcv = 1; + + // Area ID, see xxxdev_RegisterAreaa() + public static readonly int mbaDiscreteInputs = 0; + + public static readonly int mbaCoils = 1; + public static readonly int mbaInputRegisters = 2; + public static readonly int mbaHoldingRegisters = 3; + + public static readonly int mbNoError = 0; + + // Callbacks Selectors, see xxxdev_RegisterCallback() + public static readonly int cbkDeviceEvent = 0; + + public static readonly int cbkPacketLog = 1; + public static readonly int cbkDiscreteInputs = 2; + public static readonly int cbkCoils = 3; + public static readonly int cbkInputRegisters = 4; + public static readonly int cbkHoldingRegisters = 5; + public static readonly int cbkReadWriteRegisters = 6; + public static readonly int cbkMaskRegister = 7; + public static readonly int cbkFileRecord = 8; + public static readonly int cbkExceptionStatus = 9; + public static readonly int cbkDiagnostics = 10; + public static readonly int cbkGetCommEventCounter = 11; + public static readonly int cbkGetCommEventLog = 12; + public static readonly int cbkReportServerID = 13; + public static readonly int cbkReadFIFOQueue = 14; + public static readonly int cbkEncapsulatedIT = 15; + public static readonly int cbkUsrFunction = 16; + public static readonly int cbkPassthrough = 17; + + // Parameter Selectors see xxxxxx_SetDeviceParam() + public static readonly int par_TCP_UDP_Port = 1; + + public static readonly int par_DeviceID = 2; + public static readonly int par_TcpPersistence = 3; + public static readonly int par_DisconnectOnError = 4; + public static readonly int par_SendTimeout = 5; + public static readonly int par_SerialFormat = 6; + public static readonly int par_AutoTimeout = 7; + public static readonly int par_AutoTimeLimitMin = 8; + public static readonly int par_FixedTimeout = 9; + public static readonly int par_BaseAddress = 10; + public static readonly int par_DevPeerListMode = 11; + public static readonly int par_PacketLog = 12; + public static readonly int par_InterframeDelay = 13; + public static readonly int par_WorkInterval = 14; + public static readonly int par_AllowSerFunOnEth = 15; + public static readonly int par_MaxRetries = 16; + public static readonly int par_DisconnectTimeout = 17; + public static readonly int par_AttemptSleep = 18; + public static readonly int par_DevicePassthrough = 19; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DeviceEvent + { + public IntPtr EvtTime; // It's platform dependent (32 or 64 bit) + public Int32 EvtSender; + public UInt32 EvtCode; + public ushort EvtRetCode; + public ushort EvtParam1; + public ushort EvtParam2; + public ushort EvtParam3; + public ushort EvtParam4; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct XOBJECT + { + public UIntPtr Object; + public UIntPtr Selector; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DeviceStatus + { + public Int32 LastError; + public Int32 Status; + public Int32 Connected; + public UInt32 JobTime; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DeviceInfo + { + public Int32 Running; + public Int32 ClientsCount; // only for TCP + public Int32 ClientsBlocked; // only for TCP/UDP + public Int32 LastError; + } + + public static readonly int MsgTextLen = 512; + + // Using Mono under Linux you should modify snapmb.dll to libsnapmb.so + public const string SnapLibName = "snapmb.dll"; + } + + #endregion [SnapModbus Constants and types] + + #region [Library Inports] + + internal class Externals + { + #region[POLYMORPHIC BROKER WRAPPERS] + + [DllImport(MBConsts.SnapLibName)] + public static extern void broker_CreateFieldController(ref MBConsts.XOBJECT Broker); + + [DllImport(MBConsts.SnapLibName)] + public static extern void broker_CreateEthernetClient(ref MBConsts.XOBJECT Broker, int Proto, [MarshalAs(UnmanagedType.LPStr)] string Address, int Port); + + [DllImport(MBConsts.SnapLibName)] + public static extern void broker_CreateSerialClient(ref MBConsts.XOBJECT Broker, int Format, [MarshalAs(UnmanagedType.LPStr)] string PortName, int BaudRate, byte Parity, int DataBits, int Stops, int FLow); + + [DllImport(MBConsts.SnapLibName)] + public static extern void broker_Destroy(ref MBConsts.XOBJECT Broker); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_Connect(ref MBConsts.XOBJECT Broker); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_Disconnect(ref MBConsts.XOBJECT Broker); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_AddControllerNetDevice(ref MBConsts.XOBJECT Broker, int Proto, byte DeviceID, [MarshalAs(UnmanagedType.LPStr)] string Address, int Port); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_AddControllerSerDevice(ref MBConsts.XOBJECT Broker, int Format, byte DeviceID, [MarshalAs(UnmanagedType.LPStr)] string PortName, int BaudRate, byte Parity, int DataBits, int Stops, int FLow); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_SetLocalParam(ref MBConsts.XOBJECT Broker, byte DeviceID, int ParamIndex, int Value); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_SetRemoteDeviceParam(ref MBConsts.XOBJECT Broker, byte DeviceID, int ParamIndex, int Value); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_GetIOBuffer(ref MBConsts.XOBJECT Broker, byte DeviceID, int BufferKind, byte[] Data); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_GetDeviceStatus(ref MBConsts.XOBJECT Broker, byte DeviceID, ref MBConsts.DeviceStatus Status); + + // Modbus Functions + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadHoldingRegisters(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, ushort[] Data); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadHoldingRegisters(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_WriteMultipleRegisters(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, ushort[] Data); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_WriteMultipleRegisters(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadCoils(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, byte[] Data); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadCoils(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadDiscreteInputs(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, byte[] Data); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadDiscreteInputs(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadInputRegisters(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, ushort[] Data); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadInputRegisters(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_WriteSingleCoil(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Value); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_WriteSingleRegister(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Value); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadWriteMultipleRegisters(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort RDAddress, ushort RDAmount, ushort WRAddress, ushort WRAmount, ushort[] pRDUsrData, ushort[] pWRUsrData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadWriteMultipleRegisters(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort RDAddress, ushort RDAmount, ushort WRAddress, ushort WRAmount, IntPtr pRDUsrData, IntPtr pWRUsrData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_WriteMultipleCoils(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, byte[] Data); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_WriteMultipleCoils(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_MaskWriteRegister(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ushort AND_Mask, ushort OR_Mask); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadFileRecord(ref MBConsts.XOBJECT Broker, byte DeviceID, byte RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, ushort[] RecData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadFileRecord(ref MBConsts.XOBJECT Broker, byte DeviceID, byte RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, IntPtr pRecData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_WriteFileRecord(ref MBConsts.XOBJECT Broker, byte DeviceID, byte RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, ushort[] RecData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_WriteFileRecord(ref MBConsts.XOBJECT Broker, byte DeviceID, byte RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, IntPtr pRecData); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadFIFOQueue(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ref ushort FifoCount, ushort[] FIFO); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadFIFOQueue(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort Address, ref ushort FifoCount, IntPtr pFIFO); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReadExceptionStatus(ref MBConsts.XOBJECT Broker, byte DeviceID, ref byte Data); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_Diagnostics(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort SubFunction, ushort[] SendData, ushort[] RecvData, ushort ItemsToSend, ref ushort ItemsReceived); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_Diagnostics(ref MBConsts.XOBJECT Broker, byte DeviceID, ushort SubFunction, IntPtr pSendData, IntPtr pRecvData, ushort ItemsToSend, ref ushort ItemsReceived); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_GetCommEventCounter(ref MBConsts.XOBJECT Broker, byte DeviceID, ref ushort Status, ref ushort EventCount); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_GetCommEventLog(ref MBConsts.XOBJECT Broker, byte DeviceID, ref ushort Status, ref ushort EventCount, ref ushort MessageCount, ref ushort NumItems, byte[] Events); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_GetCommEventLog(ref MBConsts.XOBJECT Broker, byte DeviceID, ref ushort Status, ref ushort EventCount, ref ushort MessageCount, ref ushort NumItems, IntPtr pEvents); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReportServerID(ref MBConsts.XOBJECT Broker, byte DeviceID, byte[] Data, ref int DataSize); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ReportServerID(ref MBConsts.XOBJECT Broker, byte DeviceID, IntPtr pUsrData, ref int DataSize); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ExecuteMEIFunction(ref MBConsts.XOBJECT Broker, byte DeviceID, byte MEI_Type, byte[] WRUsrData, ushort WRSize, byte[] RDUsrData, ref ushort RDSize); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_ExecuteMEIFunction(ref MBConsts.XOBJECT Broker, byte DeviceID, byte MEI_Type, IntPtr pWRUsrData, ushort WRSize, IntPtr pRDUsrData, ref ushort RDSize); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_CustomFunctionRequest(ref MBConsts.XOBJECT Broker, byte DeviceID, byte UsrFunction, byte[] UsrPDUWrite, ushort SizePDUWrite, byte[] UsrPDURead, ref ushort SizePDURead, ushort SizePDUExpected); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_CustomFunctionRequest(ref MBConsts.XOBJECT Broker, byte DeviceID, byte UsrFunction, IntPtr pUsrPDUWrite, ushort SizePDUWrite, IntPtr pUsrPDURead, ref ushort SizePDURead, ushort SizePDUExpected); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_RawRequest(ref MBConsts.XOBJECT Broker, byte DeviceID, byte[] UsrPDUWrite, ushort SizePDUWrite, byte[] UsrPDURead, ref ushort SizePDURead, ushort SizePDUExpected); + + [DllImport(MBConsts.SnapLibName)] + public static extern int broker_RawRequest(ref MBConsts.XOBJECT Broker, byte DeviceID, IntPtr pUsrPDUWrite, ushort SizePDUWrite, IntPtr pUsrPDURead, ref ushort SizePDURead, ushort SizePDUExpected); + + #endregion [Library Inports] + + #region[POLYMORPHIC DEVICE WRAPPERS] + + [DllImport(MBConsts.SnapLibName)] + public static extern void device_CreateEthernet(ref MBConsts.XOBJECT Device, int Proto, byte DeviceID, [MarshalAs(UnmanagedType.LPStr)] string Address, int Port); + + [DllImport(MBConsts.SnapLibName)] + public static extern void device_CreateSerial(ref MBConsts.XOBJECT Device, int Format, byte DeviceID, [MarshalAs(UnmanagedType.LPStr)] string PortName, int BaudRate, byte Parity, int DataBits, int Stops, int Flow); + + [DllImport(MBConsts.SnapLibName)] + public static extern void device_Destroy(ref MBConsts.XOBJECT Device); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_SetParam(ref MBConsts.XOBJECT Device, int ParamIndex, int Value); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_BindEthernet(ref MBConsts.XOBJECT Device, [MarshalAs(UnmanagedType.LPStr)] string Address, int Port); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_BindSerial(ref MBConsts.XOBJECT Device, [MarshalAs(UnmanagedType.LPStr)] string PortName, int BaudRate, byte Parity, int DataBits, int Stops, int Flow); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_SetUserFunction(ref MBConsts.XOBJECT Device, byte FunctionID, int Value); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_Start(ref MBConsts.XOBJECT Device); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_Stop(ref MBConsts.XOBJECT Device); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_AddPeer(ref MBConsts.XOBJECT Device, [MarshalAs(UnmanagedType.LPStr)] string Address); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_RegisterArea(ref MBConsts.XOBJECT Device, int AreaID, IntPtr Data, UInt16 Amount); + + [DllImport(MBConsts.SnapLibName, EntryPoint = "device_CopyArea")] + public static extern int device_CopyArea_b(ref MBConsts.XOBJECT Device, int AreaID, UInt16 Address, UInt16 Amount, byte[] Data, int CopyMode); + + [DllImport(MBConsts.SnapLibName, EntryPoint = "device_CopyArea")] + public static extern int device_CopyArea_w(ref MBConsts.XOBJECT Device, int AreaID, UInt16 Address, UInt16 Amount, ushort[] Data, int CopyMode); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_LockArea(ref MBConsts.XOBJECT Device, int AreaID); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_UnlockArea(ref MBConsts.XOBJECT Device, int AreaID); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_RegisterCallback(ref MBConsts.XOBJECT Device, int CallbackID, IntPtr cbRequest, IntPtr usrPtr); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_PickEvent(ref MBConsts.XOBJECT Device, ref MBConsts.DeviceEvent Event); + + [DllImport(MBConsts.SnapLibName, CharSet = CharSet.Ansi)] + public static extern int device_PickEventAsText(ref MBConsts.XOBJECT Device, StringBuilder TextEvent, int TextSize); + + [DllImport(MBConsts.SnapLibName)] + public static extern int device_GetDeviceInfo(ref MBConsts.XOBJECT Device, ref MBConsts.DeviceInfo Info); + + #endregion + + #region [COMMON WRAPPERS] + + [DllImport(MBConsts.SnapLibName, CharSet = CharSet.Ansi)] + public static extern IntPtr ErrorText(int Error, StringBuilder TextError, int TextSize); + + [DllImport(MBConsts.SnapLibName, CharSet = CharSet.Ansi)] + public static extern IntPtr EventText(ref MBConsts.DeviceEvent Event, StringBuilder TextEvent, int TextSize); + + #endregion + } + + #endregion + + #region[SnapMBBroker] + + public class SnapMBBroker + { + private MBConsts.XOBJECT Broker; + + public SnapMBBroker() + { + Externals.broker_CreateFieldController(ref Broker); + } + + // Ethernet Client ctor + public SnapMBBroker(int Proto, string Address, int Port) + { + Externals.broker_CreateEthernetClient(ref Broker, Proto, Address, Port); + } + + // Serial Client ctor + public SnapMBBroker(int Format, string PortName, int BaudRate, char Parity, int DataBits, int Stops, int Flow) + { + Externals.broker_CreateSerialClient(ref Broker, Format, PortName, BaudRate, Convert.ToByte(Parity), DataBits, Stops, Flow); + } + + // To avoid Objects destruction in C#, we can change the behaviour of our Broker calling ChangeTo() method + public void ChangeTo() + { + Externals.broker_Destroy(ref Broker); + Externals.broker_CreateFieldController(ref Broker); + } + + public void ChangeTo(int Proto, string Address, int Port) + { + Externals.broker_Destroy(ref Broker); + Externals.broker_CreateEthernetClient(ref Broker, Proto, Address, Port); + } + + public void ChangeTo(int Format, string PortName, int BaudRate, char Parity, int DataBits, int Stops, int Flow) + { + Externals.broker_Destroy(ref Broker); + Externals.broker_CreateSerialClient(ref Broker, Format, PortName, BaudRate, Convert.ToByte(Parity), DataBits, Stops, Flow); + } + + // Common dtor + ~SnapMBBroker() + { + Externals.broker_Destroy(ref Broker); + } + + public int Connect() + { + return Externals.broker_Connect(ref Broker); + } + + public int Disconnect() + { + return Externals.broker_Disconnect(ref Broker); + } + + public int AddDevice(int Proto, byte DeviceID, string Address, int Port) + { + return Externals.broker_AddControllerNetDevice(ref Broker, Proto, DeviceID, Address, Port); + } + + public int AddDevice(int Format, byte DeviceID, string PortName, int BaudRate, char Parity, int DataBits, int Stops, int Flow) + { + return Externals.broker_AddControllerSerDevice(ref Broker, Format, DeviceID, PortName, BaudRate, Convert.ToByte(Parity), DataBits, Stops, Flow); + } + + public int SetLocalParam(byte DeviceID, int ParamIndex, int Value) + { + return Externals.broker_SetLocalParam(ref Broker, DeviceID, ParamIndex, Value); + } + + public int SetRemoteDeviceParam(byte DeviceID, int ParamIndex, int Value) + { + return Externals.broker_SetRemoteDeviceParam(ref Broker, DeviceID, ParamIndex, Value); + } + + public int GetIOBuffer(byte[] Data, int BufferKind) + { + return Externals.broker_GetIOBuffer(ref Broker, 0, BufferKind, Data); + } + + public int GetIOBuffer(byte DeviceID, byte[] Data, int BufferKind) + { + return Externals.broker_GetIOBuffer(ref Broker, DeviceID, BufferKind, Data); + } + + public int GetDeviceStatus(byte DeviceID, ref MBConsts.DeviceStatus Status) + { + return Externals.broker_GetDeviceStatus(ref Broker, DeviceID, ref Status); + } + + public int ReadHoldingRegisters(byte DeviceID, ushort Address, ushort Amount, ushort[] Data) + { + return Externals.broker_ReadHoldingRegisters(ref Broker, DeviceID, Address, Amount, Data); + } + + public int ReadHoldingRegisters(byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData) + { + return Externals.broker_ReadHoldingRegisters(ref Broker, DeviceID, Address, Amount, pUsrData); + } + + public int WriteMultipleRegisters(byte DeviceID, ushort Address, ushort Amount, ushort[] Data) + { + return Externals.broker_WriteMultipleRegisters(ref Broker, DeviceID, Address, Amount, Data); + } + + public int WriteMultipleRegisters(byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData) + { + return Externals.broker_WriteMultipleRegisters(ref Broker, DeviceID, Address, Amount, pUsrData); + } + + public int ReadCoils(byte DeviceID, ushort Address, ushort Amount, byte[] Data) + { + return Externals.broker_ReadCoils(ref Broker, DeviceID, Address, Amount, Data); + } + + public int ReadCoils(byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData) + { + return Externals.broker_ReadCoils(ref Broker, DeviceID, Address, Amount, pUsrData); + } + + public int ReadDiscreteInputs(byte DeviceID, ushort Address, ushort Amount, byte[] Data) + { + return Externals.broker_ReadDiscreteInputs(ref Broker, DeviceID, Address, Amount, Data); + } + + public int ReadDiscreteInputs(byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData) + { + return Externals.broker_ReadDiscreteInputs(ref Broker, DeviceID, Address, Amount, pUsrData); + } + + public int ReadInputRegisters(byte DeviceID, ushort Address, ushort Amount, ushort[] Data) + { + return Externals.broker_ReadInputRegisters(ref Broker, DeviceID, Address, Amount, Data); + } + + public int ReadInputRegisters(byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData) + { + return Externals.broker_ReadInputRegisters(ref Broker, DeviceID, Address, Amount, pUsrData); + } + + public int WriteSingleCoil(byte DeviceID, ushort Address, bool Value) + { + return Externals.broker_WriteSingleCoil(ref Broker, DeviceID, Address, Convert.ToUInt16(Value)); + } + + public int WriteSingleRegister(byte DeviceID, ushort Address, ushort Value) + { + return Externals.broker_WriteSingleRegister(ref Broker, DeviceID, Address, Value); + } + + public int ReadWriteMultipleRegisters(byte DeviceID, ushort RDAddress, ushort RDAmount, ushort WRAddress, ushort WRAmount, ushort[] RDUsrData, ushort[] WRUsrData) + { + return Externals.broker_ReadWriteMultipleRegisters(ref Broker, DeviceID, RDAddress, RDAmount, WRAddress, WRAmount, RDUsrData, WRUsrData); + } + + public int ReadWriteMultipleRegisters(byte DeviceID, ushort RDAddress, ushort RDAmount, ushort WRAddress, ushort WRAmount, IntPtr pRDUsrData, IntPtr pWRUsrData) + { + return Externals.broker_ReadWriteMultipleRegisters(ref Broker, DeviceID, RDAddress, RDAmount, WRAddress, WRAmount, pRDUsrData, pWRUsrData); + } + + public int WriteMultipleCoils(byte DeviceID, ushort Address, ushort Amount, byte[] Data) + { + return Externals.broker_WriteMultipleCoils(ref Broker, DeviceID, Address, Amount, Data); + } + + public int WriteMultipleCoils(byte DeviceID, ushort Address, ushort Amount, IntPtr pUsrData) + { + return Externals.broker_WriteMultipleCoils(ref Broker, DeviceID, Address, Amount, pUsrData); + } + + public int MaskWriteRegister(byte DeviceID, ushort Address, ushort AND_Mask, ushort OR_Mask) + { + return Externals.broker_MaskWriteRegister(ref Broker, DeviceID, Address, AND_Mask, OR_Mask); + } + + public int ReadFileRecord(byte DeviceID, byte RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, ushort[] RecData) + { + return Externals.broker_ReadFileRecord(ref Broker, DeviceID, RefType, FileNumber, RecNumber, RegsAmount, RecData); + } + + public int ReadFileRecord(byte DeviceID, byte RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, IntPtr pRecData) + { + return Externals.broker_ReadFileRecord(ref Broker, DeviceID, RefType, FileNumber, RecNumber, RegsAmount, pRecData); + } + + public int WriteFileRecord(byte DeviceID, byte RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, ushort[] RecData) + { + return Externals.broker_WriteFileRecord(ref Broker, DeviceID, RefType, FileNumber, RecNumber, RegsAmount, RecData); + } + + public int WriteFileRecord(byte DeviceID, byte RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, IntPtr pRecData) + { + return Externals.broker_WriteFileRecord(ref Broker, DeviceID, RefType, FileNumber, RecNumber, RegsAmount, pRecData); + } + + public int ReadFIFOQueue(byte DeviceID, ushort Address, ref ushort FifoCount, ushort[] FIFO) + { + return Externals.broker_ReadFIFOQueue(ref Broker, DeviceID, Address, ref FifoCount, FIFO); + } + + public int ReadFIFOQueue(byte DeviceID, ushort Address, ref ushort FifoCount, IntPtr pFIFO) + { + return Externals.broker_ReadFIFOQueue(ref Broker, DeviceID, Address, ref FifoCount, pFIFO); + } + + public int ReadExceptionStatus(byte DeviceID, ref byte Data) + { + return Externals.broker_ReadExceptionStatus(ref Broker, DeviceID, ref Data); + } + + public int Diagnostics(byte DeviceID, ushort SubFunction, ushort[] SendData, ushort[] RecvData, ushort ItemsToSend, ref ushort ItemsRecvd) + { + return Externals.broker_Diagnostics(ref Broker, DeviceID, SubFunction, SendData, RecvData, ItemsToSend, ref ItemsRecvd); + } + + public int Diagnostics(byte DeviceID, ushort SubFunction, IntPtr pSendData, IntPtr pRecvData, ushort ItemsToSend, ref ushort ItemsRecvd) + { + return Externals.broker_Diagnostics(ref Broker, DeviceID, SubFunction, pSendData, pRecvData, ItemsToSend, ref ItemsRecvd); + } + + public int GetCommEventCounter(byte DeviceID, ref ushort Status, ref ushort EventCount) + { + return Externals.broker_GetCommEventCounter(ref Broker, DeviceID, ref Status, ref EventCount); + } + + public int GetCommEventLog(byte DeviceID, ref ushort Status, ref ushort EventCount, ref ushort MessageCount, ref ushort NumItems, byte[] Events) + { + return Externals.broker_GetCommEventLog(ref Broker, DeviceID, ref Status, ref EventCount, ref MessageCount, ref NumItems, Events); + } + + public int GetCommEventLog(byte DeviceID, ref ushort Status, ref ushort EventCount, ref ushort MessageCount, ref ushort NumItems, IntPtr pEvents) + { + return Externals.broker_GetCommEventLog(ref Broker, DeviceID, ref Status, ref EventCount, ref MessageCount, ref NumItems, pEvents); + } + + public int ReportServerID(byte DeviceID, byte[] UsrData, ref int DataSize) + { + return Externals.broker_ReportServerID(ref Broker, DeviceID, UsrData, ref DataSize); + } + + public int ReportServerID(byte DeviceID, IntPtr pUsrData, ref int DataSize) + { + return Externals.broker_ReportServerID(ref Broker, DeviceID, pUsrData, ref DataSize); + } + + public int ExecuteMEIFunction(byte DeviceID, byte MEI_Type, byte[] WRUsrData, ushort WRSize, byte[] RDUsrData, ref ushort RDSize) + { + return Externals.broker_ExecuteMEIFunction(ref Broker, DeviceID, MEI_Type, WRUsrData, WRSize, RDUsrData, ref RDSize); + } + + public int ExecuteMEIFunction(byte DeviceID, byte MEI_Type, IntPtr pWRUsrData, ushort WRSize, IntPtr pRDUsrData, ref ushort RDSize) + { + return Externals.broker_ExecuteMEIFunction(ref Broker, DeviceID, MEI_Type, pWRUsrData, WRSize, pRDUsrData, ref RDSize); + } + + public int CustomFunctionRequest(byte DeviceID, byte UsrFunction, byte[] UsrPDUWrite, ushort SizePDUWrite, byte[] UsrPDURead, ref ushort SizePDURead, ushort SizePDUExpected) + { + return Externals.broker_CustomFunctionRequest(ref Broker, DeviceID, UsrFunction, UsrPDUWrite, SizePDUWrite, UsrPDURead, ref SizePDURead, SizePDUExpected); + } + + public int CustomFunctionRequest(byte DeviceID, byte UsrFunction, IntPtr pUsrPDUWrite, ushort SizePDUWrite, IntPtr pUsrPDURead, ref ushort SizePDURead, ushort SizePDUExpected) + { + return Externals.broker_CustomFunctionRequest(ref Broker, DeviceID, UsrFunction, pUsrPDUWrite, SizePDUWrite, pUsrPDURead, ref SizePDURead, SizePDUExpected); + } + + public int RawRequestRequest(byte DeviceID, byte UsrFunction, byte[] UsrPDUWrite, ushort SizePDUWrite, byte[] UsrPDURead, ref ushort SizePDURead, ushort SizePDUExpected) + { + return Externals.broker_RawRequest(ref Broker, DeviceID, UsrPDUWrite, SizePDUWrite, UsrPDURead, ref SizePDURead, SizePDUExpected); + } + + public int RawRequestRequest(byte DeviceID, byte UsrFunction, IntPtr pUsrPDUWrite, ushort SizePDUWrite, IntPtr pUsrPDURead, ref ushort SizePDURead, ushort SizePDUExpected) + { + return Externals.broker_RawRequest(ref Broker, DeviceID, pUsrPDUWrite, SizePDUWrite, pUsrPDURead, ref SizePDURead, SizePDUExpected); + } + } + + #endregion + + #region[SnapMBDevice] + + // Callbacks + public delegate void TDeviceEvent(IntPtr usrPtr, ref MBConsts.DeviceEvent Event, int Size); + + public delegate void TPacketLog(IntPtr usrPtr, UInt32 Peer, int Direction, IntPtr Data, int Size); + + public delegate int TDiscreteInputsRequest(IntPtr usrPtr, ushort Address, ushort Amount, IntPtr Data); + + public delegate int TCoilsRequest(IntPtr usrPtr, int Action, ushort Address, ushort Amount, IntPtr Data); + + public delegate int TInputRegistersRequest(IntPtr usrPtr, ushort Address, ushort Amount, IntPtr Data); + + public delegate int THoldingRegistersRequest(IntPtr usrPtr, int Action, ushort Address, ushort Amount, IntPtr Data); + + public delegate int TReadWriteMultipleRegistersRequest(IntPtr usrPtr, ushort RDAddress, ushort RDAmount, IntPtr RDData, ushort WRAddress, ushort WRAmount, IntPtr WRData); + + public delegate int TMaskRegisterRequest(IntPtr usrPtr, ushort Address, ushort AND_Mask, ushort OR_Mask); + + public delegate int TFileRecordRequest(IntPtr usrPtr, int Action, ushort RefType, ushort FileNumber, ushort RecNumber, ushort RegsAmount, IntPtr Data); + + public delegate int TExceptionStatusRequest(IntPtr usrPtr, ref byte Status); + + public delegate int TDiagnosticsRequest(IntPtr usrPtr, ushort SubFunction, IntPtr RxItems, IntPtr TxItems, int ItemsSent, ref ushort ItemsRecvd); + + public delegate int TGetCommEventCounterRequest(IntPtr usrPtr, ref ushort Status, ref ushort EventCount); + + public delegate int TGetCommEventLogRequest(IntPtr usrPtr, ref ushort Status, ref ushort EventCount, ref ushort MessageCount, IntPtr Data, ref ushort EventsAmount); + + public delegate int TReportServerIDRequest(IntPtr usrPtr, IntPtr Data, ref ushort DataSize); + + public delegate int TReadFIFOQueueRequest(IntPtr usrPtr, ushort PtrAddress, IntPtr FIFOValues, ref ushort FifoCount); + + public delegate int TEncapsulatedIT(IntPtr usrPtr, byte MEI_Type, IntPtr MEI_DataReq, int ReqDataSize, IntPtr MEI_DataRes, ref ushort ResDataSize); + + public delegate int TUsrFunctionRequest(IntPtr usrPtr, byte Function, IntPtr RxPDU, int RxPDUSize, IntPtr TxPDU, ref ushort TxPDUSize); + + public delegate int TPassthroughRequest(IntPtr usrPtr, byte DeviceID, IntPtr RxPDU, int RxPDUSize, IntPtr TxPDU, ref ushort TxPDUSize); + + public class SnapMBDevice + { + private MBConsts.XOBJECT Device; + + protected GCHandle[] Areas = new GCHandle[4]; + + private void InternalDestroy() + { + if (Device.Object != UIntPtr.Zero) + Externals.device_Destroy(ref Device); + foreach (var Item in Areas) + if (Item.IsAllocated) + Item.Free(); + } + + /// + /// Warning : use this constructor only if don't know in advance the Device Type and + /// so you want to invoke ChangeTo() in a second time + /// + public SnapMBDevice() + { + Device.Object = UIntPtr.Zero; + Device.Selector = UIntPtr.Zero; + } + + public SnapMBDevice(int Proto, byte DeviceID, string Address, int Port) + { + Externals.device_CreateEthernet(ref Device, Proto, DeviceID, Address, Port); + } + + public SnapMBDevice(int Format, byte DeviceID, string PortName, int BaudRate, char Parity, int DataBits, int Stops, int Flow) + { + Externals.device_CreateSerial(ref Device, Format, DeviceID, PortName, BaudRate, Convert.ToByte(Parity), DataBits, Stops, Flow); + } + + ~SnapMBDevice() + { + InternalDestroy(); + } + + public bool Exists + { + get + { + return (Device.Object != UIntPtr.Zero); + } + } + + public void ChangeTo(int Proto, int DeviceID, string Address, int Port) + { + InternalDestroy(); + Externals.device_CreateEthernet(ref Device, Proto, (byte)DeviceID, Address, Port); + } + + public void ChangeTo(int Format, int DeviceID, string PortName, int BaudRate, char Parity, int DataBits, int Stops, int Flow) + { + InternalDestroy(); + Externals.device_CreateSerial(ref Device, Format, (byte)DeviceID, PortName, BaudRate, Convert.ToByte(Parity), DataBits, Stops, Flow); + } + + public int Bind(byte DeviceID, string Address, int Port) + { + return Externals.device_BindEthernet(ref Device, Address, Port); + } + + public int Bind(byte DeviceID, string PortName, int BaudRate, char Parity, int DataBits, int Stops, int Flow) + { + return Externals.device_BindSerial(ref Device, PortName, BaudRate, Convert.ToByte(Parity), DataBits, Stops, Flow); + } + + public int SetParam(int ParamIndex, int Value) + { + return Externals.device_SetParam(ref Device, ParamIndex, Value); + } + + public int SetUserFunction(byte FunctionID, bool Value) + { + return Externals.device_SetUserFunction(ref Device, FunctionID, Convert.ToInt32(Value)); + } + + public int Start() + { + return Externals.device_Start(ref Device); + } + + public int Stop() + { + return Externals.device_Stop(ref Device); + } + + public int AddPeer(string Address) + { + return Externals.device_AddPeer(ref Device, Address); + } + + //-------------------------------------------------------------------------------- + // Register Area + // We need a Pinned Object to avoid that the GarbageCollector deletes the Area + //-------------------------------------------------------------------------------- + public int RegisterArea(int AreaID, ref T Data, int Amount) + { + if (Areas[AreaID].IsAllocated) + Areas[AreaID].Free(); + Areas[AreaID] = GCHandle.Alloc(Data, GCHandleType.Pinned); + int Result = Externals.device_RegisterArea(ref Device, AreaID, Areas[AreaID].AddrOfPinnedObject(), (ushort)Amount); + if (Result != 0) + Areas[AreaID].Free(); + return Result; + } + + public int CopyArea(int AreaID, UInt16 Address, UInt16 Amount, byte[] Data, int CopyMode) + { + return Externals.device_CopyArea_b(ref Device, AreaID, Address, Amount, Data, CopyMode); + } + + public int CopyArea(int AreaID, UInt16 Address, UInt16 Amount, ushort[] Data, int CopyMode) + { + return Externals.device_CopyArea_w(ref Device, AreaID, Address, Amount, Data, CopyMode); + } + + public int LockArea(int AreaID) + { + return Externals.device_LockArea(ref Device, AreaID); + } + + public int UnlockArea(int AreaID) + { + return Externals.device_UnlockArea(ref Device, AreaID); + } + + public int RegisterCallback(int CallbackID, IntPtr cbRequest, IntPtr usrPtr) + { + return Externals.device_RegisterCallback(ref Device, CallbackID, cbRequest, usrPtr); + } + + public bool PickEvent(ref MBConsts.DeviceEvent Event) + { + return Externals.device_PickEvent(ref Device, ref Event) != 0; + } + + public bool PickEventAsText(ref string TextEvent) + { + StringBuilder Text = new StringBuilder(MBConsts.MsgTextLen); + int Result = Externals.device_PickEventAsText(ref Device, Text, MBConsts.MsgTextLen); + if (Result != 0) + TextEvent = Text.ToString(); + return Result != 0; + } + + public string PickEventAsText() + { + StringBuilder Text = new StringBuilder(MBConsts.MsgTextLen); + if (Externals.device_PickEventAsText(ref Device, Text, MBConsts.MsgTextLen) != 0) + return Text.ToString(); + else + return ""; + } + + public int GetDeviceInfo(ref MBConsts.DeviceInfo Info) + { + return Externals.device_GetDeviceInfo(ref Device, ref Info); + } + } + + #endregion + + #region [Utils] + + public static class MB + { + public static string EventText(ref MBConsts.DeviceEvent Event) + { + StringBuilder Text = new StringBuilder(MBConsts.MsgTextLen); + Externals.EventText(ref Event, Text, MBConsts.MsgTextLen); + return Text.ToString(); + } + + public static string ErrorText(int Error) + { + StringBuilder Text = new StringBuilder(MBConsts.MsgTextLen); + Externals.ErrorText(Error, Text, MBConsts.MsgTextLen); + return Text.ToString(); + } + } + + #endregion +} diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs new file mode 100644 index 0000000..82da63b --- /dev/null +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs @@ -0,0 +1,149 @@ +using SmartIOT.Connector.Core; +using SmartIOT.Connector.Core.Model; +using SnapModbus; +using System.Runtime.InteropServices; + +namespace SmartIOT.Connector.Plc.SnapModBus; + +public class SnapModBusDriver : IDeviceDriver +{ + public string Name => $"{nameof(SnapModBusDriver)}.{Device.Name}"; + public Device Device { get; } + + public SnapModBusDriver(SnapModBusNode node) + { + Device = node; + } + + public int StartInterface() + { + return 0; + } + + public int StopInterface() + { + return 0; + } + + public int Connect(Device device) + { + lock (device) + { + SnapModBusNode node = (SnapModBusNode)device; + return node.Connect(); + } + } + + public int Disconnect(Device device) + { + lock (device) + { + SnapModBusNode node = (SnapModBusNode)device; + return node.Disconnect(); + } + } + + public int ReadTag(Device device, Tag tag, byte[] data, int startOffset, int length) + { + SnapModBusNode node = (SnapModBusNode)device; + + // ModBus works with ushorts! We need to adapt length and startOffset accordingly + // bytes -> ushorts | byte slots -> ushort slots + // 0,1 -> 0,1 | [. ] -> [. ] + // 0,2 -> 0,1 | [.. ] -> [. ] + // 0,3 -> 0,2 | [... ] -> [.. ] + // 0,4 -> 0,2 | [.... ] -> [.. ] + // 0,5 -> 0,3 | [..... ] -> [...] + // 0,6 -> 0,3 | [......] -> [...] + // 1,1 -> 0,1 | [ . ] -> [. ] + // 1,2 -> 0,2 | [ .. ] -> [.. ] + // 1,3 -> 0,2 | [ ... ] -> [.. ] + // 1,4 -> 0,3 | [ .... ] -> [...] + // 1,5 -> 0,3 | [ .....] -> [...] + // 2,1 -> 1,1 | [ . ] -> [ . ] + // 2,2 -> 1,1 | [ .. ] -> [ . ] + // 2,3 -> 1,2 | [ ... ] -> [ ..] + // 2,4 -> 1,2 | [ ....] -> [ ..] + ushort address = (ushort)(startOffset / 2); + int endAddress = (startOffset + length - 1) / 2; + ushort amount = (ushort)(endAddress - address + 1); + + var words = new ushort[amount]; + + // address is 1-based + int ret = node.ReadRegisters((ushort)(address + 1), amount, words); + if (ret != 0) + return ret; + + if (node.Configuration.SwapBytes) + CopyArrayAndSwapBytes(words, startOffset % 2, data, startOffset - tag.ByteOffset, length); + else + Buffer.BlockCopy(words, startOffset % 2, data, startOffset - tag.ByteOffset, length); + + return 0; + } + + public int WriteTag(Device device, Tag tag, byte[] data, int startOffset, int length) + { + SnapModBusNode node = (SnapModBusNode)device; + + // ModBus works with ushorts! We need to adapt length and startOffset accordingly + // bytes -> ushorts | byte slots -> ushort slots + // 0,1 -> 0,1 | [. ] -> [. ] + // 0,2 -> 0,1 | [.. ] -> [. ] + // 0,3 -> 0,2 | [... ] -> [.. ] + // 0,4 -> 0,2 | [.... ] -> [.. ] + // 0,5 -> 0,3 | [..... ] -> [...] + // 0,6 -> 0,3 | [......] -> [...] + // 1,1 -> 0,1 | [ . ] -> [. ] + // 1,2 -> 0,2 | [ .. ] -> [.. ] + // 1,3 -> 0,2 | [ ... ] -> [.. ] + // 1,4 -> 0,3 | [ .... ] -> [...] + // 1,5 -> 0,3 | [ .....] -> [...] + // 2,1 -> 1,1 | [ . ] -> [ . ] + // 2,2 -> 1,1 | [ .. ] -> [ . ] + // 2,3 -> 1,2 | [ ... ] -> [ ..] + // 2,4 -> 1,2 | [ ....] -> [ ..] + ushort address = (ushort)(startOffset / 2); + int endAddress = (startOffset + length - 1) / 2; + ushort amount = (ushort)(endAddress - address + 1); + + int startRemainder = startOffset % 2; + int endRemainder = (startOffset + length) % 2; + + var words = new ushort[amount]; + if (node.Configuration.SwapBytes) + CopyArrayAndSwapBytes(data, startOffset - tag.ByteOffset - startRemainder, words, 0, length + startRemainder + endRemainder); + else + Buffer.BlockCopy(data, startOffset - tag.ByteOffset - startRemainder, words, 0, length + startRemainder + endRemainder); + + // address is 1-based + return node.WriteRegisters((ushort)(address + 1), words); + } + + private void CopyArrayAndSwapBytes(byte[] source, int srcIndex, ushort[] target, int targetIndex, int length) + { + for (int i = 0; i < length; i += 2) + { + target[targetIndex + i] = (ushort)((source[srcIndex + i] << 8) + source[srcIndex + i + 1]); + } + } + + private void CopyArrayAndSwapBytes(ushort[] source, int srcIndex, byte[] target, int targetIndex, int length) + { + for (int i = 0; i < length; i += 2) + { + var word = source[srcIndex + i / 2]; + + target[targetIndex + i] = (byte)(word >> 8); + target[targetIndex + i + 1] = (byte)(word); + } + } + + public string GetErrorMessage(int errorNumber) + { + return MB.ErrorText(errorNumber); + } + + public string GetDeviceDescription(Device device) => device.Name; +} diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriverFactory.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriverFactory.cs new file mode 100644 index 0000000..9626130 --- /dev/null +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriverFactory.cs @@ -0,0 +1,21 @@ +using SmartIOT.Connector.Core; +using SmartIOT.Connector.Core.Conf; +using SmartIOT.Connector.Core.Factory; + +namespace SmartIOT.Connector.Plc.SnapModBus; + +public class SnapModBusDriverFactory : IDeviceDriverFactory +{ + private static bool AcceptConnectionString(string connectionString) + { + return connectionString?.ToLower().StartsWith("snapmodbus://") ?? false; + } + + public IDeviceDriver? CreateDriver(DeviceConfiguration deviceConfiguration) + { + if (AcceptConnectionString(deviceConfiguration.ConnectionString)) + return new SnapModBusDriver(new SnapModBusNode(new SnapModBusNodeConfiguration(deviceConfiguration))); + + return null; + } +} diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNode.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNode.cs new file mode 100644 index 0000000..9b7306d --- /dev/null +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNode.cs @@ -0,0 +1,50 @@ +using SnapModbus; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; + +namespace SmartIOT.Connector.Plc.SnapModBus; + +public class SnapModBusNode : Core.Model.Device +{ + private static readonly Regex RegexDB = new Regex(@"^DB(?[0-9]*)$"); + + public SnapMBBroker Client { get; init; } + public new SnapModBusNodeConfiguration Configuration => (SnapModBusNodeConfiguration)base.Configuration; + public bool IsConnected { get; private set; } + + public SnapModBusNode(SnapModBusNodeConfiguration deviceConfiguration) : base(deviceConfiguration) + { + Client = new SnapMBBroker(MBConsts.ProtoTCP, deviceConfiguration.IpAddress, deviceConfiguration.Port); + } + + public int Connect() + { + int err = Client.Connect(); + + IsConnected = err == 0; + + return err; + } + + public int Disconnect() + { + try + { + return Client.Disconnect(); + } + finally + { + IsConnected = false; + } + } + + public int ReadRegisters(ushort address, ushort amount, ushort[] data) + { + return Client.ReadInputRegisters(Configuration.NodeId, address, amount, data); + } + + public int WriteRegisters(ushort address, ushort[] data) + { + return Client.WriteMultipleRegisters(Configuration.NodeId, address, (ushort)data.Length, data); + } +} diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNodeConfiguration.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNodeConfiguration.cs new file mode 100644 index 0000000..f242afb --- /dev/null +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNodeConfiguration.cs @@ -0,0 +1,35 @@ +using SmartIOT.Connector.Core.Conf; +using SmartIOT.Connector.Core.Util; + +namespace SmartIOT.Connector.Plc.SnapModBus; + +public class SnapModBusNodeConfiguration : DeviceConfiguration +{ + public string IpAddress { get; } + public int Port { get; } + public byte NodeId { get; } + public bool SwapBytes { get; } + + public SnapModBusNodeConfiguration(DeviceConfiguration configuration) : base(configuration) + { + var tokens = ConnectionStringParser.ParseTokens(configuration.ConnectionString); + + IpAddress = tokens.GetOrDefault("ip")!; // suppressing null-warning: check is next line + if (string.IsNullOrWhiteSpace(IpAddress)) + throw new ArgumentException("Ip not found in ConnectionString. Provide a valid IpAddress or HostName"); + + var sPort = tokens.GetOrDefault("port") ?? "502"; + if (!int.TryParse(sPort, out var port)) + throw new ArgumentException($"Port not valid in ConnectionString. Provide a valid number"); + Port = port; + + var sNodeId = tokens.GetOrDefault("nodeid") ?? "1"; + if (!byte.TryParse(sNodeId, out var nodeId)) + throw new ArgumentException($"NodeId not valid in ConnectionString. Provide a valid byte."); + NodeId = nodeId; + + var sSwapBytes = tokens.GetOrDefault("swapbytes") ?? "false"; + if (bool.TryParse(sSwapBytes, out var swapBytes)) + SwapBytes = swapBytes; + } +} diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/snapmb.dll b/Devices/SmartIOT.Connector.Plc.SnapModBus/snapmb.dll new file mode 100644 index 0000000000000000000000000000000000000000..1927e4c4d11ddc5e6e97200695ab6f65bbc7a7ec GIT binary patch literal 264704 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

3Y0 zed{vt_bAM4F+B8IesYQ~EnMewx;e}nBw}%UeTFq$TY)vVeGY9!L!_;@wjwyltv!v?w0IpMZ#^dD z!tYtb*%)ypeMVq?WUsi2b!>aTW~$R29?mZF*Jt{XP1z~pBGBh#$bZztkVn=`M;xia zN|+0=c%qtJJD?rdzY+kzvh{hr_yB9!~2DdAKZ}JbGE<<r8oMS*OV(+Zrg394lKM{VbO}G>n^iS7`u7oOI);Tby)jHAP(Cv{zg3q8IMvkIgt> z4T7{{G_Hap%TC$AE`YXnR<1|t3zqMOJb+IIurM&zN)6Vth?k>FZtBVxr^K&VmwO8TK1PYK) zMRPUMk-Kn}uK*_;N3?}^!Ug%M-edHRrdv1DmZKSH_VaM4ZXR`774qh?0`ll(mC2*G z<&j6GHAWtNtkLr5Yn?5RENi$tvaKQV$g%p%qaRk{d8lq~t<q?VUz*P5U%%XX-zogg z;5U-r`TUCbUCPhPPv-^s2_bxw+-%)diRh%RJSoE=)Q*%h(p#n~q~-5`3G0DQrO2oLqu1G5$>%;tFJlOOYYg zks+=CRIa;0K;>E;HC=tqtl8kFSoKAI%H-mP6JajL7?xOXYG*-SUk#eHIK$Whfo&1Q zI}s~JBw!w5#ntp5TlCm;zy|ZT$Q3t(%?0PQ3E;RG+=i1ou(BP1%|8tVHjEV`UL42b zgFd2F#EXxL2*AP%Ff)L7apOXVEEfFDn}051oJFo!Y%K`+a5G@2sn@1WT)jaXHke6{ z0Zw&6f4jts*X4qzXzazOXdQ<)=yRkw|v89S>wmE`EN(+BNA|b()G7Ow3B5)yG zO#+3GZ{EhzsK_OZq8D3iN{#s>r;t8JnW!31=9i`QzS@1Es}27=De*iaJssN5gmwQP zN``HLTpp-|f3C=fxlK89mF25ilL1!THt-&7=OcH`fHil?V|z9%9Q*HNu|wZUjhU

;p=vbyB4H*>`QdwJ~n|Eaauk(JiUo!b1vwK8_tPf){!2=|%jr{Ft-0pKdd=yHhP zOx!5>;5G*_P13RO>=Q(^(`gL45@7s@ZeQ8HwB)__(BE;H2^j}2Ugep3(VOPL298#P zRg8ZOB(*D$)Xr7ZvsvDmvsTL>c8#F*aF6o;Wd5FVtYsX*JjV`0`$_+oWoGT*zh3$VOU z{tZ2=z)}Q|A^+}>{E2fpy}7KZFhzW;-eraIor~mZLJ%&|{(nI_gObu|=8wDUn$%gB z@~13Qwf7aN33#?(fT?#U97aN}uSTnkU!nQaTv=yo(CP|h8vq@-WizRu@^6~=bIZnJ z=V&ob%(4lBP1}1De8T+8Fu5T>XiHG0eLqAucKUG!RX@oGxP57zE%))ca=gr=&lTu; zFn&_wspcQMiZJg&C%KE50Cp&f2RWbL1w3TTpx$f@h-%six_FpCQI=o&3at6%G*O!wrKEQG|vse<@{YqT^4(+!+Q$_;0Bip0|li3b<;>?hbp75t`)3Obuu zYz?q=+{zMxEE-+u5m^Jx1866u2tZAjLlaW;pW?1NOivqvXvyq^HOWgzf zr|5GIl#3-~XrJZSl=pvuB+JVSk0qg_`C0M~6B)}rLF40K^!Dch@vj-oF%6d+HG5l* zI?^sTeC?yi^HMVJcaw#1Ag!y2`u}9E%#lF^K7joCqO%_H8oQ#ie#3RwxC%Y=jSN|7 zblTHg_%%EON|T@x99H8I^?y>Y*@B!HNAQX7HjX_pq^v2K6)2QLX`a{ER&G4dB&`S- z2d{`Z_xp-x7G4(05(}f8NylA_KbMp3r){jZD~y^?BxTU>KNl=$D&scOe%U1L^;b(C z+$WC6n*7EWzJh(_bKWlZv}ivQaas&5U`S|Nu%LMgWyASOVz`(KT3Nj>{W2`mdj7c> zFXErwohP4QD1*C;K1fTtFiUnx&X}ue!Lj5O#sh6qwViuX-OT-$NnTUR4@PIb!RR$% z?BNaJnZ~9yc_T?=bVab?A@mAR;PK(~XT+NfCiH%|hIB^6p@YWl&*^h8pnNzXc(%5p zcL0_7g6)@UufJF& zKhqj+PJ12vWrbF>wX$T8zGO5r67YPfMP5*=Z@aBmW3S{pLabZtpPp5{0Ea4^W0UL|Qy{H*_NCjA%rOZy6U2W);eAG1p(kK1E|o ztJ$imEq_V&Q&9RVCo_%Ju=DMg1AvXM)%<}}J|W&3dkSXl@jCXfVDSwc)b8C+sen{p z-*AAV_9bs@VvZ`Rl6ri8be)2BfpB}1d3Sf0DEUy>2&QZIK1+~wmR#)vke5=j*wd5M z-^K?{&P$Swmm3d!U@xQ13}#uKk7YEc5x*>p z+l*Pi@j7-`K|ew~wOj8lV{Lp7StixWl#mo>+wkwSa!;=hBZ};Q1WDwys1Cd?KSc-xx<~>b8vHn2Pp}PRhpiN_(Y3 z5zDXd#UzaD8}5^woVm`C1NGdtVjZi=pFkZbug>3YW55cYqrt(-1M6dpV@d>0Xfp4* ziE?z~EeixGeX`TDdOisKm-ByPWhV2k@iBnhA;1A1P>q&hxp;+baznZYa^wyew-yG( ztvT)iyYRLX#^9lII>+GXRsU@a9=+{(nXxK| z@0BR8p}B@?n>=&|<+9YEKyJDz-%SA=a(Zf1M4?Sa#R1UV%n&8>#+7~R(A3G$6fm|4cbpgj@X% zNQYsvqyj~U_*>LD)DNi)5X3396#MM>+eO96SFkRbx2;qS^biNEOV-|-Tv7{~q7$7o zJTXSDpn2=Pf&vLqn3X6)BP;QlzuUttgUCu`6^cJn+ZQQ{9ZClo?hYgj!HiDO#l&}0 z5W5X?c8bCJA?ARIHY4jio;Dt%Y@2~IRe6TFegw{RtEyYE^TfXK4-|hd7|U-67``vl z$`1M3z?vq3Hs1dMMQ!28?uv%xzqPEhT=P!^BX0CT{E8t#4!VX)EP)VN;ad(hBX%0| zH{IFhdCAtLQYl7F!C1S%k*!DQ?ulom+#-pWs^FZxP%M?0+HVD;fWkH098THrXNy%8 zXt1T3Id&CF)|UG}=vS8A&2r<@K>Qd8*EkTa>_nONdgw?A0^#G*7xuyG@ZlEt@y{^? zM~dCztINx*#>z6xT5bk}A!oFiKW?*c?jp{@LH+`qrppbdSynu=qgac5Db@FzBAA+O zN2%m7bp3FyOVay$BrVZuHjtFqAFJ4Gu$O7=mm9$&*w_8-cv(bRjok2*E%2ohIKCNi zlXUI=3n=f(SpFrnrX?sx-q%_rmpCK*7DhFCiHAlkTbaM_4-)fId3z;#FBbz*qT#pH zjv1-|ig_=eZP3E^!O3-e#>yw@aEGKt8mQ3vTQcwT33BeVB;?-dH#UQqQ6+Sjx6IG~ zl}vKfq#4sQm1-(I`4i%1jH(R9k>VvVjA(o4J1~Vpr($L=eCLS;JaLp;%xfpcJ&7Qd z_0)-RKSs95dRUDN7#7q$R-1k%*a*lBm|jcN&%I)%8Zi!!ydR?8Osp|$ha7}09E5(3 zEPlvta~HXZAX|Af@(1CermM7TF~2iGf-d@*Xar|4%G=C^L?y7PgfN3^V`XZZh4Wn+ za#c?_Ei~L*t1_KVCiB+>*$jzV$?X5IV71M!eO%q)S>dz0RyYUlCT@KDJ>2*Z=EEk6 zT!O`j9Mjjgr)`v&S4^Y$VgWIY;?pLt=MC#95V~#CC|Q_B3H1sHuW!xjO=4n8E=|){ zil#J{xFA8%G68*s=3OAs+DlXOXM2?^h)S<#Yt?z$3dHzxGH%0hfzz8Ff>FkuSzEzr zUMCAmTQR7=72w_Sb>60BEpxX86aq!(2?!-3%~n0`x=_u=DwcTzWB^qmqwSKwysjHG z?Maa(w3nbh!?#f9iE@Qz+qEhM$$0GY)B(b2j^PD-ASXFc5kSH`;`W_1L$nHymTP`Y zwrusreweOn{-ZwQOJdqZ$!h2!Y+eYbJ6msph{IG~Nn0!ZCD-%{&&6uEhT1TUltmb^ zI}~#GqWyS55UY_hDv?i0Z+$$!ZJY?cJ)dEnA#d&D+T_h)<;q*fxOS~ph_T-1(fVA` zu!=Jzt)SU>d)xco`W*#tdq=cd=lbg>I=#j~Z~gVX5J*2hE~L0d0j9@O-k6IeWRof2C5c}9WA>P)@Xw7N?jR_Pq1vuWe2R(0A zoi5dT&)bRF?1@fo#cFx>@zzWdG2az=9!1m#+ zHAChPl0nIc4nF)&Io1HsHyg3m8bHw8Hcqz5dE~Rz;+ac1zdpqnkdT5GF`cEd(gm%A zo}p|HxV_rT?E^s%M{xCmro8j1wy)d24vmzBl%-_O&V?caSzrN$cS|< ziX;4nBg17bwm==n-%5KLVh-mW64{JTde86)DI%8R8F(S>JiHV8&@ zN@r|4bg=jWeD`cf90QnlMNc5QszAs(-v_XS(79!{Wuc;S|4eBXBa-l}i9s4p*8GrA&8SLT;I4{t4Kr)b4@89T|TM zM=gYf1m|<5HfjgIHW)nH##HLs-EeQd8a9P|#F~}b)X@V~_`=ayJhhryJ5S3&?c;-$ z+I62*Y8S56?z>RM?#h$;4rnt+zG3GXJNeI&XNT-+ZR*I!ctg!tAK@C#_uz<}Q9J^z zdr>r|<{vdv*2uAve=;e zYbqUSa`QfYQd&CwBbrYGbH{GtowAtxUBm4BGgtGyjX0Q=qc)|=r(-iV^V`ig=~SE_ z-@7}-47IAFXXdR}&)j_!nKxRBXF1WQs<&Y@9vyuJon+N~nb^JU)RdlpnN^ATV6-^xbEaLo5$mtErHE?mg{~3vXmMsAMDXa zP4u<(qK375t6Ija*?Z(}LSGHLm9%S*etdB4kzJ!Nl<<)YN1ww}tMRg$*S3D~88NJF zQEDJf-u05^tC=G|u+xscl^$v}KefN+l&jcX!=}k|Z`Hx{wR_)MFLgDq-MMSz??_8& zBd?P@_x>%ly4NPGyeB37ekCoIPs~&fKsjUd8Gr;%&)!a7KLXSGmIylJ=|I z(SPH)=KcLo4QoyXM&C$)0c%sS|D%%4;H7m{^P2bfuG&NaIh+1qXLhPQ>!0pk!*a}# zc~Ik8Yx>)3-e14U)uPf)Z(Mt%Yn~sZ;=COyex|EUp4Rl1wO`$^f%uVas>a;W9d^z8 z*B+d{o;q9Cyq|b#AFbUr^J4~i*g93h z%+K-kXQJtU~I5Y1OdA3$< z9kVvEEAQ9;!^@FTjcZ$vj(%M}9Az<v0OrFyG-Y=uxh{eY@nv zBz2~pdZeU&=ET&MfJEToA}cWZ_q( zIq8z-i-VJbS~uXq^qP#&A-9QU6qzW{ywXO28AVx4YnCp zuhAOcmq{UUv$uByTHcNE_gz?-&f@SVu8%~tIZ|(Z)mujDEsHmAJdy1{d42A` z3GZbY%MtO=S)SFQQ$$}eiwVnHCDUN_UnPTY^<5=Xfe6Vna&@3<5~$l5w{Z zw5ZHd!dtnOSul(iBMQm1bd5V$|I)n^(vr!M4O~<(cfwp~&w~G~Rz7D~7-{i+L!cho zIb@x+n*I@SZ1svpUWRwXdQ7i-<83ZqmPGQdH1*d3eSorpZYhLdjAAU^W>fRA{kq|z&a0y!!qzpy zNK`a*;W5Jf>U)9Q;lt3wcU;JBYc|g%gT<%_om~_Snvr&Z2t0qf%|)Yv_z~o*g_)M! zTzyk(@i9TD3uQQq1^HBZ1c@8L4h7$0z1cSDz-xusm}i!#1}XAKKdcE!qpm{700NG! zaB!(P@kt>|Y=rx9`xX^4r~L>dyV?8+*l=yboi5T^1F_L4>`VMA>?xMs&cSLM6rGp* z(hI1k(j>bzsWs2!DZ#!}L(VXtr}mU!ZS93!q@GkCl-5JGtRQnPC0cvU@EwZ1xY5K^ z6?}TB>qkUEJ!-}Wk!!$>Vn?{B0h&}l!94pQP**)JGc9~T_Hvi(^IUe{ydlUxyOl}v ziohJvSTS%egK?zwclWa;q)hz=>G%$PPK~l8F7l1Ckl_wwiODfzi;^klvT>0R3FL-Q z3dG(hfgXYx#@j3x<<=jG7_;;6pz1MEjcT2Dv0v zC{gyVJNqbeftrKRa9KpfO8n2hnQXS>Q`WIv>TXZ|Z?U%p4sLh5Lu^7Nq}#ScO|yB` ztYq^4Ved`AqBy?4-yQ^HQ$}&YEshF`3yM36bPozTDk$z4lQ;+{D9Z?gqR|LqB1lwB z+~SHx4biA1(FBbP?zrQQ(I_s_afumF5nSJMs!q=gll*?k^St-o_ul9JTZiv_>eSxV z)zwwqJ*GQq{!*%A{7Q)3V2}Z|0c+$~K26pfRypjt-q5}))?C%Hu;p&L#xqnxMq$Cr z1@KW^*c0Jm$n1{P+WOFv=b zq0Bnu8h8$_5aHdt3Ydp=4LnhP$VP7mSC0?e;52~E4qtHd08S4?;qGrfZ=edy zx}9}3Ank6`Y7W&56Cg+UdIb1s)dLTz1*G3n!a^0ijTJggINzk7a0qH~6p}xebprC@ zi5)W}iSHoUg?%#16!aJ10o{JVeiMBv0$M;m(I0!SYt}i}-4ESb93^l2`8n{IzBxY! zHZYw7k0raNoxsx|JSygV`H_wD<$$!?tWQsWsMa?LNPAc?3(l4<0qHN*$+aO;_eU-e zKoF|IDSY?C20>Ye16~vb!pW}1^+4xC@RADGIXTc*VpZN!Lf;1+H2h?j^JVDBFv|s5 zC!pe-JZxEJndzK|jcf{VKIx$#%T?zaaE^3<(nC*I;E8$Ex9~fvZxjH3l+e?t{n0Ji zEx`HaBluB*M>R1=k7}TWo!f+J0qix}@GKMmVxxEEU#cb71)r{uoYPOLAK9m!#PVXW zRR11yn9R)>n|bkwdnIzuy4r{CER&O5-FRWt22R=tm$xN#X6vrfXU zh4Mo~!#x_nb_9UsF6A~GPu#0eer{EG;tquxK&xpNLVIlSul8Nq9V|-vBelL!+9S5( zoYPHYxPG4x&dhS*RT9rFV%>neQxoOMjQ@ywX1fpf&?9@)?a zV3I&x0)I3H_!CwfoT)A{XyHi1K0;;qXf>O?s16nO3ferx63CvH2{paL5uOf_&u4h= zQCa$(rdgHc2ORWuV2*a0>n=R!8nPh5^gAyejbg8jR?n$~JEL58-*ZfIGU%A;)uUeO z@?#2pJ#=pKncI|0d*`GIx!z_m{T3JyCl`vr9z84 zv;4_Ua-9o4y~fTju$GP$#qLqcI?Uc?HBvJ_`Z&JD3Ie&d)62T7B!o6(HL&Q_#9l5r^1<9oj!AxO)@<5 zzCXR>4%m~orx#Ys4B8)<6*LxSJK-iq>=bcb4VKIk;Md)-^0mBo47P*9Y+w2A9p1DG zPsqS&8&ZWw1X!AIUIG0uA#210^zOq__kzE0@osny7p#vO!5hWAj!@e8dx8 zy^lh5KY&;Q7^dosYXJ;C}VnDh*6If};ux z1zoJ&YzXgR-^)G-&TkN15+H~Ugw44r+4G9{lKG9>A&a{jVSb_9Dg)FKOuHTSWzQ_7 zKaPf7X>j!_h29wE3IbprTi*trGglT}QzbL#@yv_-SlVb>T5}K{E({OwE3z zN^T&l6%!Y$*hLVfugF5x?jQNZZc9DPHw(6K|snhqqrM+!bSo?=)FHSg+m)YXe0 znbCMmLizvhx4dT*{{1~rJ77-vFZVz>8csusaEDnbq#|dP`XMmOfUt0r04fM1;N6*e9u!4O!|NGo+r4Y=p8K5-Dt@ZEC;1~pH9M+ zi}k%ve3%ze32ik6%7LR;m>|9O=zTaEw}dDJHvUO}r$}~G&&e-@DoC#3SDCCTPfdmI z3VnS;ZwSS@8W@5Zx?&V?;j#~+YmMJ=)V!w!8#Ql!S9iS!Rw3Ai97Ed`7%KOJG_bPa zK2VFH^3lL^zVIR$y$keyM|iHykIGRuaw@&QJA0}Y?raHg$b|b@GXomK$7^mtokQ%{ zP(vAhhWlyZ)xhZ$Z}I0^VHX9??^6vF4*D8*zGK6hKy1dlp*ZjZlSXKbnoPSi2*vIs zT5<^XY&!p8Z|K)y{8S@~*(%>mwSQOl7BojNd$G39p4a1JR;*RECS6+$3%?a1S%q0; zFjRu^8@t!8Mhl4HUEL3%TC$3vlbZOhZYF$Budq)Z3-Q}o#Gl6E=Tq7>x^v7`d)Yv1-)&t7uaL9Z~$FAXa6+lC4X1dH9@7msO^A`9?kvjM-7rsq# zD$}@n7s8)5Y+eHDgbzxw%SquqFMQMSZp0pn1j%Ti--E^7%rMxUeC)4*dtOFp*m^N^ zR`5fqrJc|3hqZ!&%ptb02&l}&pJ_yKfy-4T{v<aNh>1CJUFbSyJi!U6kbqZB5`%e$W9S zNSUs2u`NHSG=WX|L6r#<@N1HS7Gi?Q1ySQKs9>np5>yQeS78T|;rs?sI8=MoihWVN zUoe00A&G#AHwU1G0v;;9XXL3YMARosO9S zEHM1;5Bh_KVG(?UGOM?iy!B@BXtL^QGDAZRkD+DZT4RVZ>nyDNE8sdF%8t=z1vunl zZMp95Tphn-HNtmC{Eqbt-<|Ng4gc(A_0VkzP^_UL6eQx2E`~CZ6NGG-=LQ z$8c9K_^APZ+SY`tZ}?slzt@1OOKb6NEp)?JE0AVrrae|?K&U3u9?u|fOE}zQg3~K7 zp>SBm*4wb(!Jo9NJ3rtpBr?_A(7YM;>2O2~kDLv48Nkucdm?##&fxzVYOXl_j${bI zZ`h5oOD%RjgsW;8r_eP%-jr;piJpwm75ZkKd~q)A_^i}-?^+bLc;$Q+j(eX}|CzW< ztoJNGpw_DlrMRZw?5JHdI>PYAC;irb!*y`PiH%1v3Af*Xub0HW2ZsGvCRQAHo&-n7 zDqPoF19r%q^C9S}FlP@|g9ENPc^T5t1dpd<#|nKCx`ku~K-HXpyTJ``=<~N{%Xam! zpe%Rn8Pe}mzz+@wlnb`7{;r41OSMBtM5yhH;^1oMG!cctEzb3iI5Z%u4z!v+$}GH- zu8%``prV?q-WlGepn%CiV{o;?;LecD@i7nnSZMXcU2sG|FX(jPWw7wL9ZadJ>%i}x zgu$I%1@7=u_V_xn_p(A=g8j}XIzi;sP6Yd%ORNbu5rPM=AitA|x8ZI8%bu2vOH9)& z^ZjE7!keSuDix+MjEd+T(zs>5C$mu>9-!Fh2yIt{vY) zk^G(}Vn{Eze<`hJXb_yRT{{=-a)vHse2)R{FbqG0+4xU$EUJ*KAyCn9tFsYKS9tS1 zKDk>Nkkt@g>j%FN_-pX|KDLH=FvQLZvh^5WUYrjNK)@H3RxA2+n5`b(T@Akt0K1*t zl)oaEuORzaJl-xhZ9Z3GSs(m+mGh4>IJ1ri-ZknFg155{_iztxTNL8j`$AN3R%1`R z4hezZpshC^uDS(d$o!Q8JQEZEZ(sq}WVnM0!b1CVfQF(p)x1UgZO`>juW(3gUVfmu zzRvjUd5WI4wcytu-oewGh2WO_1kT+3;ZiiYDXzJ?ay$MOE7+4uVR`5fyd$#s-Jxvo zWTWq{$%5ORA%-zv8C@dFp(8iw!gD0Ngs&2cJSo zFLBkR--g$*o_L23U&3U`h1>9Kp$$G;2vh6HZt#QYO0dXoP`p1}_rM!^4nEijm74DV z0`38^afO#~z)&0}Ss|XR!jP=u1GgOdr{6KcTPGm}m@R?l_h6L(hVr^X9I}rOaE12> zxLzq{$?49)a$&;33nj8MyqWiWi{fN&*S#m;=|{BnuXYk%)#Dm;9A3W+%MWlM%6_jx zG(~VoUBDONwm9^zutgCBGy8|BBiywx)wECWI~W2JP64UDg8n`{F9@e@*W~+8SJO=w`jsBZskR5qS>{Ts}yr4z}0WXRJVe}?#=P#^8Dxw$yV`cF+L9%w_&;d;$LkDvh;7Zt9X1q22V>n)CY^N z&L`IKk$;#M$*?GoIlf{SD(@vrJH)4kgdXe|YaL%0_pdcI6aEGghjo_D^kgv=T7*K|248P}M7cf5+^Pj2FX|nb%XGIz7U^s!M%rGsCq+zHH zW1-Q$hM%}wv|%fJ$(oP%7-ecwASDgFB;FU^aDrE&>=IOy^=S^qgqO~6$`8(Bho>fM z39l+g!@ytQO@QMuzI3flI2Ocab=Y75va248Z?%UEysATJ4v_3&xc?27 z9%9MVsK5e|hB+9{qhn^6m*JK@O#d7UQb95B=U^dQUMyFbDQqEzT=02JodRPWJchAx zn0Hntt5dk9fT=U~Y%LVM0wOY;gxeBL`_^1Qs6vB!6}?;s0A3RZso);8EE_f~gAyV$H5|DQH~a*F(o=kdPaF(e;Oje$4}@fGOYYh_<1?VD z4zut92}1aM_+XsL0PPZb5p(S+cUUHp1&1-KhDvx_n>lT0)3{@x z78+P(Siw6Zy-S*J6X=xK4=9*#O@wk=ZWg<*YB$E>#^aNp&zfWfz-$Mc^?&zgz=1F5)z*AY>GCokFT54JH*yJCQagtx5X&3J?E~w21U4(p9AMNK;5- zNwuUSNdrl{kg7=QkiK`Gr`L^i5a~qH*`&)!b4hoT7Lr~hEg^kOT0v@mfu~cC)PuAe zsfIL+R7*OAbPnlK(wCQcd^?B>NpF*ulh&sF;zJrp8b+!o%^+Ptx{b7ubSdqhXT&ux z@$@}NJCXJ!4I|Z&rjllp=8zVWUL?IuY9xJ0>O$ppCv8dEi*zVyBWwjrmAA2;wR{%F=}|>@f-NOCVkbQ zb^~Yl*pdpZp$$;mqMUGn^ETUX{7zs09~I39yxuDQOL?v95HqO$jqg_+;NccZjH6ZD z!mRAk#MJJE6k=-kLWUJDC#Lo<geHWJJEtss{3<8XtQkB&RR zomkHQ#DuEt(1YxBybC_Wa{Ru;avSJHO#O_Yw&Fl5{~9ZMuoZ_|aTu|@e$uYDtr;K-2avk%k_Vkn8pW!(aOJYi|0qqp9itrANdf=^{*y|izc>(63gY$ z5zFhd7_o}%sl?5RbBVo(i;3G1JKW*%7( zc@fL~voA4?bA(W0x&O58FSSS z9WQeKXx;vels=8ugt24Cg(pQt%GC!P5g8Q`3#toGiU^P8wI%>3=_7$%jBVeHtb@NqFbW{7uc%y|9Sj+BthovauigTxK;rBCeE z1vD`#NuL-KK?C5iW0T{2fxDyk5O;2jnDxU+dD)h^ny6RyC8zL z1NCYx2%ms{1fLo3X$gU5z?U{)Zwnu9_;i4eCwxQ*)C(ejpEVX{z~>|QXs3+VhmVVm zY6W3&(@q&@#aP3w1g(CWE-GQX>`l<(kT6!2q=#`~gla-^cw!{8X|dAcCz$m0LSPTv%}rP*ZX+5b1U$dl*NYG9DPnCw{PL9;85~K9ViScYVX@fL^cIfco{aG%+&iAXSRaE7_ zj~Ls(+^+c!iIP#!Cd9?ZsIUzs#bB$@nk$JPDV94G^Ji@z^I;Vq+PLue@Ci|o|5T~g z{{FU7F+Fp={-x&3wO_@bT>n<}Z0Q^PM^1|mkAp)aCO$?V13ky|s6^HHghZ9xE@XSS zJ}MBwKC$6RNfuUBxL(CiC6@L8 zyzZoWr68IZIMfp(qNiB|WK9|pAx^9w;K7_`bJj|HnZ z0$97tV+>1wf9EETO|8SrjQO(+hu5O`vHKeZC+7Z7ifLkaIc@Zh8@m0Q4fEsxA5`2h z9%MN@Dl%@>xtJIc{iC~_zN{Dy-Q{@Tt0_Ewmp3_GS<&4(J=w3dqCdHAQLP#MTiTJW zH_Ri3lgnnwvb&{Xcx2qeH z!=@_lv|<;^sC+y+65|*iH#|?sXNM@Ey%5RjLJ;~SBnH7aAT%){A}Wa{&(Z&kgM1zm zzvEVD#oBA!zL<0e*<}s%Do_asCo**tTRF#*1N5alQlN#dQdD!|V0h^g;J#@Im*k@IekB?n4|3ieZM5`!Lc` zpcwyX_+Z+x@WFWE;G=?%4nB?HlL#NoYbty&uW6*SLD4-OJ{Zp&_+WTwx9kX7L%a?Y z^R)p!n9euwL4QBP2lI6XK4`xJAGD8#3dCzpcc>JsA9>6EgYQpyyi-K1e8Szeqy?nP zKgpfOL!rdkq(!7kBl#mOAXS!SOm zL%!r3;9lUh_D|vY2s{yTA&14$ww(Gu@4pmQ3%m_s3_N9X41b?KrYHC6-jH`aoa2(9 zP7@(*o})-8&D6ii)8EI3VIv?8tVumw0eC}fSTDGr$*0_){!M(ACB{5sZB{)B%=-je+7$4CS5cPNFPKC5 z+$g7w`uFvPt~#ih1PFsyLf%kwSVw=?3-FdK?;knMs^#M60ZcC-F{*D&Y*c_QCMjUh zu>R~kqXc)nj0{hV8pdY3;2K8(Zs`7>6imzx7gd39$=^32F)mzB*G3o)+=s`+nceYR zh3K?5 zb=;6BeRM)3`VEI#>%i&`2^oo>o4i5sT+|ajcn)d{A5ZvLZY9ro|9zL#K>C=}NcxPl zg7h`1Z8;CGBy}hCBJDz|CJiPHB^^#0M*0D%mNb$ynlzSFN2(`HAx$OCAYDwlid2q& z1MzpHJ4g#ii%5$}4W!RVD@cVGRDMz~Qfx=K`4X#1HKd`WVWe8pXi^<%Drq)p4rxAV z5orl&1*uTM^W{LQBy}fMk$RE(kouCUNj0RQq+z7dq^YFYq;mW@#CfCzq(!6#(h5># zCC{%1X**J1(ooVA(rnUU$?-x6+yyZl<~c^e-2U+BWT9_jj4&)*FMusFd>TAHl)x4D z2lz`uNAMfZzo9F*M<*vT1AK?@EF8K+Jjsbf1H$8lzER_ZknltyK$i$QO&9>_4@iy` z0+J^P!=iM;U|4V%lrTjIjEcbE0)&CN^=TL`k$nL-2p0>OA7x8R;U zAvuXA+#pVURNS~I9ADxF@h2cb8kWwOo{0lO;JyX;;1&|nAu_VVG!`#%By8MqCU%3op{V#-vJ{j*Z84iq@90h-|;~5SkjbzZi>>LO61U(OCnTNws z9FLcWH#3hX3F6>BczihjWp;yTD1MxElYOGAoSxZ@r;BG=vm5#w0P$nK`$Ej)Ktq7B z4g;WfL`B9N4xnd_&xbiY&kvsMc!S{i;^PQDFUjNK`NT0Lj#}jW;%Gy5 z;r@93tGWk3E^!Mb#qS~TuLIO+B>bIbQ66-`f9(Vn^Mn3WCKvR{eQN{_oOj~jUr)%p z`GPnK_KLSK0_vAJ^fI&h$ z9N(Z`!{ASA${)CSCBYw=(cXVhpo|#CD<(dYaa3C$A0G%eTzH>C3qlU1mq%JaT0}Y# z+KV@|rceqooy`>Yf%e9?Nzm@Rg@Nq46kWJ|I2?!nvtbRW{_@XDkpKPF_E#4TjL!bs zzd3NlZvC&S|1`MPm;Wh*fA`NahS`GopAB5&|C@huc-8;!8$oK??DRRGel|BFb6(cx z^A{}4Ui8J6iel`mZ-^-1JTEx0||V#8#haC-M(}8_j~sZ z4<0^x{Ku0&jiqHzpFJ;sQBnEw)$2EJ-w6tvYPNRv)g2t2oLy?vbXC@>?N+C5z54DA z8a8U&lxl7k>-F?W9{XVb%|7`z%Mg0qZ>Hk!w{?F@gZ2!0PfQmHJKgGY?A-_^Y6-Q83MmM{7wH~TY>TWm@IXKI`EylD&f2Q4SoH~0+Tmu|NpME zW?xFd{GESOC@fC`GWgWu_wnMKF>b!ZI3IzVni!^$*`^`3BMu{$*8#P}IIn?QG%?P_ zJa?T+;pSR#7? zv6{G$IDoi_xDRnLaUgLCabIEsaX(@saev|p;sL~h8!!JrVh3V*oUbGvOm=r-`MN+w zJd*5Q#PW5353zjZ;Y%!ES*nR=QFslpf;xauVjE&DaW!Hcu`RKD{bEO)N_KnVY~t#~ zIm8acdBl#gf11y6CN3bm3vm%~4dN2wn#4w8S7MlDHPJd|g(XSiUZEBUX|B zI>bK2b&1u)^@u}>-HElt4TyEb4T)2U8xdy{Hzv*@ZbF<#{2p-uaZ}!#7bfhVimC`u@7-eVl{Cq;!xt HDu#5&@(#HqyXh_i{kiF1iN5a$zj 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 61d5033..095b3ad 100644 --- a/README.md +++ b/README.md @@ -17,50 +17,51 @@ SmartIOT.Connector is a good fit for industrial and automation needs, where deve The following quick start creates an SmartIOT.Connector instance that connects to a device (namely a Siemens PLC) and reads 100 bytes from Tag 20.
Whenever a change is detected in the tag, a message is published to the Mqtt Server specified in the connection string below.
-It also listens for incoming messages in topic tagWrite and tries to write data to tag 22.
+It also listens for incoming messages in topic `tagWrite` and tries to write data to tag 22.
For message formats, read the docs [here for project SmartIOT.Connector.Messages](./Core/SmartIOT.Connector.Messages/README.md). JSON serializer is used by default, but Protobuf can also be used, or even your own serializer. 1. Create a configuration json file (see [this file](./Docs/Configuration.md) for configuration reference): + ```json { - "ConnectorConnectionStrings": [ - "mqttClient://Server=;ClientId=MyClient;Port=1883" - ], - "DeviceConfigurations": [ - { - "ConnectionString": "snap7://Ip=;Rack=0;Slot=0;Type=PG", - "DeviceId": "1", - "Enabled": true, - "Name": "Test Device", - "IsPartialReadsEnabled": false, - "IsWriteOptimizationEnabled": true, - "Tags": [ - { - "TagId": "DB20", - "TagType": "READ", - "ByteOffset": 0, - "Size": 100, - "Weight": 1 - }, - { - "TagId": "DB22", - "TagType": "WRITE", - "ByteOffset": 0, - "Size": 100, - "Weight": 1 - } - ] - } - ], - "SchedulerConfiguration": { - "MaxErrorsBeforeReconnection": 10, - "RestartDeviceInErrorTimeoutMillis": 30000, - "WaitTimeAfterErrorMillis": 1000, - "WaitTimeBetweenEveryScheduleMillis": 0, - "WaitTimeBetweenReadSchedulesMillis": 0, - "TerminateAfterNoWriteRequestsDelayMillis": 3000, - "TerminateMinimumDelayMillis": 0 - } + "ConnectorConnectionStrings": [ + "mqttClient://Server=;ClientId=MyClient;Port=1883" + ], + "DeviceConfigurations": [ + { + "ConnectionString": "snap7://Ip=;Rack=0;Slot=0;Type=PG", + "DeviceId": "1", + "Enabled": true, + "Name": "Test Device", + "IsPartialReadsEnabled": false, + "IsWriteOptimizationEnabled": true, + "Tags": [ + { + "TagId": "DB20", + "TagType": "READ", + "ByteOffset": 0, + "Size": 100, + "Weight": 1 + }, + { + "TagId": "DB22", + "TagType": "WRITE", + "ByteOffset": 0, + "Size": 100, + "Weight": 1 + } + ] + } + ], + "SchedulerConfiguration": { + "MaxErrorsBeforeReconnection": 10, + "RestartDeviceInErrorTimeoutMillis": 30000, + "WaitTimeAfterErrorMillis": 1000, + "WaitTimeBetweenEveryScheduleMillis": 0, + "WaitTimeBetweenReadSchedulesMillis": 0, + "TerminateAfterNoWriteRequestsDelayMillis": 3000, + "TerminateMinimumDelayMillis": 0 + } } ``` @@ -69,10 +70,10 @@ For message formats, read the docs [here for project SmartIOT.Connector.Messages ```csharp // Build SmartIOT.Connector and bind it to your DI container or wherever you can do this: var smartiot = new SmartIOT.Connector.Core.SmartIotConnectorBuilder() - .WithAutoDiscoverDeviceDriverFactories() - .WithAutoDiscoverConnectorFactories() - .WithConfigurationJsonFilePath("smartiot-config.json") - .Build(); + .WithAutoDiscoverDeviceDriverFactories() + .WithAutoDiscoverConnectorFactories() + .WithConfigurationJsonFilePath("smartiot-config.json") + .Build(); // Start SmartIOT.Connector whenever you need it to run smartiot.Start(); @@ -83,65 +84,70 @@ smartiot.Stop(); ## Documentation - - [Configuration guide](./Docs/Configuration.md) - - [Configure the devices](./Docs/Configuration.md#configuring-the-devices) - - [Configure the connectors](./Docs/Configuration.md#configuring-the-connectors) - - [Configure the scheduler main properties](./Docs/Configuration.md#configuring-the-scheduler-main-properties) - - [Snap7 PLC configuration guide](./Devices/SmartIOT.Connector.Plc.Snap7/README.md) - - [S7Net PLC configuration guide](./Devices/SmartIOT.Connector.Plc.S7Net/README.md) - - [Connectors guide](./Docs/Connectors.md) - - [REST API guide](./Core/SmartIOT.Connector.RestApi/README.md) - - [Customization guide](./Docs/Customize.md) +- [Configuration guide](./Docs/Configuration.md) + - [Configure the devices](./Docs/Configuration.md#configuring-the-devices) + - [Configure the connectors](./Docs/Configuration.md#configuring-the-connectors) + - [Configure the scheduler main properties](./Docs/Configuration.md#configuring-the-scheduler-main-properties) +- Supported devices + - [Snap7 PLC configuration guide](./Devices/SmartIOT.Connector.Plc.Snap7/README.md) + - [S7Net PLC configuration guide](./Devices/SmartIOT.Connector.Plc.S7Net/README.md) + - [SnapModBus device configuration guide](./Devices/SmartIOT.Connector.Plc.SnapModBus/README.md) +- [Connectors guide](./Docs/Connectors.md) +- [REST API guide](./Core/SmartIOT.Connector.RestApi/README.md) +- [Customization guide](./Docs/Customize.md) ## SmartIOT.Connector.ConsoleApp and Docker integration If you want to run SmartIOT.Connector as a standalone application or as a Docker container, see project [SmartIOT.Connector.ConsoleApp](./Apps/SmartIOT.Connector.ConsoleApp/README.md) for further details. -Here is a quick link to the Docker image repository: https://hub.docker.com/repository/docker/lucadomenichini/smartiot-connector-consoleapp +Here is a quick link to the Docker image repository: ## Nuget packages You can find SmartIOT.Connector packages on nuget.org site and on Visual Studio Package Manager: -https://www.nuget.org/packages?q=SmartIOT.Connector + ## Credits -Currently Siemens PLCs support is provided by Snap7 library (http://snap7.sourceforge.net/) and S7Net library (https://github.com/S7NetPlus/s7netplus), so the same PLCs families supported by those libraries are also supported here. +These libraries provide connectivity with underlying devices: + +- Snap7 for Siemens S7 plc family, S7300, S71200, S71500 +- SnapModbus for devices on ModBus network +- S7NetPlus for Siemens S7 plc family, S7300, S71200, S71500 ## Disclaimer As of version 0.x, interfaces and implementation details are subject to change without notice. I will do my best to keep the interfaces stable, but there are possibilities to incur in such breaking changes. -**currently Siemens PLCs are the only supported devices - -## Roadmap to 1.0 - Features TODO list: - - - [X] REST Api (included in default CosoleApp project) - - [ ] GRPC Server Connector - - [X] TCP Server Connector - - [X] TCP Client Connector - - [ ] Web app with monitoring capabilities (included in default ConsoleApp project) - - [X] Nuget packages on nuget.org - https://www.nuget.org/packages?q=SmartIOT.Connector - - [X] Docker runner image on dockerhub - https://hub.docker.com/repository/docker/lucadomenichini/smartiot-connector-consoleapp - - [ ] Apps - - [X] Run SmartIOT.Connector as a console app - - [X] Run SmartIOT.Connector as a Docker image - - [ ] Run SmartIOT.Connector as a WPF app - - [ ] Run SmartIOT.Connector as a WinService - - [ ] Testers: connector counterpart as WPF app - - [ ] GRPC Client - - [X] TCP client - - [X] TCP server +## Roadmap to 1.0 - Features TODO list + +- [X] REST Api (included in default CosoleApp project) +- [ ] GRPC Server Connector +- [X] TCP Server Connector +- [X] TCP Client Connector +- [ ] Web app with monitoring capabilities (included in default ConsoleApp project) +- [X] Nuget packages on nuget.org - +- [X] Docker runner image on dockerhub - +- [ ] Apps + - [X] Run SmartIOT.Connector as a console app + - [X] Run SmartIOT.Connector as a Docker image + - [ ] Run SmartIOT.Connector as a WPF app + - [ ] Run SmartIOT.Connector as a WinService +- [ ] Testers: connector counterpart as WPF app + - [ ] GRPC Client + - [X] TCP client + - [X] TCP server ## Docs TODO list - - [ ] Extensibility docs - - [ ] Comment public classes and interfaces +- [ ] Extensibility docs +- [ ] Comment public classes and interfaces ## Technical TODO list - - [ ] Leverage the async pattern for Connectors and Devices: - introduce IAsyncDeviceDriver and IAsyncConnector and add support to autodiscover and run them - - [ ] The proto files should be part of SmartIOT.Connector.Messages project - - [ ] Build and push docker image with github workflow +- [ ] Leverage the async pattern for Devices: + introduce `IAsyncDeviceDriver` and add support to autodiscover and run them +- [ ] The proto files should be part of SmartIOT.Connector.Messages project +- [ ] Build and push docker image with github workflow +- [ ] Use ActivatorUtilities from DI container to create device factories instead of default constructor diff --git a/SmartIOT.Connector.sln b/SmartIOT.Connector.sln index 49779a1..a6c0683 100644 --- a/SmartIOT.Connector.sln +++ b/SmartIOT.Connector.sln @@ -63,6 +63,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.RestApi" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.RestApi.Tests", "Tests\SmartIOT.Connector.RestApi.Tests\SmartIOT.Connector.RestApi.Tests.csproj", "{4EC87D61-7FC7-413A-9B35-7D15AAB2308F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Plc.SnapModBus", "Devices\SmartIOT.Connector.Plc.SnapModBus\SmartIOT.Connector.Plc.SnapModBus.csproj", "{BA4F5CF9-A2F9-4CA4-BAE1-27AEF1EC2800}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -145,6 +147,10 @@ Global {4EC87D61-7FC7-413A-9B35-7D15AAB2308F}.Debug|Any CPU.Build.0 = Debug|Any CPU {4EC87D61-7FC7-413A-9B35-7D15AAB2308F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4EC87D61-7FC7-413A-9B35-7D15AAB2308F}.Release|Any CPU.Build.0 = Release|Any CPU + {BA4F5CF9-A2F9-4CA4-BAE1-27AEF1EC2800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA4F5CF9-A2F9-4CA4-BAE1-27AEF1EC2800}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA4F5CF9-A2F9-4CA4-BAE1-27AEF1EC2800}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA4F5CF9-A2F9-4CA4-BAE1-27AEF1EC2800}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml b/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml index a1aea30..d4e18c7 100644 --- a/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml +++ b/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml @@ -1,39 +1,41 @@  - -