diff --git a/.github/workflows/dotnet-prerelease.yml b/.github/workflows/dotnet-prerelease.yml index 7c45ec7..97fc6d4 100644 --- a/.github/workflows/dotnet-prerelease.yml +++ b/.github/workflows/dotnet-prerelease.yml @@ -42,6 +42,7 @@ jobs: run: | dotnet nuget push Connectors\SmartIOT.Connector.Mqtt\bin\Release\SmartIOT.Connector.Mqtt.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Connectors\SmartIOT.Connector.Tcp\bin\Release\SmartIOT.Connector.Tcp.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} + dotnet nuget push Core\SmartIOT.Connector.DependencyInjection\bin\Release\SmartIOT.Connector.DependencyInjection.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Core\SmartIOT.Connector.Messages\bin\Release\SmartIOT.Connector.Messages.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Core\SmartIOT.Connector.Prometheus\bin\Release\SmartIOT.Connector.Prometheus.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Core\SmartIOT.Connector.Core\bin\Release\SmartIOT.Connector.Core.${{ 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 6a03cc2..e88677c 100644 --- a/.github/workflows/dotnet-release.yml +++ b/.github/workflows/dotnet-release.yml @@ -42,6 +42,7 @@ jobs: run: | dotnet nuget push Connectors\SmartIOT.Connector.Mqtt\bin\Release\SmartIOT.Connector.Mqtt.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Connectors\SmartIOT.Connector.Tcp\bin\Release\SmartIOT.Connector.Tcp.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} + dotnet nuget push Core\SmartIOT.Connector.DependencyInjection\bin\Release\SmartIOT.Connector.DependencyInjection.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Core\SmartIOT.Connector.Messages\bin\Release\SmartIOT.Connector.Messages.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Core\SmartIOT.Connector.Prometheus\bin\Release\SmartIOT.Connector.Prometheus.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} dotnet nuget push Core\SmartIOT.Connector.Core\bin\Release\SmartIOT.Connector.Core.${{ steps.version.outputs.PRODUCT_VERSION }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} diff --git a/.gitignore b/.gitignore index 361293a..48a98fb 100644 --- a/.gitignore +++ b/.gitignore @@ -349,3 +349,4 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ /Apps/SmartIOT.Connector.App/log.txt +/out diff --git a/Apps/SmartIOT.Connector.App/AspNetExtensions.cs b/Apps/SmartIOT.Connector.App/AspNetExtensions.cs deleted file mode 100644 index c9c8ce4..0000000 --- a/Apps/SmartIOT.Connector.App/AspNetExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using SmartIOT.Connector.Core; -using SmartIOT.Connector.Core.Factory; - -namespace SmartIOT.Connector.App; - -public static class AspNetExtensions -{ - public static IServiceCollection AddSmartIotConnectorRunner(this IServiceCollection services, AppConfiguration configuration) - { - // Add SmartIOT.Connector services to the container. - services.AddSingleton(configuration); - services.AddSingleton(s => s.GetRequiredService().SmartIotConnector); - services.AddSingleton(s => s.GetRequiredService().ConnectorFactory); - services.AddSingleton(s => s.GetRequiredService().DeviceDriverFactory); - services.AddSingleton(s => s.GetRequiredService().SchedulerFactory); - services.AddSingleton(s => s.GetRequiredService().TimeService); - - // 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.App/Dockerfile b/Apps/SmartIOT.Connector.App/Dockerfile index 96c7033..a943eb8 100644 --- a/Apps/SmartIOT.Connector.App/Dockerfile +++ b/Apps/SmartIOT.Connector.App/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env WORKDIR /build # used for versioning the dll @@ -11,10 +11,10 @@ RUN dotnet restore SmartIOT.Connector.App.sln RUN dotnet build --no-restore -c Release SmartIOT.Connector.App.sln -RUN dotnet publish -c Release -o out /p:version=$version SmartIOT.Connector.App.sln +RUN dotnet publish -c Release -o out -f net8.0 /p:version=$version SmartIOT.Connector.App.sln # Build runtime image -FROM mcr.microsoft.com/dotnet/aspnet:6.0 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 WORKDIR /app COPY --from=build-env /build/out . ENTRYPOINT ["dotnet", "SmartIOT.Connector.App.dll", "/SmartIOT.Connector/smartiot-config.json"] diff --git a/Apps/SmartIOT.Connector.App/Program.cs b/Apps/SmartIOT.Connector.App/Program.cs index 5d4f279..9bcc1fa 100644 --- a/Apps/SmartIOT.Connector.App/Program.cs +++ b/Apps/SmartIOT.Connector.App/Program.cs @@ -1,5 +1,7 @@ using Asp.Versioning.ApiExplorer; using Serilog; +using SmartIOT.Connector.DependencyInjection; +using SmartIOT.Connector.Prometheus; using SmartIOT.Connector.RestApi; using System.Diagnostics; using System.Text.Json; @@ -33,7 +35,7 @@ public static void Main(string[] args) { ReadCommentHandling = JsonCommentHandling.Skip }); - if (configuration == null) + if (configuration?.Configuration is null) { Console.WriteLine($"Configuration not valid for file {path}"); return; @@ -48,7 +50,12 @@ public static void Main(string[] args) builder.Logging.AddSerilog(dispose: true); // Add SmartIOT.Connector services to the container. - builder.Services.AddSmartIotConnectorRunner(configuration); + builder.Services.AddSmartIOTConnector(cfg => + { + cfg.WithAutoDiscoverConnectorFactories() + .WithAutoDiscoverDeviceDriverFactories() + .WithConfiguration(configuration.Configuration); + }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); @@ -69,8 +76,17 @@ public static void Main(string[] args) o.ServiceName = typeof(Program).Assembly.GetName().Name!; }); + // ---------------------------------------------------------------------------- + // build app var app = builder.Build(); + // configure more things on SmartIOT.Connector + app.UseSmartIOTConnector(cfg => + { + if (configuration.PrometheusConfiguration is not null) + cfg.AddPrometheus(configuration.PrometheusConfiguration); + }); + // Configure the HTTP request pipeline. app.UseSwagger(); app.UseSwaggerUI(options => @@ -93,6 +109,7 @@ public static void Main(string[] args) var logger = app.Services.GetRequiredService>(); logger.LogInformation("{NewLine}{NewLine} --> SmartIOT.Connector v{version}{NewLine}", Environment.NewLine, Environment.NewLine, version, Environment.NewLine); + // -------------------------------------------------------------------------------- app.Run(); } diff --git a/Apps/SmartIOT.Connector.App/Runner.cs b/Apps/SmartIOT.Connector.App/Runner.cs deleted file mode 100644 index a314a19..0000000 --- a/Apps/SmartIOT.Connector.App/Runner.cs +++ /dev/null @@ -1,112 +0,0 @@ -using SmartIOT.Connector.Core; -using SmartIOT.Connector.Core.Factory; -using SmartIOT.Connector.Prometheus; - -namespace SmartIOT.Connector.App; - -public class Runner : IHostedService -{ - private readonly ILogger _logger; - - public SmartIotConnector SmartIotConnector { get; } - public ConnectorFactory ConnectorFactory { get; } - public DeviceDriverFactory DeviceDriverFactory { get; } - public ISchedulerFactory SchedulerFactory { get; } - public ITimeService TimeService { get; } - - public Runner(AppConfiguration configuration, ILogger logger) - { - _logger = logger; - - if (configuration.Configuration == null) - throw new ArgumentException($"SmartIOT.Connector Configuration not valid"); - - SmartIotConnectorBuilder builder = new SmartIotConnectorBuilder(); - SmartIotConnector = builder - .WithAutoDiscoverDeviceDriverFactories() - .WithAutoDiscoverConnectorFactories() - .WithConfiguration(configuration.Configuration) - .Build(); - - ConnectorFactory = builder.ConnectorFactory; - DeviceDriverFactory = builder.DeviceDriverFactory; - SchedulerFactory = builder.SchedulerFactory; - TimeService = builder.TimeService; - - if (configuration.PrometheusConfiguration != null) - SmartIotConnector.AddPrometheus(configuration.PrometheusConfiguration); - - SmartIotConnector.SchedulerStarting += (s, e) => _logger.LogInformation($"{e.Scheduler.DeviceDriver.Name}: Scheduler starting"); - SmartIotConnector.SchedulerStopping += (s, e) => _logger.LogInformation($"{e.Scheduler.DeviceDriver.Name}: Scheduler stopping"); - SmartIotConnector.SchedulerRestarting += (s, e) => _logger.LogInformation($"{e.DeviceDriver.Name}: Scheduler restarting"); - SmartIotConnector.SchedulerRestarted += (s, e) => - { - if (e.IsSuccess) - _logger.LogInformation($"{e.DeviceDriver.Name}: Scheduler restarted successfully"); - else - _logger.LogError($"{e.DeviceDriver.Name}: Error during scheduler restart: {e.ErrorDescription}"); - }; - SmartIotConnector.TagReadEvent += (s, e) => - { - if (e.TagScheduleEvent.Data != null) - { - // data event - if (e.TagScheduleEvent.Data.Length > 0) - _logger.LogInformation($"{e.DeviceDriver.Name}: {e.TagScheduleEvent.Device.Name}, {e.TagScheduleEvent.Tag.TagId}: received data[{e.TagScheduleEvent.StartOffset}..{e.TagScheduleEvent.StartOffset + e.TagScheduleEvent.Data.Length - 1}], size {e.TagScheduleEvent.Data.Length}"); - } - else if (e.TagScheduleEvent.IsErrorNumberChanged) - { - // status changed - _logger.LogInformation($"{e.DeviceDriver.Name}: {e.TagScheduleEvent.Device.Name}, {e.TagScheduleEvent.Tag.TagId}: status changed {e.TagScheduleEvent.ErrorNumber} {e.TagScheduleEvent.Description}"); - } - }; - SmartIotConnector.TagWriteEvent += (s, e) => - { - }; - 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.."); - SmartIotConnector.Stopped += (s, e) => _logger.LogInformation("SmartIOT.Connector stopped"); - SmartIotConnector.ConnectorStarted += (s, e) => - { - _logger.LogInformation($"{e.Connector.GetType().Name}: {e.Info}"); - }; - SmartIotConnector.ConnectorStopped += (s, e) => - { - _logger.LogInformation($"{e.Connector.GetType().Name}: {e.Info}"); - }; - SmartIotConnector.ConnectorConnected += (s, e) => - { - _logger.LogInformation($"{e.Connector.GetType().Name}: {e.Info}"); - }; - SmartIotConnector.ConnectorConnectionFailed += (s, e) => - { - _logger.LogError(e.Exception, $"{e.Connector.GetType().Name}: {e.Info}"); - }; - SmartIotConnector.ConnectorDisconnected += (s, e) => - { - _logger.LogInformation($"{e.Connector.GetType().Name}: {e.Info}"); - }; - SmartIotConnector.ConnectorException += (s, e) => - { - _logger.LogError(e.Exception, $"{e.Connector.GetType().Name}: Unexpected exception: {e.Exception.Message}"); - }; - - if (builder.AutoDiscoveryExceptions.Any()) - { - if (_logger.IsEnabled(LogLevel.Debug)) - _logger.LogDebug($"Error autodiscoverying dll: [{Environment.NewLine}{string.Join($"{Environment.NewLine}\t", builder.AutoDiscoveryExceptions.Select(x => x.Message))}{Environment.NewLine}]"); - } - } - - public Task StartAsync(CancellationToken cancellationToken) - { - return SmartIotConnector.StartAsync(); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return SmartIotConnector.StopAsync(); - } -} diff --git a/Apps/SmartIOT.Connector.App/SmartIOT.Connector.App.csproj b/Apps/SmartIOT.Connector.App/SmartIOT.Connector.App.csproj index 35499db..25b7de3 100644 --- a/Apps/SmartIOT.Connector.App/SmartIOT.Connector.App.csproj +++ b/Apps/SmartIOT.Connector.App/SmartIOT.Connector.App.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -35,6 +35,7 @@ + diff --git a/Core/SmartIOT.Connector.Core/Connector/AggregatingConnectorEventQueue.cs b/Core/SmartIOT.Connector.Core/Connector/AggregatingConnectorEventQueue.cs index 829d1b1..31b6cc8 100644 --- a/Core/SmartIOT.Connector.Core/Connector/AggregatingConnectorEventQueue.cs +++ b/Core/SmartIOT.Connector.Core/Connector/AggregatingConnectorEventQueue.cs @@ -99,8 +99,12 @@ public class AggregatingConnectorEventQueue : AggregatingQueue 0) { if (item == default) { diff --git a/Core/SmartIOT.Connector.Core/Factory/ConnectorFactory.cs b/Core/SmartIOT.Connector.Core/Factory/ConnectorFactory.cs index 0ff9b9c..8fe44e3 100644 --- a/Core/SmartIOT.Connector.Core/Factory/ConnectorFactory.cs +++ b/Core/SmartIOT.Connector.Core/Factory/ConnectorFactory.cs @@ -1,27 +1,27 @@ -namespace SmartIOT.Connector.Core.Factory +namespace SmartIOT.Connector.Core.Factory; + +public class ConnectorFactory : IConnectorFactory { - public class ConnectorFactory : IConnectorFactory - { - private readonly List _factories = new List(); + private readonly List _factories = new List(); + + public void Add(IConnectorFactory factory) + { + _factories.Add(factory); + } - public void Add(IConnectorFactory factory) - { - _factories.Add(factory); - } - public void AddRange(IList connectorFactories) - { - _factories.AddRange(connectorFactories); - } - public bool Any(Func predicate) - { - return _factories.Any(x => predicate(x)); - } + public void AddRange(IList connectorFactories) + { + _factories.AddRange(connectorFactories); + } - public IConnector? CreateConnector(string connectionString) - { - return _factories.Select(x => x.CreateConnector(connectionString)) - .FirstOrDefault(x => x != null); - } + public bool Any(Func predicate) + { + return _factories.Exists(x => predicate(x)); + } - } + public IConnector? CreateConnector(string connectionString) + { + return _factories.Select(x => x.CreateConnector(connectionString)) + .FirstOrDefault(x => x != null); + } } diff --git a/Core/SmartIOT.Connector.Core/Factory/DeviceDriverFactory.cs b/Core/SmartIOT.Connector.Core/Factory/DeviceDriverFactory.cs index 267deb2..309a8ba 100644 --- a/Core/SmartIOT.Connector.Core/Factory/DeviceDriverFactory.cs +++ b/Core/SmartIOT.Connector.Core/Factory/DeviceDriverFactory.cs @@ -1,41 +1,34 @@ using SmartIOT.Connector.Core.Conf; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace SmartIOT.Connector.Core.Factory -{ - public class DeviceDriverFactory : IDeviceDriverFactory - { - private readonly List _factories = new List(); +namespace SmartIOT.Connector.Core.Factory; - public void Add(IDeviceDriverFactory factory) - { - _factories.Add(factory); - } +public class DeviceDriverFactory : IDeviceDriverFactory +{ + private readonly List _factories = new List(); - public void AddRange(IList deviceDriverFactories) - { - _factories.AddRange(deviceDriverFactories); - } + public void Add(IDeviceDriverFactory factory) + { + _factories.Add(factory); + } - public bool Any() => _factories.Any(); + public void AddRange(IList deviceDriverFactories) + { + _factories.AddRange(deviceDriverFactories); + } - public bool Any(Func predicate) => _factories.Any(predicate); + public bool Any() => _factories.Count > 0; - public IDeviceDriver? CreateDriver(DeviceConfiguration deviceConfiguration) - { - foreach (var factory in _factories) - { - var d = factory.CreateDriver(deviceConfiguration); - if (d != null) - return d; - } + public bool Any(Func predicate) => _factories.Exists(x => predicate.Invoke(x)); - return null; - } + public IDeviceDriver? CreateDriver(DeviceConfiguration deviceConfiguration) + { + foreach (var factory in _factories) + { + var d = factory.CreateDriver(deviceConfiguration); + if (d != null) + return d; + } - } + return null; + } } diff --git a/Core/SmartIOT.Connector.Core/Model/Device.cs b/Core/SmartIOT.Connector.Core/Model/Device.cs index e892640..710531c 100644 --- a/Core/SmartIOT.Connector.Core/Model/Device.cs +++ b/Core/SmartIOT.Connector.Core/Model/Device.cs @@ -190,13 +190,7 @@ internal void SetTagSynchronized(Tag tag, DateTime instant) maxPunti = GetMaxPoints(type); List list = GetTagsInRange(type, maxPunti - GetWeightMCM(type), maxPunti); - int min = int.MaxValue; - foreach (Tag t in list) - { - if (t.Points < min) - min = t.Points; - } - + int min = list.Count > 0 ? list.Min(x => x.Points) : int.MaxValue; if (min - GetWeightMCM(type) < 0) { riscalaturaPossibile = false; @@ -273,34 +267,33 @@ private int GetWeightMCM(TagType type) private int CalculateMCM(TagType type) { - int max = int.MinValue; + return FindLCM(_tags.Where(x => x.TagType == type).Select(x => x.Weight)); + } - foreach (Tag t in _tags) - { - if (t.TagType == type && t.Weight > max) - max = t.Weight; - } + private static int FindLCM(IEnumerable numbers) + { + int lcm = int.MinValue; - bool mcmTrovato = false; - while (!mcmTrovato) + foreach (var n in numbers) { - mcmTrovato = true; - - foreach (Tag t in _tags) - { - if (t.TagType == type && max % t.Weight != 0) - { - mcmTrovato = false; - break; - } - } + if (lcm == int.MinValue) + lcm = n; + else + lcm = Math.Abs(lcm * n) / FindGCD(lcm, n); + } - if (mcmTrovato) - break; + return lcm; + } - max++; + private static int FindGCD(int a, int b) + { + while (b != 0) + { + int temp = b; + b = a % b; + a = temp; } - return max; + return a; } } diff --git a/Core/SmartIOT.Connector.Core/Model/Tag.cs b/Core/SmartIOT.Connector.Core/Model/Tag.cs index 7cb4b4f..6e20227 100644 --- a/Core/SmartIOT.Connector.Core/Model/Tag.cs +++ b/Core/SmartIOT.Connector.Core/Model/Tag.cs @@ -48,11 +48,13 @@ public Tag(TagConfiguration tagConfiguration) /// public void RequestTagWrite(byte[] data, int startOffset) { +#pragma warning disable S2551 // Shared resources should not be used for locking: we purposefully lock on "this" to avoid races between tag-scheduler and services that request tag write. lock (this) { Array.Copy(data, 0, Data, startOffset - ByteOffset, data.Length); IsWriteSynchronizationRequested = true; } +#pragma warning restore S2551 // Shared resources should not be used for locking } /// diff --git a/Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs b/Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs index dd04a8b..b74bde1 100644 --- a/Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs +++ b/Core/SmartIOT.Connector.Core/Scheduler/TagSchedulerEngine.cs @@ -376,7 +376,7 @@ private TagScheduleEvent WriteTag(TagSchedule schedule) // del valore 0 sempre in DB22.DBX0.0, annullando in pratica la richiesta precedente. Il flag "needsWrite" rimane a true // tuttavia non c'è alcun cambiamento sul datablock che deve effettivamente essere scritto. List listBounds = ParseWriteBounds(device, tag); - if (!listBounds.Any()) + if (listBounds.Count == 0) { // se la lista dei bounds è vuota, interpreto la schedule come una scrittura completa // questo è usato anche per gestire il parametro rewritePeriod dei tag in scrittura: ogni tot millisecondi, vengono comunque scritti tutti diff --git a/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj b/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj index 5d02069..1af87fb 100644 --- a/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj +++ b/Core/SmartIOT.Connector.Core/SmartIOT.Connector.Core.csproj @@ -16,6 +16,10 @@ 1.0.0.0 + + + + diff --git a/Core/SmartIOT.Connector.Core/SmartIotConnectorBuilder.cs b/Core/SmartIOT.Connector.Core/SmartIotConnectorBuilder.cs index 6773c04..cbce1ef 100644 --- a/Core/SmartIOT.Connector.Core/SmartIotConnectorBuilder.cs +++ b/Core/SmartIOT.Connector.Core/SmartIotConnectorBuilder.cs @@ -1,4 +1,7 @@ -using SmartIOT.Connector.Core.Conf; +#pragma warning disable S3885 // "Assembly.Load" should be used + +using Microsoft.Extensions.DependencyInjection; +using SmartIOT.Connector.Core.Conf; using SmartIOT.Connector.Core.Factory; using SmartIOT.Connector.Core.Scheduler; using System.Reflection; @@ -11,6 +14,7 @@ public class SmartIotConnectorBuilder private readonly List _deviceDrivers = new List(); private bool _autoDiscoverConnectorFactory; private readonly List _connectors = new List(); + public ITimeService TimeService { get; private set; } = new TimeService(); public ISchedulerFactory SchedulerFactory { get; private set; } = new SchedulerFactory(); public SmartIotConnectorConfiguration? Configuration { get; private set; } @@ -78,36 +82,33 @@ public SmartIotConnectorBuilder WithTimeService(ITimeService timeService) return this; } - public SmartIotConnector Build() + public SmartIotConnector Build(IServiceProvider? serviceProvider = null) { if (Configuration == null) throw new InvalidOperationException("Error building module: Configuration is not set"); if (_autoDiscoverDeviceDriverFactory) - DeviceDriverFactory.AddRange(AutoDiscoverDeviceDriverFactories()); + DeviceDriverFactory.AddRange(AutoDiscoverDeviceDriverFactories(serviceProvider)); - if (!DeviceDriverFactory.Any() && !_deviceDrivers.Any()) - throw new ArgumentException($"Nessuna {nameof(IDeviceDriverFactory)} o {nameof(IDeviceDriver)} presente in configurazione"); + if (!DeviceDriverFactory.Any() && _deviceDrivers.Count == 0) + throw new ArgumentException($"No {nameof(IDeviceDriverFactory)} or {nameof(IDeviceDriver)} configured"); if (_autoDiscoverConnectorFactory) - ConnectorFactory.AddRange(AutoDiscoverConnectorFactories()); + ConnectorFactory.AddRange(AutoDiscoverConnectorFactories(serviceProvider)); - IList schedulers = BuildSchedulers(); - IList connectors = BuildConnectors(); + var schedulers = BuildSchedulers(); + var connectors = BuildConnectors(); return new SmartIotConnector(schedulers, connectors, Configuration.SchedulerConfiguration); } - private IList BuildConnectors() + private List BuildConnectors() { var list = new List(); foreach (var connectionString in Configuration!.ConnectorConnectionStrings) { - IConnector? connector = ConnectorFactory.CreateConnector(connectionString); - if (connector == null) - throw new ArgumentException($"Impossibile creare il connector: ConnectionString {connectionString} non riconosciuta."); - + IConnector? connector = ConnectorFactory.CreateConnector(connectionString) ?? throw new ArgumentException($"Error creating connector: ConnectionString {connectionString} not recognized."); list.Add(connector); } @@ -116,12 +117,12 @@ private IList BuildConnectors() return list; } - private IList BuildSchedulers() + private List BuildSchedulers() { // creating schedulers from configuration var drivers = new Dictionary(); var devices = new List(Configuration!.DeviceConfigurations); - if (devices.Any()) + if (devices.Count > 0) { foreach (var device in devices) { @@ -135,7 +136,7 @@ private IList BuildSchedulers() devices.RemoveAll(x => drivers.ContainsKey(x)); // rimuovo dalla lista temporanea le configurazioni che hanno ritornato un driver } - if (devices.Any()) + if (devices.Count > 0) throw new ArgumentException($"Error configuring SmartIotConnector: no scheduler factory found for these devices:\r\n{string.Join("\r\n", devices.Select(x => x.Name + ": " + x.ConnectionString))}"); var schedulers = drivers.Values.Select(x => SchedulerFactory.CreateScheduler(x.Name, x, TimeService, Configuration.SchedulerConfiguration)).ToList(); @@ -146,7 +147,7 @@ private IList BuildSchedulers() return schedulers; } - private IList AutoDiscoverDeviceDriverFactories() + private List AutoDiscoverDeviceDriverFactories(IServiceProvider? serviceProvider) { var list = new List(); @@ -158,20 +159,25 @@ private IList AutoDiscoverDeviceDriverFactories() foreach (var type in assembly.ExportedTypes) { - // le factory già presenti in elenco non li aggiungiamo nuovamente - bool alreadyAvailable = DeviceDriverFactory.Any(x => x.GetType() == type); - if (!alreadyAvailable - && typeof(IDeviceDriverFactory).IsAssignableFrom(type) + if (typeof(IDeviceDriverFactory).IsAssignableFrom(type) && type != typeof(DeviceDriverFactory) + && !DeviceDriverFactory.Any(x => x.GetType() == type) // do not add already added factories && !type.IsAbstract && type.IsClass && !type.IsInterface && type.IsPublic && type.IsVisible) { - var ctor = type.GetConstructor(Array.Empty()); - if (ctor != null) - list.Add((IDeviceDriverFactory)ctor.Invoke(Array.Empty())); + if (serviceProvider is not null) + { + list.Add((IDeviceDriverFactory)ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, type)); + } + else + { + var ctor = type.GetConstructor(Array.Empty()); + if (ctor != null) + list.Add((IDeviceDriverFactory)ctor.Invoke(Array.Empty())); + } } } } @@ -188,7 +194,7 @@ ex is BadImageFormatException return list; } - private IList AutoDiscoverConnectorFactories() + private List AutoDiscoverConnectorFactories(IServiceProvider? serviceProvider) { var list = new List(); @@ -200,20 +206,25 @@ private IList AutoDiscoverConnectorFactories() foreach (var type in assembly.ExportedTypes) { - // le factory già presenti in elenco non li aggiungiamo nuovamente - bool alreadyAvailable = ConnectorFactory.Any(x => x.GetType() == type); - if (!alreadyAvailable - && typeof(IConnectorFactory).IsAssignableFrom(type) + if (typeof(IConnectorFactory).IsAssignableFrom(type) && type != typeof(ConnectorFactory) + && !ConnectorFactory.Any(x => x.GetType() == type) // do not add already added factories && !type.IsAbstract && type.IsClass && !type.IsInterface && type.IsPublic && type.IsVisible) { - var ctor = type.GetConstructor(Array.Empty()); - if (ctor != null) - list.Add((IConnectorFactory)ctor.Invoke(Array.Empty())); + if (serviceProvider is not null) + { + list.Add((IConnectorFactory)ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, type)); + } + else + { + var ctor = type.GetConstructor(Array.Empty()); + if (ctor != null) + list.Add((IConnectorFactory)ctor.Invoke(Array.Empty())); + } } } } diff --git a/Core/SmartIOT.Connector.Core/SmartIotConnectorConfiguration.cs b/Core/SmartIOT.Connector.Core/SmartIotConnectorConfiguration.cs index 07201f4..ef0408e 100644 --- a/Core/SmartIOT.Connector.Core/SmartIotConnectorConfiguration.cs +++ b/Core/SmartIOT.Connector.Core/SmartIotConnectorConfiguration.cs @@ -5,8 +5,8 @@ namespace SmartIOT.Connector.Core; public class SmartIotConnectorConfiguration { - public IList ConnectorConnectionStrings { get; set; } = new List(); - public IList DeviceConfigurations { get; set; } = new List(); + public List ConnectorConnectionStrings { get; set; } = new List(); + public List DeviceConfigurations { get; set; } = new List(); public SchedulerConfiguration SchedulerConfiguration { get; set; } = new SchedulerConfiguration(); public static SmartIotConnectorConfiguration? FromJson(string json) diff --git a/Core/SmartIOT.Connector.Core/Util/ConnectionStringParser.cs b/Core/SmartIOT.Connector.Core/Util/ConnectionStringParser.cs index 555d9b3..b5daf4d 100644 --- a/Core/SmartIOT.Connector.Core/Util/ConnectionStringParser.cs +++ b/Core/SmartIOT.Connector.Core/Util/ConnectionStringParser.cs @@ -1,6 +1,6 @@ namespace SmartIOT.Connector.Core.Util; -public class ConnectionStringParser +public static class ConnectionStringParser { public static IDictionary ParseTokens(string connectionString) { diff --git a/Core/SmartIOT.Connector.Core/Util/CopyOnWriteArrayList.cs b/Core/SmartIOT.Connector.Core/Util/CopyOnWriteArrayList.cs index d964374..bcd1fba 100644 --- a/Core/SmartIOT.Connector.Core/Util/CopyOnWriteArrayList.cs +++ b/Core/SmartIOT.Connector.Core/Util/CopyOnWriteArrayList.cs @@ -101,8 +101,7 @@ public bool Contains(object? value) public void CopyTo(T[] array, int arrayIndex) { - if (array == null) - throw new ArgumentNullException(nameof(array)); + ArgumentNullException.ThrowIfNull(array); var data = _data; @@ -111,8 +110,7 @@ public void CopyTo(T[] array, int arrayIndex) public void CopyTo(Array array, int index) { - if (array == null) - throw new ArgumentNullException(nameof(array)); + ArgumentNullException.ThrowIfNull(array); var data = _data; Array.Copy(data, array, data.Length); diff --git a/Core/SmartIOT.Connector.DependencyInjection/AspNetCoreExtensions.cs b/Core/SmartIOT.Connector.DependencyInjection/AspNetCoreExtensions.cs new file mode 100644 index 0000000..bb8d835 --- /dev/null +++ b/Core/SmartIOT.Connector.DependencyInjection/AspNetCoreExtensions.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using SmartIOT.Connector.Core; +using SmartIOT.Connector.Core.Factory; + +namespace SmartIOT.Connector.DependencyInjection; + +public static class AspNetCoreExtensions +{ + public static IServiceCollection AddSmartIOTConnector(this IServiceCollection services, Action configure) + { + var builder = new SmartIotConnectorBuilder(); + + configure?.Invoke(builder); + + // add main stuffs + services.AddSingleton(builder); + services.AddSingleton(builder.Build); + + // expose more things on DI + services.AddSingleton(_ => builder.ConnectorFactory); + services.AddSingleton(_ => builder.DeviceDriverFactory); + services.AddSingleton(_ => builder.SchedulerFactory); + services.AddSingleton(_ => builder.TimeService); + + // add hosted service + services.AddSingleton(); + services.AddHostedService(sp => sp.GetRequiredService()); + + return services; + } + + public static IApplicationBuilder UseSmartIOTConnector(this IApplicationBuilder app, Action configure) + { + var connector = app.ApplicationServices.GetRequiredService(); + + configure.Invoke(connector); + + return app; + } +} diff --git a/Core/SmartIOT.Connector.DependencyInjection/SmartIOT.Connector.DependencyInjection.csproj b/Core/SmartIOT.Connector.DependencyInjection/SmartIOT.Connector.DependencyInjection.csproj new file mode 100644 index 0000000..ad6dfa0 --- /dev/null +++ b/Core/SmartIOT.Connector.DependencyInjection/SmartIOT.Connector.DependencyInjection.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/Core/SmartIOT.Connector.DependencyInjection/SmartIotConnectorHostedService.cs b/Core/SmartIOT.Connector.DependencyInjection/SmartIotConnectorHostedService.cs new file mode 100644 index 0000000..67b7051 --- /dev/null +++ b/Core/SmartIOT.Connector.DependencyInjection/SmartIotConnectorHostedService.cs @@ -0,0 +1,92 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using SmartIOT.Connector.Core; + +namespace SmartIOT.Connector.DependencyInjection; + +public class SmartIotConnectorHostedService : IHostedService +{ + private readonly SmartIotConnector _smartIotConnector; + private readonly ILogger _logger; + + public SmartIotConnectorHostedService(SmartIotConnector smartIotConnector, SmartIotConnectorBuilder builder, ILogger logger) + { + _smartIotConnector = smartIotConnector; + _logger = logger; + + _smartIotConnector.SchedulerStarting += (s, e) => _logger.LogInformation("{driver}: Scheduler starting", e.Scheduler.DeviceDriver.Name); + _smartIotConnector.SchedulerStopping += (s, e) => _logger.LogInformation("{driver}: Scheduler stopping", e.Scheduler.DeviceDriver.Name); + _smartIotConnector.SchedulerRestarting += (s, e) => _logger.LogInformation("{driver}: Scheduler restarting", e.DeviceDriver.Name); + _smartIotConnector.SchedulerRestarted += (s, e) => + { + if (e.IsSuccess) + _logger.LogInformation("{driver}: Scheduler restarted successfully", e.DeviceDriver.Name); + else + _logger.LogError("{driver}: Error during scheduler restart: {message}", e.DeviceDriver.Name, e.ErrorDescription); + }; + _smartIotConnector.TagReadEvent += (s, e) => + { + if (e.TagScheduleEvent.Data != null) + { + // data event + if (_logger.IsEnabled(LogLevel.Debug) && e.TagScheduleEvent.Data.Length > 0) + _logger.LogDebug("{driver}: {device}, {tag}: received data[{offset}..{endOffset}], size {size}", e.DeviceDriver.Name, e.TagScheduleEvent.Device.Name, e.TagScheduleEvent.Tag.TagId, e.TagScheduleEvent.StartOffset, e.TagScheduleEvent.StartOffset + e.TagScheduleEvent.Data.Length - 1, e.TagScheduleEvent.Data.Length); + } + else if (e.TagScheduleEvent.IsErrorNumberChanged) + { + // status changed + _logger.LogWarning("{driver}: {device}, {tag}: status changed {err} {message}", e.DeviceDriver.Name, e.TagScheduleEvent.Device.Name, e.TagScheduleEvent.Tag.TagId, e.TagScheduleEvent.ErrorNumber, e.TagScheduleEvent.Description); + } + }; + _smartIotConnector.TagWriteEvent += (s, e) => + { + if (e.TagScheduleEvent.Data is not null && _logger.IsEnabled(LogLevel.Debug)) + _logger.LogDebug("{driver}: {device}, {tag}: written data[{offset}..{endOffset}], size {size}", e.DeviceDriver.Name, e.TagScheduleEvent.Device.Name, e.TagScheduleEvent.Tag.TagId, e.TagScheduleEvent.StartOffset, e.TagScheduleEvent.StartOffset + e.TagScheduleEvent.Data.Length - 1, e.TagScheduleEvent.Data.Length); + }; + _smartIotConnector.ExceptionHandler += (s, e) => _logger.LogError(e.Exception, "Unexpected 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.."); + _smartIotConnector.Stopped += (s, e) => _logger.LogInformation("SmartIOT.Connector stopped"); + _smartIotConnector.ConnectorStarted += (s, e) => + { + _logger.LogInformation("{connector}: {message}", e.Connector.GetType().Name, e.Info); + }; + _smartIotConnector.ConnectorStopped += (s, e) => + { + _logger.LogInformation("{connector}: {message}", e.Connector.GetType().Name, e.Info); + }; + _smartIotConnector.ConnectorConnected += (s, e) => + { + _logger.LogInformation("{connector}: {message}", e.Connector.GetType().Name, e.Info); + }; + _smartIotConnector.ConnectorConnectionFailed += (s, e) => + { + _logger.LogWarning(e.Exception, "{connector}: {message}", e.Connector.GetType().Name, e.Info); + }; + _smartIotConnector.ConnectorDisconnected += (s, e) => + { + _logger.LogInformation("{connector}: {message}", e.Connector.GetType().Name, e.Info); + }; + _smartIotConnector.ConnectorException += (s, e) => + { + _logger.LogError(e.Exception, "{connector}: Unexpected exception: {message}", e.Connector.GetType().Name, e.Exception.Message); + }; + + if (builder.AutoDiscoveryExceptions.Any() && _logger.IsEnabled(LogLevel.Debug)) + { + string details = string.Join($"{Environment.NewLine}\t", builder.AutoDiscoveryExceptions.Select(x => x.Message)); + _logger.LogDebug("Error autodiscoverying dll: [{nl}{details}{nl}]", Environment.NewLine, details, Environment.NewLine); + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return _smartIotConnector.StartAsync(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return _smartIotConnector.StopAsync(); + } +} diff --git a/Core/SmartIOT.Connector.Messages/Serializers/JsonSingleMessageSerializer.cs b/Core/SmartIOT.Connector.Messages/Serializers/JsonSingleMessageSerializer.cs index ee330e0..fadbe67 100644 --- a/Core/SmartIOT.Connector.Messages/Serializers/JsonSingleMessageSerializer.cs +++ b/Core/SmartIOT.Connector.Messages/Serializers/JsonSingleMessageSerializer.cs @@ -6,11 +6,6 @@ public class JsonSingleMessageSerializer : ISingleMessageSerializer { private readonly JsonSerializerOptions _options; - public JsonSingleMessageSerializer() - : this(CreateDefaultSerializerOptions()) - { - } - private static JsonSerializerOptions CreateDefaultSerializerOptions() { var options = new JsonSerializerOptions() @@ -20,6 +15,11 @@ private static JsonSerializerOptions CreateDefaultSerializerOptions() return options; } + public JsonSingleMessageSerializer() + : this(CreateDefaultSerializerOptions()) + { + } + public JsonSingleMessageSerializer(JsonSerializerOptions options) { _options = options; @@ -30,8 +30,8 @@ public byte[] SerializeMessage(object message) return JsonSerializer.SerializeToUtf8Bytes(message, _options); } - public T? DeserializeMessage(byte[] message) + public T? DeserializeMessage(byte[] bytes) { - return JsonSerializer.Deserialize(message, _options); + return JsonSerializer.Deserialize(bytes, _options); } } diff --git a/Core/SmartIOT.Connector.Prometheus/ExtensionImpl.cs b/Core/SmartIOT.Connector.Prometheus/ExtensionImpl.cs index 6575d40..b133dd4 100644 --- a/Core/SmartIOT.Connector.Prometheus/ExtensionImpl.cs +++ b/Core/SmartIOT.Connector.Prometheus/ExtensionImpl.cs @@ -7,7 +7,6 @@ namespace SmartIOT.Connector.Prometheus; internal class ExtensionImpl { private readonly bool _isManagedServer; - private readonly string _metricsPrefix; private readonly Gauge _synchronizationAvgTimeSeconds; private readonly Gauge _synchronizationCount; private readonly Gauge _writesCount; @@ -17,26 +16,25 @@ internal class ExtensionImpl public ExtensionImpl(IMetricServer metricServer, bool isManagedServer, string metricsPrefix) { _isManagedServer = isManagedServer; - _metricsPrefix = metricsPrefix; - if (string.IsNullOrWhiteSpace(_metricsPrefix)) - _metricsPrefix = "smartiot_connector_"; - if (!_metricsPrefix.EndsWith("_")) - _metricsPrefix += "_"; + if (string.IsNullOrWhiteSpace(metricsPrefix)) + metricsPrefix = "smartiot_connector_"; + if (!metricsPrefix.EndsWith('_')) + metricsPrefix += "_"; MetricServer = metricServer; - _synchronizationAvgTimeSeconds = Metrics.CreateGauge($"{_metricsPrefix}synchronization_avg_time_seconds", "This metric represents the average time elapsed to synchronize a tag", new GaugeConfiguration() + _synchronizationAvgTimeSeconds = Metrics.CreateGauge($"{metricsPrefix}synchronization_avg_time_seconds", "This metric represents the average time elapsed to synchronize a tag", new GaugeConfiguration() { LabelNames = new[] { "DeviceId", "TagId" }, SuppressInitialValue = true, }); - _synchronizationCount = Metrics.CreateGauge($"{_metricsPrefix}synchronization_count", "This metric represents the number of synchronization on the tag", new GaugeConfiguration() + _synchronizationCount = Metrics.CreateGauge($"{metricsPrefix}synchronization_count", "This metric represents the number of synchronization on the tag", new GaugeConfiguration() { LabelNames = new[] { "DeviceId", "TagId" }, SuppressInitialValue = true, }); - _writesCount = Metrics.CreateGauge($"{_metricsPrefix}writes_count", "This metric represents the number of writes on the tag", new GaugeConfiguration() + _writesCount = Metrics.CreateGauge($"{metricsPrefix}writes_count", "This metric represents the number of writes on the tag", new GaugeConfiguration() { LabelNames = new[] { "DeviceId", "TagId" }, SuppressInitialValue = true, diff --git a/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs b/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs index 8219589..9fac066 100644 --- a/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs +++ b/Core/SmartIOT.Connector.RestApi/Controllers/V1/DeviceController.cs @@ -70,7 +70,7 @@ public IActionResult AddDevice([FromBody] DeviceConfiguration deviceConfiguratio _deviceService.AddDevice(deviceConfiguration); return Ok(); } - catch (ApplicationException ex) + catch (DeviceException ex) { return BadRequest(ex.Message); } @@ -92,7 +92,7 @@ public IActionResult RemoveDevice(string deviceId) _deviceService.RemoveDevice(deviceId); return Ok(); } - catch (ApplicationException ex) + catch (DeviceException ex) { return BadRequest(ex.Message); } @@ -179,7 +179,7 @@ public IActionResult AddTag(string deviceId, [FromBody] TagConfiguration tagConf _deviceService.AddTag(deviceId, tagConfiguration); return Ok(); } - catch (ApplicationException ex) + catch (DeviceException ex) { return BadRequest(ex.Message); } @@ -202,7 +202,7 @@ public IActionResult RemoveTag(string deviceId, string tagId) _deviceService.RemoveTag(deviceId, tagId); return Ok(); } - catch (ApplicationException ex) + catch (DeviceException ex) { return BadRequest(ex.Message); } @@ -225,7 +225,7 @@ public IActionResult UpdateTag(string deviceId, [FromBody] TagConfiguration tagC _deviceService.UpdateTag(deviceId, tagConfiguration); return Ok(); } - catch (ApplicationException ex) + catch (DeviceException ex) { return BadRequest(ex.Message); } @@ -272,7 +272,7 @@ public IActionResult SetTagData(string deviceId, string tagId, [FromBody] TagDat _deviceService.SetTagData(deviceId, tagId, tagData); return Ok(); } - catch (ApplicationException ex) + catch (DeviceException ex) { return BadRequest(ex.Message); } diff --git a/Core/SmartIOT.Connector.RestApi/Services/DeviceException.cs b/Core/SmartIOT.Connector.RestApi/Services/DeviceException.cs new file mode 100644 index 0000000..b047be4 --- /dev/null +++ b/Core/SmartIOT.Connector.RestApi/Services/DeviceException.cs @@ -0,0 +1,5 @@ +namespace SmartIOT.Connector.RestApi.Services; + +public class DeviceException(string? message) : ApplicationException(message) +{ +} diff --git a/Core/SmartIOT.Connector.RestApi/Services/DeviceService.cs b/Core/SmartIOT.Connector.RestApi/Services/DeviceService.cs index 98eee9f..2f53873 100644 --- a/Core/SmartIOT.Connector.RestApi/Services/DeviceService.cs +++ b/Core/SmartIOT.Connector.RestApi/Services/DeviceService.cs @@ -54,7 +54,7 @@ public void AddDevice(DeviceConfiguration deviceConfiguration) { var driver = _deviceDriverFactory.CreateDriver(deviceConfiguration); if (driver == null) - throw new ApplicationException($"DeviceConfiguration not valid {deviceConfiguration.ConnectionString}"); + throw new DeviceException($"DeviceConfiguration not valid {deviceConfiguration.ConnectionString}"); _smartIotConnector.AddScheduler(_schedulerFactory.CreateScheduler(driver.Name, driver, _timeService, _smartIotConnector.SchedulerConfiguration)); } @@ -65,7 +65,7 @@ public void RemoveDevice(string deviceId) .FirstOrDefault(x => x.Device.DeviceId.Equals(deviceId, StringComparison.InvariantCultureIgnoreCase)); if (scheduler == null) - throw new ApplicationException($"Device {deviceId} does not exists"); + throw new DeviceException($"Device {deviceId} does not exists"); _smartIotConnector.RemoveScheduler(scheduler); } @@ -93,17 +93,17 @@ public void SetTagData(string deviceId, string tagId, TagData tagData) .FirstOrDefault(x => x.DeviceId.Equals(deviceId, StringComparison.InvariantCultureIgnoreCase)); if (device == null) - throw new ApplicationException($"Device {deviceId} does not exists"); + throw new DeviceException($"Device {deviceId} does not exists"); var tag = device.Tags.FirstOrDefault(x => x.TagId.Equals(tagId, StringComparison.InvariantCultureIgnoreCase)); if (tag == null) - throw new ApplicationException($"Tag {tagId} does not exists"); + throw new DeviceException($"Tag {tagId} does not exists"); if (tagData.StartOffset < tag.ByteOffset) - throw new ApplicationException($"Requested StartOffset {tagData.StartOffset} < {tag.ByteOffset}"); + throw new DeviceException($"Requested StartOffset {tagData.StartOffset} < {tag.ByteOffset}"); if (tagData.StartOffset + tagData.Bytes.Length > tag.ByteOffset + tag.Size) - throw new ApplicationException($"Data packet is too big. Requested: [{tagData.StartOffset}..{tagData.StartOffset + tagData.Bytes.Length - 1}], accepted: [{tag.ByteOffset}..{tag.ByteOffset + tag.Size - 1}]"); + throw new DeviceException($"Data packet is too big. Requested: [{tagData.StartOffset}..{tagData.StartOffset + tagData.Bytes.Length - 1}], accepted: [{tag.ByteOffset}..{tag.ByteOffset + tag.Size - 1}]"); tag.RequestTagWrite(tagData.Bytes, tagData.StartOffset); } @@ -135,10 +135,10 @@ public void AddTag(string deviceId, TagConfiguration tagConfiguration) .FirstOrDefault(x => x.DeviceId.Equals(deviceId, StringComparison.InvariantCultureIgnoreCase)); if (device == null) - throw new ApplicationException($"Device {deviceId} does not exists"); + throw new DeviceException($"Device {deviceId} does not exists"); if (device.Tags.Any(x => x.TagId.Equals(tagConfiguration.TagId, StringComparison.InvariantCultureIgnoreCase))) - throw new ApplicationException($"Tag {tagConfiguration.TagId} already exists"); + throw new DeviceException($"Tag {tagConfiguration.TagId} already exists"); device.AddTag(tagConfiguration); } @@ -150,11 +150,11 @@ public void RemoveTag(string deviceId, string tagId) .FirstOrDefault(x => x.DeviceId.Equals(deviceId, StringComparison.InvariantCultureIgnoreCase)); if (device == null) - throw new ApplicationException($"Device {deviceId} does not exists"); + throw new DeviceException($"Device {deviceId} does not exists"); var tag = device.Tags.FirstOrDefault(x => x.TagId.Equals(tagId, StringComparison.InvariantCultureIgnoreCase)); if (tag == null) - throw new ApplicationException($"Tag {tagId} does not exists"); + throw new DeviceException($"Tag {tagId} does not exists"); device.RemoveTag(tag); } @@ -166,13 +166,13 @@ public void UpdateTag(string deviceId, TagConfiguration tagConfiguration) .FirstOrDefault(x => x.DeviceId.Equals(deviceId, StringComparison.InvariantCultureIgnoreCase)); if (device == null) - throw new ApplicationException($"Device {deviceId} does not exists"); + throw new DeviceException($"Device {deviceId} does not exists"); var oldTag = device.Tags.FirstOrDefault(x => x.TagId.Equals(tagConfiguration.TagId, StringComparison.InvariantCultureIgnoreCase)); if (oldTag == null) - throw new ApplicationException($"Tag {tagConfiguration.TagId} does not exists"); + throw new DeviceException($"Tag {tagConfiguration.TagId} does not exists"); if (!device.UpdateTag(tagConfiguration)) - throw new ApplicationException($"Tag {tagConfiguration.TagId} does not exists"); + throw new DeviceException($"Tag {tagConfiguration.TagId} does not exists"); } } diff --git a/Devices/SmartIOT.Connector.Plc.S7Net/S7NetDriverFactory.cs b/Devices/SmartIOT.Connector.Plc.S7Net/S7NetDriverFactory.cs index f2100b8..35f0ce9 100644 --- a/Devices/SmartIOT.Connector.Plc.S7Net/S7NetDriverFactory.cs +++ b/Devices/SmartIOT.Connector.Plc.S7Net/S7NetDriverFactory.cs @@ -8,7 +8,7 @@ public class S7NetDriverFactory : IDeviceDriverFactory { private static bool AcceptConnectionString(string connectionString) { - return connectionString?.ToLower().StartsWith("s7net://") ?? false; + return connectionString?.StartsWith("s7net://", StringComparison.InvariantCultureIgnoreCase) ?? false; } public IDeviceDriver? CreateDriver(DeviceConfiguration deviceConfiguration) diff --git a/Devices/SmartIOT.Connector.Plc.Snap7/README.md b/Devices/SmartIOT.Connector.Plc.Snap7/README.md index 56237a1..207ad84 100644 --- a/Devices/SmartIOT.Connector.Plc.Snap7/README.md +++ b/Devices/SmartIOT.Connector.Plc.Snap7/README.md @@ -1,6 +1,6 @@ # Snap7 Device configuration -This project provides a bridge to make SmartIOT.Connector able to use the [Snap7 library](http://snap7.sourceforge.net) by Dave Nardella (and in particular Sharp7) to connect to a Siemens PLC and to exchange data defined in its datablocks. +This project provides a bridge to make SmartIOT.Connector able to use the [Snap7 library](http://snap7.sourceforge.net) by Davide Nardella (and in particular Sharp7) to connect to a Siemens PLC and to exchange data defined in its datablocks. The supported connection string is as follows (square brackets for optional parameters):
snap7://Ip=<plc ip address>;Rack=<rack>;Slot=<slot>[;Type=<type>]
diff --git a/Devices/SmartIOT.Connector.Plc.Snap7/Sharp7.cs b/Devices/SmartIOT.Connector.Plc.Snap7/Sharp7.cs index ea31394..ecf5c11 100644 --- a/Devices/SmartIOT.Connector.Plc.Snap7/Sharp7.cs +++ b/Devices/SmartIOT.Connector.Plc.Snap7/Sharp7.cs @@ -2,6 +2,7 @@ #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8073 // The result of the expression is always the same since a value of this type is never equal to 'null' #pragma warning disable CS0414 // The field is assigned but its value is never used +#pragma warning disable CS9125 // Attribute parameter 'SizeConst' must be specified. #pragma warning disable IDE0017 // Simplify object initialization #pragma warning disable IDE0044 // Add readonly modifier #pragma warning disable IDE0051 // Remove unused private members @@ -10,6 +11,29 @@ #pragma warning disable IDE0066 // Convert switch statement to expression #pragma warning disable IDE0054 // Use compound assignment #pragma warning disable IDE0034 // Simplify 'default' expression +#pragma warning disable IDE0300 // Simplify collection initialization +#pragma warning disable IDE0031 // Use null propagation +#pragma warning disable S4144 // Methods should not have identical implementations +#pragma warning disable S108 // Nested blocks of code should not be left empty +#pragma warning disable S1905 // Redundant casts should not be used +#pragma warning disable S3626 // Jump statements should not be redundant +#pragma warning disable S1116 // Empty statements should be removed +#pragma warning disable S1104 // Fields should not have public accessibility +#pragma warning disable S2933 // Fields that are only assigned in the constructor should be "readonly" +#pragma warning disable S1871 // Two branches in a conditional structure should not have exactly the same implementation +#pragma warning disable S2292 // Trivial properties should be auto-implemented +#pragma warning disable S1172 // Unused method parameters should be removed +#pragma warning disable S1854 // Unused assignments should be removed +#pragma warning disable S101 // Types should be named in PascalCase +#pragma warning disable S1144 // Unused private types or members should be removed +#pragma warning disable S6562 // Always set the "DateTimeKind" when creating new "DateTime" instances +#pragma warning disable S1481 // Unused local variables should be removed +#pragma warning disable S3878 // Arrays should not be created for params parameters +#pragma warning disable S3241 // Methods should not return values that are never used +#pragma warning disable S2971 // "IEnumerable" LINQs should be simplified +#pragma warning disable S2486 // Generic exceptions should not be ignored +#pragma warning disable S125 // Sections of code should not be commented out +#pragma warning disable S2589 // Boolean expressions should not be gratuitous /*=============================================================================| | PROJECT Sharp7 1.1.0 | |==============================================================================| @@ -341,7 +365,7 @@ public int ConnectTimeout #if !WINDOWS_UWP && !NETFX_CORE -class MsgSocket +internal class MsgSocket { private Socket TCPSocket; private int _ReadTimeout = 2000; @@ -673,10 +697,10 @@ public class S7Timer { #region S7Timer - TimeSpan pt; - TimeSpan et; - bool input = false; - bool q = false; + private TimeSpan pt; + private TimeSpan et; + private bool input = false; + private bool q = false; public S7Timer(byte[] buff, int position) { @@ -1644,26 +1668,26 @@ public class S7Client public static readonly int MaxVars = 20; // Result transport size - const byte TS_ResBit = 0x03; - - const byte TS_ResByte = 0x04; - const byte TS_ResInt = 0x05; - const byte TS_ResReal = 0x07; - const byte TS_ResOctet = 0x09; - - const ushort Code7Ok = 0x0000; - const ushort Code7AddressOutOfRange = 0x0005; - const ushort Code7InvalidTransportSize = 0x0006; - const ushort Code7WriteDataSizeMismatch = 0x0007; - const ushort Code7ResItemNotAvailable = 0x000A; - const ushort Code7ResItemNotAvailable1 = 0xD209; - const ushort Code7InvalidValue = 0xDC01; - const ushort Code7NeedPassword = 0xD241; - const ushort Code7InvalidPassword = 0xD602; - const ushort Code7NoPasswordToClear = 0xD604; - const ushort Code7NoPasswordToSet = 0xD605; - const ushort Code7FunNotAvailable = 0x8104; - const ushort Code7DataOverPDU = 0x8500; + private const byte TS_ResBit = 0x03; + + private const byte TS_ResByte = 0x04; + private const byte TS_ResInt = 0x05; + private const byte TS_ResReal = 0x07; + private const byte TS_ResOctet = 0x09; + + private const ushort Code7Ok = 0x0000; + private const ushort Code7AddressOutOfRange = 0x0005; + private const ushort Code7InvalidTransportSize = 0x0006; + private const ushort Code7WriteDataSizeMismatch = 0x0007; + private const ushort Code7ResItemNotAvailable = 0x000A; + private const ushort Code7ResItemNotAvailable1 = 0xD209; + private const ushort Code7InvalidValue = 0xDC01; + private const ushort Code7NeedPassword = 0xD241; + private const ushort Code7InvalidPassword = 0xD602; + private const ushort Code7NoPasswordToClear = 0xD604; + private const ushort Code7NoPasswordToSet = 0xD605; + private const ushort Code7FunNotAvailable = 0x8104; + private const ushort Code7DataOverPDU = 0x8500; // Client Connection Type public static readonly UInt16 CONNTYPE_PG = 0x01; // Connect to the PLC as a PG @@ -1791,7 +1815,7 @@ public struct S7Protection #region [S7 Telegrams] // ISO Connection Request telegram (contains also ISO Header and COTP Header) - byte[] ISO_CR = { + private byte[] ISO_CR = { // TPKT (RFC1006 Header) 0x03, // RFC 1006 ID (3) 0x00, // Reserved, always 0 @@ -1819,14 +1843,14 @@ public struct S7Protection }; // TPKT + ISO COTP Header (Connection Oriented Transport Protocol) - byte[] TPKT_ISO = { // 7 bytes + private byte[] TPKT_ISO = { // 7 bytes 0x03,0x00, 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) 0x02,0xf0,0x80 // COTP (see above for info) }; // S7 PDU Negotiation Telegram (contains also ISO Header and COTP Header) - byte[] S7_PN = { + private byte[] S7_PN = { 0x03, 0x00, 0x00, 0x19, 0x02, 0xf0, 0x80, // TPKT + COTP (see above for info) 0x32, 0x01, 0x00, 0x00, @@ -1837,7 +1861,7 @@ public struct S7Protection }; // S7 Read/Write Request Header (contains also ISO Header and COTP Header) - byte[] S7_RW = { // 31-35 bytes + private byte[] S7_RW = { // 31-35 bytes 0x03,0x00, 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) 0x02,0xf0, 0x80, // COTP (see above for info) @@ -1867,7 +1891,7 @@ public struct S7Protection private static int Size_WR = 35; // Header Size when Writing // S7 Variable MultiRead Header - byte[] S7_MRD_HEADER = { + private byte[] S7_MRD_HEADER = { 0x03,0x00, 0x00,0x1f, // Telegram Length 0x02,0xf0, 0x80, // COTP (see above for info) @@ -1882,7 +1906,7 @@ public struct S7Protection }; // S7 Variable MultiRead Item - byte[] S7_MRD_ITEM = { + private byte[] S7_MRD_ITEM = { 0x12, // Var spec. 0x0a, // Length of remaining bytes 0x10, // Syntax ID @@ -1894,7 +1918,7 @@ public struct S7Protection }; // S7 Variable MultiWrite Header - byte[] S7_MWR_HEADER = { + private byte[] S7_MWR_HEADER = { 0x03,0x00, 0x00,0x1f, // Telegram Length 0x02,0xf0, 0x80, // COTP (see above for info) @@ -1909,7 +1933,7 @@ public struct S7Protection }; // S7 Variable MultiWrite Item (Param) - byte[] S7_MWR_PARAM = { + private byte[] S7_MWR_PARAM = { 0x12, // Var spec. 0x0a, // Length of remaining bytes 0x10, // Syntax ID @@ -1921,7 +1945,7 @@ public struct S7Protection }; // SZL First telegram request - byte[] S7_SZL_FIRST = { + private byte[] S7_SZL_FIRST = { 0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, @@ -1936,7 +1960,7 @@ public struct S7Protection }; // SZL Next telegram request - byte[] S7_SZL_NEXT = { + private byte[] S7_SZL_NEXT = { 0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x06, @@ -1949,7 +1973,7 @@ public struct S7Protection }; // Get Date/Time request - byte[] S7_GET_DT = { + private byte[] S7_GET_DT = { 0x03, 0x00, 0x00, 0x1d, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x38, @@ -1961,7 +1985,7 @@ public struct S7Protection }; // Set Date/Time command - byte[] S7_SET_DT = { + private byte[] S7_SET_DT = { 0x03, 0x00, 0x00, 0x27, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x89, @@ -1981,7 +2005,7 @@ public struct S7Protection }; // S7 Set Session Password - byte[] S7_SET_PWD = { + private byte[] S7_SET_PWD = { 0x03, 0x00, 0x00, 0x25, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x27, @@ -1996,7 +2020,7 @@ public struct S7Protection }; // S7 Clear Session Password - byte[] S7_CLR_PWD = { + private byte[] S7_CLR_PWD = { 0x03, 0x00, 0x00, 0x1d, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x29, @@ -2008,7 +2032,7 @@ public struct S7Protection }; // S7 STOP request - byte[] S7_STOP = { + private byte[] S7_STOP = { 0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x0e, @@ -2021,7 +2045,7 @@ public struct S7Protection }; // S7 HOT Start request - byte[] S7_HOT_START = { + private byte[] S7_HOT_START = { 0x03, 0x00, 0x00, 0x25, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x0c, @@ -2035,7 +2059,7 @@ public struct S7Protection }; // S7 COLD Start request - byte[] S7_COLD_START = { + private byte[] S7_COLD_START = { 0x03, 0x00, 0x00, 0x27, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x0f, @@ -2048,13 +2072,13 @@ public struct S7Protection 0x52, 0x41, 0x4d }; - const byte pduStart = 0x28; // CPU start - const byte pduStop = 0x29; // CPU stop - const byte pduAlreadyStarted = 0x02; // CPU already in run mode - const byte pduAlreadyStopped = 0x07; // CPU already in stop mode + private const byte pduStart = 0x28; // CPU start + private const byte pduStop = 0x29; // CPU stop + private const byte pduAlreadyStarted = 0x02; // CPU already in run mode + private const byte pduAlreadyStopped = 0x07; // CPU already in stop mode // S7 Get PLC Status - byte[] S7_GET_STAT = { + private byte[] S7_GET_STAT = { 0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x2c, @@ -2067,7 +2091,7 @@ public struct S7Protection }; // S7 Get Block Info Request Header (contains also ISO Header and COTP Header) - byte[] S7_BI = { + private byte[] S7_BI = { 0x03, 0x00, 0x00, 0x25, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x05, @@ -2082,7 +2106,7 @@ public struct S7Protection }; // S7 List Blocks Request Header - byte[] S7_LIST_BLOCKS = { + private byte[] S7_LIST_BLOCKS = { 0x03, 0x00, 0x00, 0x1d, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x00, @@ -2094,7 +2118,7 @@ public struct S7Protection }; // S7 List Blocks Of Type Request Header - byte[] S7_LIST_BLOCKS_OF_TYPE = { + private byte[] S7_LIST_BLOCKS_OF_TYPE = { 0x03, 0x00, 0x00, 0x1f, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x00, @@ -4369,7 +4393,7 @@ public int DoubleFromByteArr(byte[] data, int position) } // S7 Get Force Values frame 1 - byte[] S7_FORCE_VAL1 = { + private byte[] S7_FORCE_VAL1 = { 0x03, 0x00, 0x00, 0x3d, 0x02, 0xf0 ,0x80, 0x32, 0x07, 0x00, 0x00, 0x07, @@ -4389,7 +4413,7 @@ public int DoubleFromByteArr(byte[] data, int position) }; // S7 Get Force Values frame 2 (300 series ) - byte[] S7_FORCE_VAL300 = { + private byte[] S7_FORCE_VAL300 = { 0x03, 0x00, 0x00, 0x3b, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x0c, @@ -4408,7 +4432,7 @@ public int DoubleFromByteArr(byte[] data, int position) }; // S7 Get Force Values frame 2 (400 series ) - byte[] S7_FORCE_VAL400 = { + private byte[] S7_FORCE_VAL400 = { 0x03, 0x00, 0x00, 0x3b, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x0c, @@ -4555,7 +4579,7 @@ public class SymbolTableRecord #region [S7 DriveES Telegrams] // S7 DriveES Read/Write Request Header (contains also ISO Header and COTP Header) - byte[] S7_DrvRW = { // 31-35 bytes + private byte[] S7_DrvRW = { // 31-35 bytes 0x03,0x00, 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) 0x02,0xf0, 0x80, // COTP (see above for info) @@ -4582,7 +4606,7 @@ public class SymbolTableRecord }; // S7 Drv Variable MultiRead Header - byte[] S7Drv_MRD_HEADER = { + private byte[] S7Drv_MRD_HEADER = { 0x03,0x00, 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) 0x02,0xf0, 0x80, // COTP (see above for info) @@ -4597,7 +4621,7 @@ public class SymbolTableRecord }; // S7 Drv Variable MultiRead Item - byte[] S7Drv_MRD_ITEM = + private byte[] S7Drv_MRD_ITEM = { 0x12, // Var spec. 0x0a, // Length of remaining bytes @@ -4610,7 +4634,7 @@ public class SymbolTableRecord }; // S7 Drv Variable MultiWrite Header - byte[] S7Drv_MWR_HEADER = { + private byte[] S7Drv_MWR_HEADER = { 0x03,0x00, 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) 0x02,0xf0, 0x80, // COTP (see above for info) @@ -4625,7 +4649,7 @@ public class SymbolTableRecord }; // S7 Drv Variable MultiWrite Item - byte[] S7Drv_MWR_PARAM = + private byte[] S7Drv_MWR_PARAM = { 0x12, // Var spec. 0x0a, // Length of remaining bytes @@ -4999,7 +5023,7 @@ public int DrvConnectTo(string Address, int Rack, int Slot) private static int Size_NckWR = 33; // Header Size when Writing // S7 NCK Read/Write Request Header (contains also ISO Header and COTP Header) - byte[] S7_NckRW = { // 31-35 bytes + private byte[] S7_NckRW = { // 31-35 bytes 0x03,0x00, 0x00,0x1d, // Telegram Length (Data Size + 29 or 33) 0x02,0xf0, 0x80, // COTP (see above for info) @@ -5026,7 +5050,7 @@ public int DrvConnectTo(string Address, int Rack, int Slot) }; // S7 Nck Variable MultiRead Header - byte[] S7Nck_MRD_HEADER = { + private byte[] S7Nck_MRD_HEADER = { 0x03,0x00, 0x00,0x1d, // Telegram Length (Data Size + 29 or 33) 0x02,0xf0, 0x80, // COTP (see above for info) @@ -5041,7 +5065,7 @@ public int DrvConnectTo(string Address, int Rack, int Slot) }; // S7 Nck Variable MultiRead Item - byte[] S7Nck_MRD_ITEM = { + private byte[] S7Nck_MRD_ITEM = { 0x12, // Var spec. 0x08, // Length of remaining bytes 0x82, // Syntax ID @@ -5053,7 +5077,7 @@ public int DrvConnectTo(string Address, int Rack, int Slot) }; // S7 Nck Variable MultiWrite Header - byte[] S7Nck_MWR_HEADER = { + private byte[] S7Nck_MWR_HEADER = { 0x03,0x00, 0x00,0x1d, // Telegram Length (Data Size + 29 or 33) 0x02,0xf0, 0x80, // COTP (see above for info) @@ -5068,7 +5092,7 @@ public int DrvConnectTo(string Address, int Rack, int Slot) }; // S7 Nck Variable MultiWrite Item - byte[] S7Nck_MWR_PARAM = { + private byte[] S7Nck_MWR_PARAM = { 0x12, // Var spec. 0x08, // Length of remaining bytes 0x82, // Syntax ID diff --git a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs index 706208c..f56e208 100644 --- a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs +++ b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs @@ -11,7 +11,7 @@ public class Snap7Driver : IDeviceDriver public string Name => $"{nameof(Snap7Driver)}.{Device.Name}"; public Device Device { get; } - private byte[] _tmp; + private readonly byte[] _tmp; public Snap7Driver(Snap7Plc plc) { diff --git a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7DriverFactory.cs b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7DriverFactory.cs index 5c7bcb9..01091f5 100644 --- a/Devices/SmartIOT.Connector.Plc.Snap7/Snap7DriverFactory.cs +++ b/Devices/SmartIOT.Connector.Plc.Snap7/Snap7DriverFactory.cs @@ -8,7 +8,7 @@ public class Snap7DriverFactory : IDeviceDriverFactory { private static bool AcceptConnectionString(string connectionString) { - return connectionString?.ToLower().StartsWith("snap7://") ?? false; + return connectionString?.StartsWith("snap7://", StringComparison.InvariantCultureIgnoreCase) ?? false; } public IDeviceDriver? CreateDriver(DeviceConfiguration deviceConfiguration) diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/README.md b/Devices/SmartIOT.Connector.Plc.SnapModBus/README.md index 495e462..faf060e 100644 --- a/Devices/SmartIOT.Connector.Plc.SnapModBus/README.md +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/README.md @@ -1,6 +1,6 @@ # 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. +This project provides a bridge to make SmartIOT.Connector able to use the [SnapModBus library](https://snapmodbus.sourceforge.io/) by Davide 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): diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapMB.net.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapMB.net.cs index a4d4cdc..bfccc35 100644 --- a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapMB.net.cs +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapMB.net.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; +#pragma warning disable S1118 // Utility classes should not have public constructors +#pragma warning disable S101 // Types should be named in PascalCase + using System.Text; using System.Runtime.InteropServices; -using System.Net; -using System.Reflection.Metadata; namespace SnapModbus { diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs index 22373a4..c756f38 100644 --- a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs @@ -1,7 +1,6 @@ using SmartIOT.Connector.Core; using SmartIOT.Connector.Core.Model; using SnapModbus; -using System.Runtime.InteropServices; namespace SmartIOT.Connector.Plc.SnapModBus; @@ -10,7 +9,7 @@ public class SnapModBusDriver : IDeviceDriver public string Name => $"{nameof(SnapModBusDriver)}.{Device.Name}"; public Device Device { get; } - private ushort[] _tmp; + private readonly ushort[] _tmp; public SnapModBusDriver(SnapModBusNode node) { diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriverFactory.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriverFactory.cs index 9626130..677be2e 100644 --- a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriverFactory.cs +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriverFactory.cs @@ -8,7 +8,7 @@ public class SnapModBusDriverFactory : IDeviceDriverFactory { private static bool AcceptConnectionString(string connectionString) { - return connectionString?.ToLower().StartsWith("snapmodbus://") ?? false; + return connectionString?.StartsWith("snapmodbus://", StringComparison.InvariantCultureIgnoreCase) ?? false; } public IDeviceDriver? CreateDriver(DeviceConfiguration deviceConfiguration) diff --git a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNode.cs b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNode.cs index d7fe4a2..86c2713 100644 --- a/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNode.cs +++ b/Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusNode.cs @@ -1,13 +1,9 @@ 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; } diff --git a/README.md b/README.md index ff720a4..b69efe4 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,27 @@ smartiot.Start(); smartiot.Stop(); ``` +otherwise, you can add SmartIOT.Connector to DI container with [these extension methods](./SmartIOT.Connector.DependencyInjection/AspNetCoreExtensions.cs): + +```csharp +// this method will start an IHostedService running SmartIOT.Connector +builder.Services.AddSmartIOTConnector(cfg => +{ + // configure here. + cfg.WithAutoDiscoverDeviceDriverFactories() + .WithAutoDiscoverConnectorFactories() + .WithConfigurationJsonFilePath("smartiot-config.json"); +}); + +var app = builder.Build(); + +// you can configure further.. +app.UseSmartIOTConnector(smartIotConnector => +{ + smartIotConnector.AddPrometheus(conf); // for example, adding Prometheus here.. +}); +``` + ## Documentation - [Configuration guide](./Docs/Configuration.md) @@ -128,7 +149,7 @@ I will do my best to keep the interfaces stable, but there are possibilities to ## Roadmap to 1.0 - Features TODO list -- [X] REST Api (included in default CosoleApp project) +- [X] REST Api (included in default App project) - [ ] GRPC Server Connector - [X] TCP Server Connector - [X] TCP Client Connector @@ -155,7 +176,6 @@ I will do my best to keep the interfaces stable, but there are possibilities to 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 - [ ] Reduce memory allocation on read/write operations. The aim is to use the underlying byte[] as source or target directly, without further allocations. - [ ] use existing ReadOnlySpan aware methods on S7Net - [ ] introduce new method ReadOnlySpan aware on Snap7 and SnapModBus diff --git a/SmartIOT.Connector.App.sln b/SmartIOT.Connector.App.sln new file mode 100644 index 0000000..4d43f51 --- /dev/null +++ b/SmartIOT.Connector.App.sln @@ -0,0 +1,101 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32421.90 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{00225731-BE18-495B-9FDA-DEC2D79868B1}" + ProjectSection(SolutionItems) = preProject + .dockerignore = .dockerignore + .editorconfig = .editorconfig + docker-build-app.bat = docker-build-app.bat + docker-push-app.bat = docker-push-app.bat + docker-run-app.bat = docker-run-app.bat + dotnet-build-and-push.bat = dotnet-build-and-push.bat + .github\workflows\dotnet-develop.yml = .github\workflows\dotnet-develop.yml + .github\workflows\dotnet-master.yml = .github\workflows\dotnet-master.yml + .github\workflows\dotnet-prerelease.yml = .github\workflows\dotnet-prerelease.yml + .github\workflows\dotnet-release.yml = .github\workflows\dotnet-release.yml + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Mqtt", "Connectors\SmartIOT.Connector.Mqtt\SmartIOT.Connector.Mqtt.csproj", "{8FC9CA9D-94BF-473C-887C-6EF6D40CBE82}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Core", "Core\SmartIOT.Connector.Core\SmartIOT.Connector.Core.csproj", "{78A4C66E-A994-4E5D-89C3-26713532F608}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Messages", "Core\SmartIOT.Connector.Messages\SmartIOT.Connector.Messages.csproj", "{7CFA3DA8-F742-4ADA-9301-46833D4FCFB9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Prometheus", "Core\SmartIOT.Connector.Prometheus\SmartIOT.Connector.Prometheus.csproj", "{EC862678-7398-4544-A22D-48B56F6BF489}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Plc.S7Net", "Devices\SmartIOT.Connector.Plc.S7Net\SmartIOT.Connector.Plc.S7Net.csproj", "{02665260-499D-488E-912D-D6A23EC1C6CB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Plc.Snap7", "Devices\SmartIOT.Connector.Plc.Snap7\SmartIOT.Connector.Plc.Snap7.csproj", "{99AD52AC-5983-4016-BC18-81ED7E21CD13}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Tcp", "Connectors\SmartIOT.Connector.Tcp\SmartIOT.Connector.Tcp.csproj", "{5E9D195E-AF4E-4D09-B45E-675549981D50}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.RestApi", "Core\SmartIOT.Connector.RestApi\SmartIOT.Connector.RestApi.csproj", "{E39654FA-5AA3-4C69-94B8-0BEF1272349C}" +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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.App", "Apps\SmartIOT.Connector.App\SmartIOT.Connector.App.csproj", "{2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.DependencyInjection", "Core\SmartIOT.Connector.DependencyInjection\SmartIOT.Connector.DependencyInjection.csproj", "{06857D97-E4EC-4EEC-8202-0C323E1BABF3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FC9CA9D-94BF-473C-887C-6EF6D40CBE82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FC9CA9D-94BF-473C-887C-6EF6D40CBE82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FC9CA9D-94BF-473C-887C-6EF6D40CBE82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FC9CA9D-94BF-473C-887C-6EF6D40CBE82}.Release|Any CPU.Build.0 = Release|Any CPU + {78A4C66E-A994-4E5D-89C3-26713532F608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78A4C66E-A994-4E5D-89C3-26713532F608}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78A4C66E-A994-4E5D-89C3-26713532F608}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78A4C66E-A994-4E5D-89C3-26713532F608}.Release|Any CPU.Build.0 = Release|Any CPU + {7CFA3DA8-F742-4ADA-9301-46833D4FCFB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CFA3DA8-F742-4ADA-9301-46833D4FCFB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CFA3DA8-F742-4ADA-9301-46833D4FCFB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CFA3DA8-F742-4ADA-9301-46833D4FCFB9}.Release|Any CPU.Build.0 = Release|Any CPU + {EC862678-7398-4544-A22D-48B56F6BF489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC862678-7398-4544-A22D-48B56F6BF489}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC862678-7398-4544-A22D-48B56F6BF489}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC862678-7398-4544-A22D-48B56F6BF489}.Release|Any CPU.Build.0 = Release|Any CPU + {02665260-499D-488E-912D-D6A23EC1C6CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02665260-499D-488E-912D-D6A23EC1C6CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02665260-499D-488E-912D-D6A23EC1C6CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02665260-499D-488E-912D-D6A23EC1C6CB}.Release|Any CPU.Build.0 = Release|Any CPU + {99AD52AC-5983-4016-BC18-81ED7E21CD13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99AD52AC-5983-4016-BC18-81ED7E21CD13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99AD52AC-5983-4016-BC18-81ED7E21CD13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99AD52AC-5983-4016-BC18-81ED7E21CD13}.Release|Any CPU.Build.0 = Release|Any CPU + {5E9D195E-AF4E-4D09-B45E-675549981D50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E9D195E-AF4E-4D09-B45E-675549981D50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E9D195E-AF4E-4D09-B45E-675549981D50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E9D195E-AF4E-4D09-B45E-675549981D50}.Release|Any CPU.Build.0 = Release|Any CPU + {E39654FA-5AA3-4C69-94B8-0BEF1272349C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E39654FA-5AA3-4C69-94B8-0BEF1272349C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E39654FA-5AA3-4C69-94B8-0BEF1272349C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E39654FA-5AA3-4C69-94B8-0BEF1272349C}.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 + {2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}.Release|Any CPU.Build.0 = Release|Any CPU + {06857D97-E4EC-4EEC-8202-0C323E1BABF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06857D97-E4EC-4EEC-8202-0C323E1BABF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06857D97-E4EC-4EEC-8202-0C323E1BABF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06857D97-E4EC-4EEC-8202-0C323E1BABF3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {72862639-80E2-4903-910B-7FC4EC818672} + EndGlobalSection +EndGlobal diff --git a/SmartIOT.Connector.sln b/SmartIOT.Connector.sln index 4b84305..7f19cd2 100644 --- a/SmartIOT.Connector.sln +++ b/SmartIOT.Connector.sln @@ -65,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.Plc.Snap EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.App", "Apps\SmartIOT.Connector.App\SmartIOT.Connector.App.csproj", "{2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartIOT.Connector.DependencyInjection", "Core\SmartIOT.Connector.DependencyInjection\SmartIOT.Connector.DependencyInjection.csproj", "{B6A92416-6EF6-490F-9236-B352182E3298}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -151,6 +153,10 @@ Global {2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}.Debug|Any CPU.Build.0 = Debug|Any CPU {2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}.Release|Any CPU.ActiveCfg = Release|Any CPU {2943C3E6-C95D-4D9F-A3DE-FCA2B6AB3349}.Release|Any CPU.Build.0 = Release|Any CPU + {B6A92416-6EF6-490F-9236-B352182E3298}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6A92416-6EF6-490F-9236-B352182E3298}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6A92416-6EF6-490F-9236-B352182E3298}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6A92416-6EF6-490F-9236-B352182E3298}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml.cs b/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml.cs index 4cec941..31aecc4 100644 --- a/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml.cs +++ b/Testers/SmartIOT.Connector.MqttClient.Tester/MainWindow.xaml.cs @@ -193,7 +193,7 @@ public bool IsTopicRoot(string? subscribed, string topic) if (subscribed == null) return false; - if (!topic.EndsWith("/")) + if (!topic.EndsWith('/')) topic += "/"; if (subscribed.Contains('/')) diff --git a/Testers/SmartIOT.Connector.TcpClient.Tester/MainWindow.xaml.cs b/Testers/SmartIOT.Connector.TcpClient.Tester/MainWindow.xaml.cs index 396156c..6d94a68 100644 --- a/Testers/SmartIOT.Connector.TcpClient.Tester/MainWindow.xaml.cs +++ b/Testers/SmartIOT.Connector.TcpClient.Tester/MainWindow.xaml.cs @@ -1,4 +1,6 @@ -using SmartIOT.Connector.Messages; +#pragma warning disable S2589 // Boolean expressions should not be gratuitous + +using SmartIOT.Connector.Messages; using SmartIOT.Connector.Messages.Serializers; using System; using System.Linq; 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 c9e5ad1..46aade7 100644 --- a/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj +++ b/Tests/SmartIOT.Connector.Core.Tests/SmartIOT.Connector.Core.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/Tests/SmartIOT.Connector.Core.Tests/SmartIotBaseTests.cs b/Tests/SmartIOT.Connector.Core.Tests/SmartIotBaseTests.cs index 4673682..583c562 100644 --- a/Tests/SmartIOT.Connector.Core.Tests/SmartIotBaseTests.cs +++ b/Tests/SmartIOT.Connector.Core.Tests/SmartIotBaseTests.cs @@ -62,7 +62,7 @@ protected virtual SmartIotConnectorConfiguration SetupSampleConfiguration() ); } - protected virtual SmartIotConnectorConfiguration SetupConfiguration(IList deviceConfigurations) + protected virtual SmartIotConnectorConfiguration SetupConfiguration(List deviceConfigurations) { return new SmartIotConnectorConfiguration() { diff --git a/Tests/SmartIOT.Connector.Core.Tests/SmartIotConnectorTests.cs b/Tests/SmartIOT.Connector.Core.Tests/SmartIotConnectorTests.cs index d87df0d..b002c9e 100644 --- a/Tests/SmartIOT.Connector.Core.Tests/SmartIotConnectorTests.cs +++ b/Tests/SmartIOT.Connector.Core.Tests/SmartIotConnectorTests.cs @@ -1,4 +1,5 @@ -using SmartIOT.Connector.Core.Conf; +using Microsoft.Extensions.DependencyInjection; +using SmartIOT.Connector.Core.Conf; using SmartIOT.Connector.Mocks; using SmartIOT.Connector.Plc.S7Net; using SmartIOT.Connector.Plc.Snap7; @@ -84,6 +85,39 @@ public void Build_driver_module() Assert.Single(module.Connectors); Assert.True(module.Connectors[0] is FakeConnector); + + Assert.Null(((FakeConnector)module.Connectors[0]).ServiceProvider); + } + + [Fact] + public void Build_driver_module_using_ServiceProvider() + { + var services = new ServiceCollection(); + var sp = services.BuildServiceProvider(); + + var module = new SmartIotConnectorBuilder() + .WithAutoDiscoverDeviceDriverFactories() + .WithAutoDiscoverConnectorFactories() + .WithConfigurationJsonFilePath("driver2.json") + .Build(sp); + + var drivers = module.Schedulers; + Assert.Equal(2, drivers.Count); + + var d0 = drivers[0]; + var d1 = drivers[1]; + Assert.IsType(d0.DeviceDriver); + Assert.IsType(d1.DeviceDriver); + + Assert.NotNull(d0.Device); + var p0 = d0.Device; + + Assert.Equal(2, p0.Tags.Count); + + Assert.Single(module.Connectors); + Assert.True(module.Connectors[0] is FakeConnector); + + Assert.NotNull(((FakeConnector)module.Connectors[0]).ServiceProvider); } [Fact] diff --git a/Tests/SmartIOT.Connector.Mocks/FakeConnector.cs b/Tests/SmartIOT.Connector.Mocks/FakeConnector.cs index 7ccd14f..95b7e14 100644 --- a/Tests/SmartIOT.Connector.Mocks/FakeConnector.cs +++ b/Tests/SmartIOT.Connector.Mocks/FakeConnector.cs @@ -12,11 +12,18 @@ public class FakeConnector : AbstractConnector public IList TagWriteEvents { get; } = new List(); public IList DeviceStatusEvents { get; } = new List(); public IList ExceptionEvents { get; } = new List(); + public IServiceProvider? ServiceProvider { get; } public FakeConnector() : base("fake://") { } + // constructor for testing DI injection + public FakeConnector(IServiceProvider serviceProvider) : base("fake://") + { + ServiceProvider = serviceProvider; + } + public override void OnTagReadEvent(object? sender, TagScheduleEventArgs args) { TagReadEvents.Add(args.TagScheduleEvent); diff --git a/Tests/SmartIOT.Connector.Mocks/FakeConnectorFactory.cs b/Tests/SmartIOT.Connector.Mocks/FakeConnectorFactory.cs index 1a1a384..37d58d5 100644 --- a/Tests/SmartIOT.Connector.Mocks/FakeConnectorFactory.cs +++ b/Tests/SmartIOT.Connector.Mocks/FakeConnectorFactory.cs @@ -5,10 +5,22 @@ namespace SmartIOT.Connector.Mocks; public class FakeConnectorFactory : IConnectorFactory { + private readonly IServiceProvider? _serviceProvider; + + public FakeConnectorFactory() + { + } + + // contructor to test DI injection + public FakeConnectorFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public IConnector? CreateConnector(string connectionString) { if (connectionString.StartsWith("fake://")) - return new FakeConnector(); + return new FakeConnector(_serviceProvider!); return null; } diff --git a/Tests/SmartIOT.Connector.Mocks/MockDriverFactory.cs b/Tests/SmartIOT.Connector.Mocks/MockDriverFactory.cs index 0cc6077..56423dd 100644 --- a/Tests/SmartIOT.Connector.Mocks/MockDriverFactory.cs +++ b/Tests/SmartIOT.Connector.Mocks/MockDriverFactory.cs @@ -8,7 +8,7 @@ public class MockDriverFactory : IDeviceDriverFactory { private static bool AcceptConnectionString(string connectionString) { - return connectionString?.ToLower().StartsWith("mock://") ?? false; + return connectionString?.StartsWith("mock://", StringComparison.InvariantCultureIgnoreCase) ?? false; } public IDeviceDriver? CreateDriver(DeviceConfiguration deviceConfiguration)