Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
luca-domenichini committed Dec 14, 2023
2 parents 7181535 + 7b640e5 commit 1392561
Show file tree
Hide file tree
Showing 29 changed files with 129 additions and 166 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,4 @@ MigrationBackup/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/
/Apps/SmartIOT.Connector.ConsoleApp/log.txt
/Apps/SmartIOT.Connector.App/log.txt
12 changes: 12 additions & 0 deletions Apps/SmartIOT.Connector.App/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.0",
"commands": [
"dotnet-ef"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using SmartIOT.Connector.Core;
using SmartIOT.Connector.Prometheus;

namespace SmartIOT.Connector.ConsoleApp;
namespace SmartIOT.Connector.App;

public class AppConfiguration
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
using SmartIOT.Connector.Core;
using SmartIOT.Connector.Core.Factory;

namespace SmartIOT.Connector.ConsoleApp;
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<AppConfiguration>(configuration);
services.AddSingleton<SmartIotConnector>(s => s.GetRequiredService<Runner>().SmartIotConnector);
services.AddSingleton(configuration);
services.AddSingleton(s => s.GetRequiredService<Runner>().SmartIotConnector);
services.AddSingleton<IConnectorFactory>(s => s.GetRequiredService<Runner>().ConnectorFactory);
services.AddSingleton<IDeviceDriverFactory>(s => s.GetRequiredService<Runner>().DeviceDriverFactory);
services.AddSingleton<ISchedulerFactory>(s => s.GetRequiredService<Runner>().SchedulerFactory);
services.AddSingleton<ITimeService>(s => s.GetRequiredService<Runner>().TimeService);
services.AddSingleton(s => s.GetRequiredService<Runner>().SchedulerFactory);
services.AddSingleton(s => s.GetRequiredService<Runner>().TimeService);

// add custom Runner as a singleton and as a hosted service
services.AddSingleton<Runner>();
services.AddHostedService<Runner>(sp => sp.GetRequiredService<Runner>());
services.AddHostedService(sp => sp.GetRequiredService<Runner>());

return services;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using SmartIOT.Connector.RestApi.Services;
using System.Text.Json;

namespace SmartIOT.Connector.ConsoleApp;
namespace SmartIOT.Connector.App;

internal class ConfigurationPersister : IConfigurationPersister
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ ARG version
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore SmartIOT.Connector.ConsoleApp.sln
RUN dotnet restore SmartIOT.Connector.App.sln

RUN dotnet build --no-restore -c Release SmartIOT.Connector.ConsoleApp.sln
RUN dotnet build --no-restore -c Release SmartIOT.Connector.App.sln

RUN dotnet publish -c Release -o out /p:version=$version SmartIOT.Connector.ConsoleApp.sln
RUN dotnet publish -c Release -o out /p:version=$version SmartIOT.Connector.App.sln

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build-env /build/out .
ENTRYPOINT ["dotnet", "SmartIOT.Connector.ConsoleApp.dll", "/SmartIOT.Connector/smartiot-config.json"]
ENTRYPOINT ["dotnet", "SmartIOT.Connector.App.dll", "/SmartIOT.Connector/smartiot-config.json"]
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
using Serilog;
using SmartIOT.Connector.RestApi;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;

namespace SmartIOT.Connector.ConsoleApp;
namespace SmartIOT.Connector.App;

public class Program
{
Expand All @@ -15,12 +14,13 @@ protected Program()

public static void Main(string[] args)
{
Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
var version = fileVersionInfo.ProductVersion ?? "--Unknown";
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(Process.GetCurrentProcess().MainModule?.FileName!);
var version = fileVersionInfo?.ProductVersion ?? "--Unknown";

Environment.CurrentDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule?.FileName!)!;

if (args.Length == 0)
args = new[] { "smartiot-config.json" };
args = ["smartiot-config.json"];

string path = args[0];
if (!File.Exists(path))
Expand Down Expand Up @@ -63,6 +63,12 @@ public static void Main(string[] args)
options.LowercaseQueryStrings = true;
});

// setup winservice
builder.Services.AddWindowsService(o =>
{
o.ServiceName = typeof(Program).Assembly.GetName().Name!;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}
},
"profiles": {
"SmartIOT.Connector.ConsoleApp": {
"SmartIOT.Connector.App": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SmartIOT.Connector.ConsoleApp
# SmartIOT.Connector.App

This project implements a simple SmartIOT.Connector runner able to run a SmartIOT.Connector instance as a console application.

Expand All @@ -11,15 +11,15 @@ The sample configuration will log to the console and to daily rolling file <code
The Console runner can also be run on Docker. You just need to map a volume pointing the configuration folder <code>/SmartIOT.Connector</code> and expose the ports needed for external communications.<br>
The container image will look for configuration file at <code>/SmartIOT.Connector/smartiot-config.json</code>

A prebuilt Docker image is also available on Docker Hub. Use the following command to pull the latest image, or browse https://hub.docker.com/repository/docker/lucadomenichini/smartiot-connector-consoleapp for available tags.
A prebuilt Docker image is also available on Docker Hub. Use the following command to pull the latest image, or browse https://hub.docker.com/repository/docker/lucadomenichini/smartiot-connector-app for available tags.

<pre>docker pull lucadomenichini/smartiot-connector-consoleapp:latest</pre>
<pre>docker pull lucadomenichini/smartiot-connector-app:latest</pre>

### Building the Docker image from source

Provided you installed docker on your machine, go to SmartIOT.Connector root project folder and type:

<pre>docker build -t smartiot-connector -f Apps/SmartIOT.Connector.ConsoleApp/Dockerfile .</pre>
<pre>docker build -t smartiot-connector -f Apps/SmartIOT.Connector.App/Dockerfile .</pre>

### Running the container

Expand All @@ -32,6 +32,6 @@ Type this to run the container and expose ports on the host machine:
docker run -it --rm -v /path/to/smartiot-connector/configuration/folder:/SmartIOT.Connector -p 9001:9001 -p 1883:1883 smartiot-connector
</pre>

Suppose you have downloaded the solution on Windows on folder C:\develop\SmartIOT.Connector. You will have an <code>smartiot-config.json</code> file under the <code>SmartIOT.Connector.ConsoleApp</code> project folder.<br>
Suppose you have downloaded the solution on Windows on folder C:\develop\SmartIOT.Connector. You will have an <code>smartiot-config.json</code> file under the <code>SmartIOT.Connector.App</code> project folder.<br>
Type this to use that configuration file:<pre>
docker run -it --rm -v C:\develop\SmartIOT.Connector\Apps\SmartIOT.Connector.ConsoleApp:/SmartIOT.Connector -p 9001:9001 -p 1883:1883 smartiot-connector</pre>
docker run -it --rm -v C:\develop\SmartIOT.Connector\Apps\SmartIOT.Connector.App:/SmartIOT.Connector -p 9001:9001 -p 1883:1883 smartiot-connector</pre>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using SmartIOT.Connector.Core.Factory;
using SmartIOT.Connector.Prometheus;

namespace SmartIOT.Connector.ConsoleApp;
namespace SmartIOT.Connector.App;

public class Runner : IHostedService
{
Expand Down Expand Up @@ -95,7 +95,8 @@ public Runner(AppConfiguration configuration, ILogger<Runner> logger)

if (builder.AutoDiscoveryExceptions.Any())
{
_logger.LogWarning($"Error autodiscoverying dll: [{Environment.NewLine}{string.Join($"{Environment.NewLine}\t", builder.AutoDiscoveryExceptions.Select(x => x.Message))}{Environment.NewLine}]");
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}]");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

<ItemGroup>
<!--<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.1" />-->
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Enrichers.Context" Version="4.6.5" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
},
"EventLog": {
"LogLevel": {
"Microsoft.Hosting.Lifetime": "Information"
}
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"Configuration": {
"ConnectorConnectionStrings": [
"tcpServer://port=1885"
"mqttServer://ServerId=MyServer;Port=1883;Serializer=json"
],
"DeviceConfigurations": [
{
"ConnectionString": "snap7://ip=localhost;rack=0;slot=2",
"ConnectionString": "snapmodbus://ip=127.0.0.1;SwapBytes=true",
"DeviceId": "1",
"Enabled": true,
"Name": "Test Device",
Expand All @@ -16,14 +16,14 @@
"TagId": "DB2",
"TagType": "READ",
"ByteOffset": 0,
"Size": 100,
"Size": 10,
"Weight": 1
},
{
"TagId": "DB3",
"TagType": "WRITE",
"ByteOffset": 0,
"Size": 100,
"Size": 20,
"Weight": 1
}
]
Expand All @@ -40,7 +40,7 @@
}
},
"PrometheusConfiguration": {
"HostName": "+",
"HostName": "\u002B",
"Port": 0,
"Url": "metrics/",
"MetricsPrefix": "smartiot_connector",
Expand Down
2 changes: 1 addition & 1 deletion Core/SmartIOT.Connector.RestApi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To plug the controllers you just need to call an extension method to your AspNet
builder.Services.AddSmartIotConnectorRestApi(new ConfigurationPersister(configuration, path));
```

See project [SmartIOT.Connector.ConsoleApp](../../Apps/SmartIOT.Connector.ConsoleApp/README.md) to see how that's done in details.
See project [SmartIOT.Connector.App](../../Apps/SmartIOT.Connector.App/README.md) to see how that's done in details.

You can then use the Swagger API page to test your API calls:

Expand Down
15 changes: 8 additions & 7 deletions Devices/SmartIOT.Connector.Plc.Snap7/Snap7Driver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ public class Snap7Driver : IDeviceDriver
public string Name => $"{nameof(Snap7Driver)}.{Device.Name}";
public Device Device { get; }

private byte[] _tmp;

public Snap7Driver(Snap7Plc plc)
{
Device = plc;

_tmp = new byte[plc.Tags.Max(x => x.TagConfiguration.Size)];
}

public int StartInterface()
Expand Down Expand Up @@ -48,25 +52,22 @@ public int ReadTag(Device device, Tag tag, byte[] data, int startOffset, int len
{
Snap7Plc p = (Snap7Plc)device;

var bytes = new byte[length];

int ret = p.ReadBytes(tag.TagId, startOffset, bytes, length);
int ret = p.ReadBytes(tag.TagId, startOffset, _tmp, length);
if (ret != 0)
return ret;

Array.Copy(bytes, 0, data, startOffset - tag.ByteOffset, length);
Array.Copy(_tmp, 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);
Array.Copy(data, startOffset - tag.ByteOffset, _tmp, 0, length);

Snap7Plc p = (Snap7Plc)device;

return p.WriteBytes(tag.TagId, startOffset, bytes);
return p.WriteBytes(tag.TagId, startOffset, _tmp, length);
}

public string GetErrorMessage(int errorNumber)
Expand Down
6 changes: 3 additions & 3 deletions Devices/SmartIOT.Connector.Plc.Snap7/Snap7Plc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,19 @@ public int ReadBytes(string tagId, int startOffset, byte[] data, int length)
}
}

public int WriteBytes(string tagId, int startOffset, byte[] data)
public int WriteBytes(string tagId, int startOffset, byte[] data, int length)
{
if (int.TryParse(tagId, out int t))
{
return S7Client.DBWrite(t, startOffset, data.Length, data);
return S7Client.DBWrite(t, startOffset, length, data);
}
else
{
var match = RegexDB.Match(tagId);
if (match.Success)
{
t = int.Parse(match.Groups["tag"].Value);
return S7Client.DBWrite(t, startOffset, data.Length, data);
return S7Client.DBWrite(t, startOffset, length, data);
}

// other tag types can be supported here..
Expand Down
21 changes: 11 additions & 10 deletions Devices/SmartIOT.Connector.Plc.SnapModBus/SnapModBusDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ public class SnapModBusDriver : IDeviceDriver
public string Name => $"{nameof(SnapModBusDriver)}.{Device.Name}";
public Device Device { get; }

private ushort[] _tmp;

public SnapModBusDriver(SnapModBusNode node)
{
Device = node;

_tmp = new ushort[node.Tags.Max(x => x.TagConfiguration.Size / 2 + 1)]; // allocating words from bytes, +1 to handle odd sizes..
}

public int StartInterface()
Expand Down Expand Up @@ -68,17 +72,15 @@ public int ReadTag(Device device, Tag tag, byte[] data, int startOffset, int len
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);
int ret = node.ReadRegisters((ushort)(address + 1), amount, _tmp);
if (ret != 0)
return ret;

if (node.Configuration.SwapBytes)
CopyArrayAndSwapBytes(words, startOffset % 2, data, startOffset - tag.ByteOffset, length);
CopyArrayAndSwapBytes(_tmp, startOffset % 2, data, startOffset - tag.ByteOffset, length);
else
Buffer.BlockCopy(words, startOffset % 2, data, startOffset - tag.ByteOffset, length);
Buffer.BlockCopy(_tmp, startOffset % 2, data, startOffset - tag.ByteOffset, length);

return 0;
}
Expand Down Expand Up @@ -111,21 +113,20 @@ public int WriteTag(Device device, Tag tag, byte[] data, int startOffset, int le
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);
CopyArrayAndSwapBytes(data, startOffset - tag.ByteOffset - startRemainder, _tmp, 0, length + startRemainder + endRemainder);
else
Buffer.BlockCopy(data, startOffset - tag.ByteOffset - startRemainder, words, 0, length + startRemainder + endRemainder);
Buffer.BlockCopy(data, startOffset - tag.ByteOffset - startRemainder, _tmp, 0, length + startRemainder + endRemainder);

// address is 1-based
return node.WriteRegisters((ushort)(address + 1), words);
return node.WriteRegisters((ushort)(address + 1), _tmp, amount);
}

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]);
target[targetIndex + i / 2] = (ushort)((source[srcIndex + i] << 8) + source[srcIndex + i + 1]);
}
}

Expand Down
Loading

0 comments on commit 1392561

Please sign in to comment.