Fix some of the old tests

- add new examples
- fix struct builder
- complete array interfaces
This commit is contained in:
Felix Weiß
2023-07-20 23:28:58 +02:00
parent 3a7b787949
commit 9bcffad77b
59 changed files with 2190 additions and 2101 deletions

View File

@@ -7,7 +7,6 @@ using System.Reflection;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Text; using System.Text;
using MewtocolNet; using MewtocolNet;
using MewtocolNet.DocAttributes;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,72 @@
using MewtocolNet;
using MewtocolNet.Logging;
namespace Examples.BasicUsage;
internal class Program {
const string IP = "192.168.115.210";
const int PORT = 9094;
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain () {
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Verbose;
Logger.OnNewLogMessage((d, l, m) => Console.WriteLine($"{d}: {m}"));
//create a new interface to the plc using ethernet / tcp ip
//the using keyword is optional, if you want to use your PLC instance
//globally leave it
//you can also specify the source ip with:
//Mewtocol.Ethernet(IP, PORT).FromSource("192.168.113.2", 46040).Build()
using (var plc = Mewtocol.Ethernet(IP, PORT).Build()) {
//connect async to the plc
await plc.ConnectAsync();
//check if the connection was established
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
//print basic plc info
Console.WriteLine(plc.PlcInfo);
//check if the plc is not in RUN mode, change to run
if(!plc.PlcInfo.IsRunMode) await plc.SetOperationModeAsync(true);
//get information about the plc
Console.WriteLine($"PLC type: {plc.PlcInfo.TypeName}");
Console.WriteLine($"Capacity: {plc.PlcInfo.ProgramCapacity}k");
Console.WriteLine($"Error: {plc.PlcInfo.SelfDiagnosticError}k");
//set the plc to prog mode
//await plc.SetOperationModeAsync(false);
//do disconnect use
plc.Disconnect();
//or
//await plc.DisconnectAsync();
//you can then change the connection settings for example to another PLC
plc.ConfigureConnection("192.168.115.212", 9094);
await plc.ConnectAsync();
}
//you can also find any applicable source endpoints by using:
foreach (var endpoint in Mewtocol.GetSourceEndpoints()) {
Console.WriteLine($"Usable endpoint: {endpoint}");
}
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,145 @@
using MewtocolNet;
using MewtocolNet.Logging;
using MewtocolNet.Registers;
namespace Examples.BasicRegisterReadWrite;
internal class Program {
const string PLC_IP = "192.168.178.55";
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain() {
Console.Clear();
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Critical;
//here we collect our built registers
IRegister<short> simpleNumberRegister = null!;
IRegister<ushort> simpleNumberRegister2 = null!;
IRegister<Word> simpleWordRegister = null!;
IArrayRegister<Word> overlapWordRegister = null!;
IStringRegister stringRegister = null!;
IArrayRegister<string> stringArrayRegister = null!;
IArrayRegister2D<short> simpleNumberRegister2Dim = null!;
//create a new interface to the plc using ethernet / tcp ip
using var plc = Mewtocol.Ethernet(PLC_IP)
.WithRegisters(b => {
//a simple INT at register address DT1000
b.Struct<short>("DT1000").Build(out simpleNumberRegister);
b.Struct<ushort>("DT1001").Build(out simpleNumberRegister2);
//you can also read the same array as an other data type
//not how they are at the same address, that means their values are linked
b.Struct<Word>("DT1000").Build(out simpleWordRegister);
//same link feature is also true for arrays that overlap their addresses
//this will go from DT999 to DT1001 because its a 3 Word array
b.Struct<Word>("DT999").AsArray(3).Build(out overlapWordRegister);
//strings area not stacially sized, they use a different constructor
b.String("DT1024", 32).Build(out stringRegister);
//string can also be arrayed
b.String("DT2030", 5).AsArray(3).Build(out stringArrayRegister);
//a simple 2 dimensional ARRAY [0..2, 0..2] OF INT at DT2003
b.Struct<short>("DT2003").AsArray(3, 3).Build(out simpleNumberRegister2Dim);
})
.Build();
//you can explain the internal register layout and which ones are linked by
Console.WriteLine(plc.Explain());
//connect async to the plc
await plc.ConnectAsync();
//check if the connection was established
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
//restarts the program, this will make sure the global registers of the plc get reset each run
await plc.RestartProgramAsync();
//from this point on we can modify our registers
//read the value of the the register
short readValue = await simpleNumberRegister.ReadAsync();
ushort readValue2 = await simpleNumberRegister2.ReadAsync();
//show the value
Console.WriteLine($"Read value for {nameof(simpleNumberRegister)}: {readValue}");
Console.WriteLine($"Read value for {nameof(simpleNumberRegister2)}: {readValue2}");
//write the value
await simpleNumberRegister.WriteAsync(30);
//show the value
Console.WriteLine($"Value of {nameof(simpleNumberRegister)}: {simpleNumberRegister.Value}");
//because the two registers at DT1000 are linked by their memory address in the plc,
//both of their values got updated
Console.WriteLine($"Value of {nameof(simpleWordRegister)}: {simpleWordRegister.Value}");
//also the overlapping word array register value got updated, but only at the DT positions that were read
int i = 0;
overlapWordRegister.Value.ToList().ForEach(x => {
Console.WriteLine($"Value of {nameof(overlapWordRegister)}[{i}]: {x}");
i++;
});
//you can even read out a word bitwise
Console.WriteLine($"Bits of {nameof(simpleWordRegister)}: {simpleWordRegister.Value?.ToStringBits()}");
//or as a single bit
Console.WriteLine($"Bit at index 3 of {nameof(simpleWordRegister)}: {simpleWordRegister.Value?[3]}");
//reading / writing the string register
//await stringRegister.WriteAsync("Lorem ipsum dolor sit amet, cons");
await stringRegister.ReadAsync();
Console.WriteLine($"Value of {nameof(stringRegister)}: {stringRegister.Value}");
//reading writing a string array register
await stringArrayRegister.ReadAsync();
i = 0;
stringArrayRegister.Value.ToList().ForEach(x => {
Console.WriteLine($"Value of {nameof(stringArrayRegister)}[{i}]: {x}");
i++;
});
//you can either set the whole array at once,
//this will be slow if you only want to update one item
await stringArrayRegister.WriteAsync(new string[] {
"Test1",
"Test2",
"Test3",
});
//or update just one
//COMING LATER
//same thing also works for 2 dim arrays
await simpleNumberRegister2Dim.ReadAsync();
//the array is multi dimensional but can also be iterated per element
foreach (var item in simpleNumberRegister2Dim)
Console.WriteLine($"Element of {nameof(simpleNumberRegister2Dim)}: {item}");
//you can also use the array indexer accessors
Console.WriteLine($"Element [1, 2] of {nameof(simpleNumberRegister2Dim)}: {simpleNumberRegister2Dim[1, 2]}");
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,90 @@
using MewtocolNet.Logging;
using MewtocolNet.Registers;
using MewtocolNet;
namespace Examples.Polling;
internal class Program {
const string PLC_IP = "192.168.178.55";
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain() {
Console.Clear();
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Change;
//create a new interface to the plc using ethernet / tcp ip
using var plc = Mewtocol.Ethernet(PLC_IP)
.WithPoller() // <= use this in the builder pattern to automatically poll registers
.WithInterfaceSettings(s => {
//this means registers at the same address
//or that are contained by overlapping arrays
//always get assigned the same poll level
s.PollLevelOverwriteMode = PollLevelOverwriteMode.Highest;
//this means combine all registers that are not farther
//than 8 words apart from another into one tcp message,
//this safes message frames but a to large number can block read write traffic
s.MaxOptimizationDistance = 8;
})
.WithCustomPollLevels(l => {
//this makes registers at polllevel 2 only run all 3 iterations
l.SetLevel(2, 3);
//this makes registers at polllevel 3 only run all 5 seconds
l.SetLevel(3, TimeSpan.FromSeconds(5));
})
.WithRegisters(b => {
b.Struct<short>("DT1000").Build();
b.Struct<Word>("DT1000").Build();
b.Struct<ushort>("DT1001").Build();
b.Struct<Word>("DT999").AsArray(3).Build();
//we dont want to poll the string register as fast as we can
//so we assign it to level 2 to run only all 3 iterations
b.String("DT1024", 32).PollLevel(2).Build();
//we want to poll this array only at the first iteration,
//and after this only if we need the data
b.Struct<Word>("DT2000").AsArray(3).PollLevel(PollLevel.FirstIteration).Build();
//we want to poll this string array all 5 seconds
b.String("DT2030", 5).AsArray(3).PollLevel(3).Build();
//this is a fairly large array, so we never poll it automatically
//only if we need the data
b.Struct<short>("DT2003").AsArray(3, 3).PollLevel(PollLevel.Never).Build();
})
.Build();
//this explains the underlying data structure for the poller
Console.WriteLine(plc.Explain());
await plc.ConnectAsync(async () => {
//we want to restart the program before the poller starts
await plc.RestartProgramAsync();
});
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
Console.ReadLine();
}
}

View File

@@ -1,53 +0,0 @@
using System;
using System.Collections.Generic;
using CommandLine;
using MewtocolNet;
using MewtocolNet.ComCassette;
using MewtocolNet.Logging;
using MewtocolNet.RegisterBuilding;
using Spectre.Console;
namespace MewTerminal.Commands.OnlineCommands;
[Verb("rget", HelpText = "Gets the values of the given PLC registers")]
internal class GetRegisterCommand : OnlineCommand {
[Value(0, MetaName = "registers", Default = "DT0", HelpText = "The registers to read formatted as <mewtocol_name:plc_type> (DT0:INT)")]
public IEnumerable<string> Registers { get; set; }
internal override async Task AfterSetup(IPlc plc) {
var builder = RegBuilder.ForInterface(plc);
foreach (var reg in Registers) {
var split = reg.Split(":");
if (split.Length <= 1) {
throw new FormatException($"Register name was not formatted correctly: {reg}, missing :PlcVarType");
}
var mewtocolName = split[0];
var mewtocolType = split[1];
if (Enum.TryParse<PlcVarType>(mewtocolType, out var parsedT)) {
builder.FromPlcRegName(mewtocolName).AsPlcType(parsedT).Build();
}
}
await plc.ConnectAsync();
foreach (var reg in plc.GetAllRegisters()) {
await reg.ReadAsync();
}
AnsiConsole.Write(plc.GetAllRegisters().ToTable());
}
}

View File

@@ -1,64 +0,0 @@
using CommandLine;
using MewtocolNet;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.Registers;
using Spectre.Console;
namespace MewTerminal.Commands.OnlineCommands;
[Verb("rset", HelpText = "Sets the values of the given PLC registers")]
internal class SetRegisterCommand : OnlineCommand {
[Value(0, MetaName = "registers", Default = "DT0", HelpText = "The registers to write formatted as <mewtocol_name:plc_type> (DT0:INT:VALUE)")]
public IEnumerable<string> Registers { get; set; }
internal override async Task AfterSetup(IPlc plc) {
var builder = RegBuilder.ForInterface(plc);
var toWriteVals = new List<object>();
foreach (var reg in Registers) {
var split = reg.Split(":");
if (split.Length <= 2) {
throw new FormatException($"Register name was not formatted correctly: {reg}, missing :PlcVarType:Value");
}
var mewtocolName = split[0];
var mewtocolType = split[1];
var value = split[2];
if (Enum.TryParse<PlcVarType>(mewtocolType, out var parsedT)) {
var built = builder.FromPlcRegName(mewtocolName).AsPlcType(parsedT).Build();
if(built is BoolRegister) toWriteVals.Add(bool.Parse(value));
else if(built is NumberRegister<short>) toWriteVals.Add(short.Parse(value));
else if(built is NumberRegister<ushort>) toWriteVals.Add(ushort.Parse(value));
else if(built is NumberRegister<int>) toWriteVals.Add(int.Parse(value));
else if(built is NumberRegister<uint>) toWriteVals.Add(uint.Parse(value));
else if(built is NumberRegister<float>) toWriteVals.Add(float.Parse(value));
else if(built is NumberRegister<TimeSpan>) toWriteVals.Add(TimeSpan.Parse(value));
}
}
await plc.ConnectAsync();
int i = 0;
foreach (var reg in plc.GetAllRegisters()) {
await reg.WriteAsync(toWriteVals[i]);
i++;
}
AnsiConsole.WriteLine("All registers written");
}
}

View File

@@ -11,6 +11,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocBuilder", "DocBuilder\Do
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewTerminal", "MewTerminal\MewTerminal.csproj", "{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewTerminal", "MewTerminal\MewTerminal.csproj", "{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{323729B0-5FB2-4592-9FA6-220C46BBF84C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.BasicEthernet", "Examples.BasicEthernet\Examples.BasicEthernet.csproj", "{80806065-163D-43F3-90CD-9221F391793F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.BasicRegisterReadWrite", "Examples.BasicRegisterReadWrite\Examples.BasicRegisterReadWrite.csproj", "{A24444C8-691D-44CB-8DE8-19618C6DE94B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.Polling", "Examples.Polling\Examples.Polling.csproj", "{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -69,10 +77,51 @@ Global
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x64.Build.0 = Release|Any CPU {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x64.Build.0 = Release|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.ActiveCfg = Release|Any CPU {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.ActiveCfg = Release|Any CPU
{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.Build.0 = Release|Any CPU {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.Build.0 = Release|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Debug|x64.ActiveCfg = Debug|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Debug|x64.Build.0 = Debug|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Debug|x86.ActiveCfg = Debug|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Debug|x86.Build.0 = Debug|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Release|Any CPU.Build.0 = Release|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Release|x64.ActiveCfg = Release|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Release|x64.Build.0 = Release|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Release|x86.ActiveCfg = Release|Any CPU
{80806065-163D-43F3-90CD-9221F391793F}.Release|x86.Build.0 = Release|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|x64.ActiveCfg = Debug|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|x64.Build.0 = Debug|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|x86.ActiveCfg = Debug|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|x86.Build.0 = Debug|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|Any CPU.Build.0 = Release|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|x64.ActiveCfg = Release|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|x64.Build.0 = Release|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|x86.ActiveCfg = Release|Any CPU
{A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|x86.Build.0 = Release|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|x64.ActiveCfg = Debug|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|x64.Build.0 = Debug|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|x86.ActiveCfg = Debug|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|x86.Build.0 = Debug|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|Any CPU.Build.0 = Release|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|x64.ActiveCfg = Release|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|x64.Build.0 = Release|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|x86.ActiveCfg = Release|Any CPU
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{80806065-163D-43F3-90CD-9221F391793F} = {323729B0-5FB2-4592-9FA6-220C46BBF84C}
{A24444C8-691D-44CB-8DE8-19618C6DE94B} = {323729B0-5FB2-4592-9FA6-220C46BBF84C}
{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61} = {323729B0-5FB2-4592-9FA6-220C46BBF84C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4ABB8137-CD8F-4691-9802-9ED371012F47} SolutionGuid = {4ABB8137-CD8F-4691-9802-9ED371012F47}
EndGlobalSection EndGlobalSection

View File

@@ -20,7 +20,7 @@ namespace MewtocolNet.ComCassette {
var interfacesTasks = new List<Task<List<CassetteInformation>>>(); var interfacesTasks = new List<Task<List<CassetteInformation>>>();
var usableInterfaces = GetUseableNetInterfaces(); var usableInterfaces = Mewtocol.GetUseableNetInterfaces();
if (ipSource == null) { if (ipSource == null) {
@@ -71,36 +71,6 @@ namespace MewtocolNet.ComCassette {
} }
private static IEnumerable<NetworkInterface> GetUseableNetInterfaces() {
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) {
bool isEthernet =
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet;
bool isWlan = netInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
bool isUsable = netInterface.OperationalStatus == OperationalStatus.Up;
if (!isUsable) continue;
if (!(isWlan || isEthernet)) continue;
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
var hasUnicastInfo = ipProps.UnicastAddresses
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
if (!hasUnicastInfo) continue;
yield return netInterface;
}
}
private static async Task<List<CassetteInformation>> FindClientsForEndpoint(IPEndPoint from, int timeoutMs, string ipEndpointName) { private static async Task<List<CassetteInformation>> FindClientsForEndpoint(IPEndPoint from, int timeoutMs, string ipEndpointName) {
var cassettesFound = new List<CassetteInformation>(); var cassettesFound = new List<CassetteInformation>();

View File

@@ -13,17 +13,13 @@ namespace MewtocolNet {
internal uint value; internal uint value;
public uint Value { public uint Value => value;
get => value;
set {
this.value = value;
}
}
public DWord(uint bytes) { public DWord(uint bytes) {
value = bytes; value = bytes;
bitLength = Marshal.SizeOf(value) * 8; bitLength = Marshal.SizeOf(value) * 8;
} }
public DWord(byte[] bytes) { public DWord(byte[] bytes) {
bytes = bytes.Take(4).ToArray(); bytes = bytes.Take(4).ToArray();
value = BitConverter.ToUInt32(bytes, 0); value = BitConverter.ToUInt32(bytes, 0);
@@ -62,16 +58,7 @@ namespace MewtocolNet {
return (value & (1 << bitIndex)) != 0; return (value & (1 << bitIndex)) != 0;
} }
set {
if (bitIndex > bitLength - 1)
throw new IndexOutOfRangeException($"The DWord bit index was out of range ({bitIndex}/{bitLength - 1})");
int mask = 1 << bitIndex;
this.value = value ? this.value |= (uint)mask : this.value &= (uint)~mask;
} }
}
public void ClearBits() => this.value = 0;
public override bool Equals(object obj) { public override bool Equals(object obj) {
@@ -107,8 +94,6 @@ namespace MewtocolNet {
} }
public int GetIntialPlcByteSize() => 4;
} }
} }

View File

@@ -13,17 +13,13 @@ namespace MewtocolNet {
internal ushort value; internal ushort value;
public ushort Value { public ushort Value => value;
get => value;
set {
this.value = value;
}
}
public Word(ushort bytes) { public Word(ushort bytes) {
value = bytes; value = bytes;
bitLength = Marshal.SizeOf(value) * 8; bitLength = Marshal.SizeOf(value) * 8;
} }
public Word(byte[] bytes) { public Word(byte[] bytes) {
bytes = bytes.Take(2).ToArray(); bytes = bytes.Take(2).ToArray();
value = BitConverter.ToUInt16(bytes, 0); value = BitConverter.ToUInt16(bytes, 0);
@@ -53,7 +49,7 @@ namespace MewtocolNet {
public static bool operator !=(Word a, Word b) => a.value != b.value; public static bool operator !=(Word a, Word b) => a.value != b.value;
/// <summary> /// <summary>
/// Gets or sets the bit value at the given position /// Gets the bit value at the given position
/// </summary> /// </summary>
public bool this[int bitIndex] { public bool this[int bitIndex] {
get { get {
@@ -62,16 +58,7 @@ namespace MewtocolNet {
return (value & (1 << bitIndex)) != 0; return (value & (1 << bitIndex)) != 0;
} }
set {
if (bitIndex > bitLength - 1)
throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})");
int mask = 1 << bitIndex;
this.value = value ? this.value |= (ushort)mask : this.value &= (ushort)~mask;
} }
}
public void ClearBits() => this.value = 0;
public override bool Equals(object obj) { public override bool Equals(object obj) {

View File

@@ -57,11 +57,6 @@ namespace MewtocolNet {
/// </summary> /// </summary>
int ConnectTimeout { get; set; } int ConnectTimeout { get; set; }
/// <summary>
/// Provides an anonymous interface for register reading and writing without memory management
/// </summary>
RBuildAnon Register { get; }
/// <summary> /// <summary>
/// Tries to establish a connection with the device asynchronously /// Tries to establish a connection with the device asynchronously
/// </summary> /// </summary>

View File

@@ -22,7 +22,7 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// The host ip endpoint, leave it null to use an automatic interface /// The host ip endpoint, leave it null to use an automatic interface
/// </summary> /// </summary>
IPEndPoint HostEndpoint { get; set; } IPEndPoint HostEndpoint { get; }
/// <summary> /// <summary>
/// Configures the serial interface /// Configures the serial interface

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
namespace MewtocolNet.Logging { namespace MewtocolNet.Logging {
@@ -12,8 +13,58 @@ namespace MewtocolNet.Logging {
/// </summary> /// </summary>
public static LogLevel LogLevel { get; set; } public static LogLevel LogLevel { get; set; }
/// <summary>
/// Defines the default output logger targets
/// </summary>
public static LoggerTargets DefaultTargets { get; set; } = LoggerTargets.Console;
internal static Action<DateTime, LogLevel, string> LogInvoked; internal static Action<DateTime, LogLevel, string> LogInvoked;
static Logger () {
OnNewLogMessage((d, l, m) => {
if(DefaultTargets.HasFlag(LoggerTargets.Console)) {
switch (l) {
case LogLevel.Error:
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
case LogLevel.Info:
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
case LogLevel.Change:
Console.ForegroundColor = ConsoleColor.DarkBlue;
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
case LogLevel.Verbose:
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
case LogLevel.Critical:
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($"{d:hh:mm:ss:ff} {m}");
break;
}
Console.ResetColor();
}
if (DefaultTargets.HasFlag(LoggerTargets.Trace)) {
Trace.WriteLine($"{d:hh:mm:ss:ff} {m}");
}
});
}
//for static calling purposes only
internal static void Start() { }
/// <summary> /// <summary>
/// Gets invoked whenever a new log message is ready /// Gets invoked whenever a new log message is ready
/// </summary> /// </summary>

View File

@@ -0,0 +1,12 @@
using System;
namespace MewtocolNet.Logging {
[Flags]
public enum LoggerTargets {
None = 0,
Console = 1,
Trace = 2,
}
}

View File

@@ -1,14 +1,18 @@
using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterBuilding; using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.SetupClasses; using MewtocolNet.SetupClasses;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Ports; using System.IO.Ports;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MewtocolNet { namespace MewtocolNet
{
/// <summary> /// <summary>
/// Builder helper for mewtocol interfaces /// Builder helper for mewtocol interfaces
@@ -24,11 +28,11 @@ namespace MewtocolNet {
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="station">Plc station number 0xEE for direct communication</param> /// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns> /// <returns></returns>
public static PostInit<IPlcEthernet> Ethernet(string ip, int port = 9094, int station = 0xEE) { public static PostInitEth<IPlcEthernet> Ethernet(string ip, int port = 9094, int station = 0xEE) {
var instance = new MewtocolInterfaceTcp(); var instance = new MewtocolInterfaceTcp();
instance.ConfigureConnection(ip, port, station); instance.ConfigureConnection(ip, port, station);
return new PostInit<IPlcEthernet> { return new PostInitEth<IPlcEthernet> {
intf = instance intf = instance
}; };
@@ -41,11 +45,11 @@ namespace MewtocolNet {
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="station">Plc station number 0xEE for direct communication</param> /// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns> /// <returns></returns>
public static PostInit<IPlcEthernet> Ethernet(IPAddress ip, int port = 9094, int station = 0xEE) { public static PostInitEth<IPlcEthernet> Ethernet(IPAddress ip, int port = 9094, int station = 0xEE) {
var instance = new MewtocolInterfaceTcp(); var instance = new MewtocolInterfaceTcp();
instance.ConfigureConnection(ip, port, station); instance.ConfigureConnection(ip, port, station);
return new PostInit<IPlcEthernet> { return new PostInitEth<IPlcEthernet> {
intf = instance intf = instance
}; };
@@ -63,8 +67,6 @@ namespace MewtocolNet {
/// <returns></returns> /// <returns></returns>
public static PostInit<IPlcSerial> Serial(string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 0xEE) { public static PostInit<IPlcSerial> Serial(string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 0xEE) {
TestPortName(portName);
var instance = new MewtocolInterfaceSerial(); var instance = new MewtocolInterfaceSerial();
instance.ConfigureConnection(portName, (int)baudRate, (int)dataBits, parity, stopBits, station); instance.ConfigureConnection(portName, (int)baudRate, (int)dataBits, parity, stopBits, station);
return new PostInit<IPlcSerial> { return new PostInit<IPlcSerial> {
@@ -81,8 +83,6 @@ namespace MewtocolNet {
/// <returns></returns> /// <returns></returns>
public static PostInit<IPlcSerial> SerialAuto(string portName, int station = 0xEE) { public static PostInit<IPlcSerial> SerialAuto(string portName, int station = 0xEE) {
TestPortName(portName);
var instance = new MewtocolInterfaceSerial(); var instance = new MewtocolInterfaceSerial();
instance.ConfigureConnection(portName, station); instance.ConfigureConnection(portName, station);
instance.ConfigureConnectionAuto(); instance.ConfigureConnectionAuto();
@@ -92,12 +92,52 @@ namespace MewtocolNet {
} }
private static void TestPortName(string portName) { /// <summary>
/// Lists all useable source endpoints of the device this is running on for usage with PLCs
/// </summary>
public static IEnumerable<IPEndPoint> GetSourceEndpoints () {
var portnames = SerialPort.GetPortNames(); foreach (var netIf in GetUseableNetInterfaces()) {
if (!portnames.Any(x => x == portName)) var addressInfo = netIf.GetIPProperties().UnicastAddresses
throw new NotSupportedException($"The port {portName} is no valid port"); .FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
yield return new IPEndPoint(addressInfo.Address, 9094);
}
}
/// <summary>
/// Lists all useable network interfaces of the device this is running on for usage with PLCs
/// </summary>
public static IEnumerable<NetworkInterface> GetUseableNetInterfaces () {
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) {
bool isEthernet =
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT ||
netInterface.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet;
bool isWlan = netInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
bool isUsable = netInterface.OperationalStatus == OperationalStatus.Up;
if (!isUsable) continue;
if (!(isWlan || isEthernet)) continue;
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
var hasUnicastInfo = ipProps.UnicastAddresses
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
if (!hasUnicastInfo) continue;
yield return netInterface;
}
} }
@@ -116,8 +156,8 @@ namespace MewtocolNet {
/// <param name="interval">Delay between poll requests</param> /// <param name="interval">Delay between poll requests</param>
public PollLevelConfigurator SetLevel(int level, TimeSpan interval) { public PollLevelConfigurator SetLevel(int level, TimeSpan interval) {
if (level <= 1) if (level == PollLevel.Always || level == PollLevel.Never || level == PollLevel.FirstIteration)
throw new NotSupportedException($"The poll level {level} is not configurable"); throw new NotSupportedException("The poll level is reserved for the library");
if (!levelConfigs.ContainsKey(level)) { if (!levelConfigs.ContainsKey(level)) {
levelConfigs.Add(level, new PollLevelConfig { levelConfigs.Add(level, new PollLevelConfig {
@@ -133,8 +173,8 @@ namespace MewtocolNet {
public PollLevelConfigurator SetLevel(int level, int skipNth) { public PollLevelConfigurator SetLevel(int level, int skipNth) {
if (level <= 1) if (level == PollLevel.Always || level == PollLevel.Never || level == PollLevel.FirstIteration)
throw new NotSupportedException($"The poll level {level} is not configurable"); throw new NotSupportedException("The poll level is reserved for the library");
if (!levelConfigs.ContainsKey(level)) { if (!levelConfigs.ContainsKey(level)) {
levelConfigs.Add(level, new PollLevelConfig { levelConfigs.Add(level, new PollLevelConfig {
@@ -174,6 +214,56 @@ namespace MewtocolNet {
} }
public class PostInitEth<T> : PostInit<T> {
/// <summary>
/// Sets the source of the outgoing ethernet connection
/// </summary>
public PostInit<T> FromSource (IPEndPoint endpoint) {
if(endpoint == null)
throw new ArgumentNullException("Endpoint can't be null", nameof(endpoint));
if(intf is MewtocolInterfaceTcp imew) {
imew.HostEndpoint = endpoint;
}
return this;
}
/// <summary>
/// Sets the source of the outgoing ethernet connection
/// </summary>
/// <param name="ip">IP address of the source interface (Format: 127.0.0.1)</param>
/// <param name="port">Port of the source interface</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public PostInit<T> FromSource(string ip, int port) {
if (intf is MewtocolInterfaceTcp imew) {
if(port < IPEndPoint.MinPort)
throw new ArgumentException($"Source port cant be smaller than {IPEndPoint.MinPort}", nameof(port));
if (port > IPEndPoint.MaxPort)
throw new ArgumentException($"Source port cant be larger than {IPEndPoint.MaxPort}", nameof(port));
if (!IPAddress.TryParse(ip, out var ipParsed))
throw new ArgumentException("Failed to parse the source IP", nameof(ip));
imew.HostEndpoint = new IPEndPoint(ipParsed, port);
}
return this;
}
}
public class PostInit<T> { public class PostInit<T> {
internal T intf; internal T intf;
@@ -269,18 +359,16 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// A builder for attaching register collections /// A builder for attaching register collections
/// </summary> /// </summary>
public PostInit<T> WithRegisters(Action<RBuildMult> builder) { public PostInit<T> WithRegisters(Action<RBuild> builder) {
try { try {
var plc = (MewtocolInterface)(object)intf; var plc = (MewtocolInterface)(object)intf;
var assembler = new RegisterAssembler(plc); var regBuilder = new RBuild(plc);
var regBuilder = new RBuildMult(plc);
builder.Invoke(regBuilder); builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder); plc.AddRegisters(regBuilder.assembler.assembled.ToArray());
plc.AddRegisters(registers.ToArray());
return this; return this;

View File

@@ -55,7 +55,7 @@ namespace MewtocolNet {
private protected AsyncQueue queue = new AsyncQueue(); private protected AsyncQueue queue = new AsyncQueue();
private protected Stopwatch speedStopwatchUpstr; private protected Stopwatch speedStopwatchUpstr;
private protected Stopwatch speedStopwatchDownstr; private protected Stopwatch speedStopwatchDownstr;
private protected Task firstPollTask = new Task(() => { }); private protected Task firstPollTask;
private protected bool wasInitialStatusReceived; private protected bool wasInitialStatusReceived;
private protected MewtocolVersion mewtocolVersion; private protected MewtocolVersion mewtocolVersion;
@@ -144,6 +144,8 @@ namespace MewtocolNet {
private protected MewtocolInterface() { private protected MewtocolInterface() {
Logger.Start();
memoryManager = new MemoryAreaManager(this); memoryManager = new MemoryAreaManager(this);
Connected += MewtocolInterface_Connected; Connected += MewtocolInterface_Connected;

View File

@@ -2,6 +2,7 @@
using MewtocolNet.Logging; using MewtocolNet.Logging;
using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterBuilding; using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers; using MewtocolNet.Registers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -38,9 +39,6 @@ namespace MewtocolNet {
} }
} }
/// <inheritdoc/>
public RBuildAnon Register => new RBuildAnon(this);
#region Register Polling #region Register Polling
/// <summary> /// <summary>
@@ -191,7 +189,7 @@ namespace MewtocolNet {
if (registerCollections.Count != 0) if (registerCollections.Count != 0)
throw new NotSupportedException("Register collections can only be build once"); throw new NotSupportedException("Register collections can only be build once");
var regBuild = new RBuildMult(this); var regBuild = new RBuildFromAttributes(this);
foreach (var collection in collections) { foreach (var collection in collections) {
@@ -218,7 +216,7 @@ namespace MewtocolNet {
if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel; if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel;
//add builder item //add builder item
var stp1 = regBuild regBuild
.AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, byteHint) .AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, byteHint)
.AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType) .AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType)
.PollLevel(pollLevel); .PollLevel(pollLevel);
@@ -242,8 +240,8 @@ namespace MewtocolNet {
} }
var assembler = new RegisterAssembler(this); var assembler = new RegisterAssembler(this);
var registers = assembler.AssembleAll(regBuild, true);
AddRegisters(registers.ToArray()); AddRegisters(assembler.assembled.ToArray());
} }

View File

@@ -78,6 +78,9 @@ namespace MewtocolNet {
/// <inheritdoc/> /// <inheritdoc/>
public void ConfigureConnection(string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 0xEE) { public void ConfigureConnection(string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 0xEE) {
if (IsConnected)
throw new NotSupportedException("Can't change the connection settings while the PLC is connected");
PortName = _portName; PortName = _portName;
SerialBaudRate = _baudRate; SerialBaudRate = _baudRate;
SerialDataBits = _dataBits; SerialDataBits = _dataBits;
@@ -95,6 +98,9 @@ namespace MewtocolNet {
internal void ConfigureConnectionAuto() { internal void ConfigureConnectionAuto() {
if (IsConnected)
throw new NotSupportedException("Can't change the connection settings while the PLC is connected");
autoSerial = true; autoSerial = true;
} }
@@ -106,6 +112,10 @@ namespace MewtocolNet {
/// <inheritdoc/> /// <inheritdoc/>
private async Task ConnectAsyncPriv(Func<Task> callBack, Action onTryingConfig = null) { private async Task ConnectAsyncPriv(Func<Task> callBack, Action onTryingConfig = null) {
var portnames = SerialPort.GetPortNames();
if (!portnames.Any(x => x == PortName))
throw new NotSupportedException($"The port {PortName} is no valid port");
void OnTryConfig() { void OnTryConfig() {
onTryingConfig(); onTryingConfig();
} }
@@ -115,6 +125,8 @@ namespace MewtocolNet {
try { try {
firstPollTask = new Task(() => { });
Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this); Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this);
isConnectingStage = true; isConnectingStage = true;

View File

@@ -1,5 +1,6 @@
using MewtocolNet.Logging; using MewtocolNet.Logging;
using System; using System;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -23,7 +24,7 @@ namespace MewtocolNet {
public int Port { get; private set; } public int Port { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public IPEndPoint HostEndpoint { get; set; } public IPEndPoint HostEndpoint { get; internal set; }
internal MewtocolInterfaceTcp() : base() { } internal MewtocolInterfaceTcp() : base() { }
@@ -32,6 +33,9 @@ namespace MewtocolNet {
/// <inheritdoc/> /// <inheritdoc/>
public void ConfigureConnection(string ip, int port = 9094, int station = 0xEE) { public void ConfigureConnection(string ip, int port = 9094, int station = 0xEE) {
if (IsConnected)
throw new NotSupportedException("Can't change the connection settings while the PLC is connected");
if (!IPAddress.TryParse(ip, out ipAddr)) if (!IPAddress.TryParse(ip, out ipAddr))
throw new NotSupportedException($"The ip: {ip} is no valid ip address"); throw new NotSupportedException($"The ip: {ip} is no valid ip address");
@@ -48,6 +52,9 @@ namespace MewtocolNet {
/// <inheritdoc/> /// <inheritdoc/>
public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 0xEE) { public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 0xEE) {
if (IsConnected)
throw new NotSupportedException("Can't change the connection settings while the PLC is connected");
ipAddr = ip; ipAddr = ip;
Port = port; Port = port;
@@ -65,11 +72,22 @@ namespace MewtocolNet {
try { try {
firstPollTask = new Task(() => { });
Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this); Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this);
isConnectingStage = true; isConnectingStage = true;
if (HostEndpoint != null) { if (HostEndpoint != null) {
var hasEndpoint = Mewtocol
.GetSourceEndpoints()
.Any(x => x.Address.ToString() == HostEndpoint.Address.ToString());
if (!hasEndpoint)
throw new NotSupportedException($"The specified source endpoint: " +
$"{HostEndpoint}, doesn't exist on the device, " +
$"use 'Mewtocol.GetSourceEndpoints()' to find applicable ones");
client = new TcpClient(HostEndpoint) { client = new TcpClient(HostEndpoint) {
ReceiveBufferSize = RecBufferSize, ReceiveBufferSize = RecBufferSize,
NoDelay = false, NoDelay = false,

17
MewtocolNet/PollLevel.cs Normal file
View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet {
public static class PollLevel {
public const int Always = 1;
public const int FirstIteration = 254;
public const int Never = 255;
}
}

View File

@@ -0,0 +1,266 @@
using MewtocolNet.PublicEnums;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
namespace MewtocolNet.RegisterBuilding {
internal static class AddressTools {
internal protected struct ParseResult {
internal ParseResultState state;
internal string hardFailReason;
internal StepData stepData;
}
//methods to test the input string on
static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
(x) => TryBuildBoolean(x),
(x) => TryBuildNumericBased(x),
(x) => TryBuildByteRangeBased(x),
};
//bool registers
private static ParseResult TryBuildBoolean(string plcAddrName) {
//regex to find special register values
var patternBool = new Regex(@"(?<prefix>X|Y|R)(?<area>[0-9]{0,3})(?<special>(?:[0-9]|[A-F]){1})?");
var match = patternBool.Match(plcAddrName);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
string special = match.Groups["special"].Value;
IOType regType;
uint areaAdd = 0;
byte specialAdd = 0x0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for boolean registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
//special address not given
if (string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) {
var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt);
if (isAreaInt && areaInt >= 0 && areaInt <= 9) {
//area address is actually meant as special address but 0-9
specialAdd = (byte)areaInt;
areaAdd = 0;
} else if (isAreaInt && areaInt > 9) {
//area adress is meant to be the actual area address
areaAdd = areaInt;
specialAdd = 0;
} else {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 1",
};
}
} else {
//special address parsed as hex num
if (!byte.TryParse(special, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out specialAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 2",
};
}
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new StepData {
regType = (RegisterType)(int)regType,
memAddress = areaAdd,
specialAddress = specialAdd,
}
};
}
// one to two word registers
private static ParseResult TryBuildNumericBased(string plcAddrName) {
var patternByte = new Regex(@"^(?<prefix>DT|DDT)(?<area>[0-9]{1,5})$");
var match = patternByte.Match(plcAddrName);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for numeric registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new StepData {
regType = regType,
memAddress = areaAdd,
}
};
}
// one to two word registers
private static ParseResult TryBuildByteRangeBased(string plcAddrName) {
var split = plcAddrName.Split('-');
if (split.Length > 2)
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'"
};
uint[] addresses = new uint[2];
for (int i = 0; i < split.Length; i++) {
string addr = split[i];
var patternByte = new Regex(@"(?<prefix>DT|DDT)(?<area>[0-9]{1,5})");
var match = patternByte.Match(addr);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
addresses[i] = areaAdd;
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new StepData {
regType = RegisterType.DT_BYTE_RANGE,
wasAddressStringRangeBased = true,
dotnetVarType = typeof(byte[]),
memAddress = addresses[0],
byteSizeHint = (addresses[1] - addresses[0] + 1) * 2
}
};
}
internal static StepData ParseAddress(string plcAddrName, string name = null) {
foreach (var method in parseMethods) {
var res = method.Invoke(plcAddrName);
if (res.state == ParseResultState.Success) {
if (!string.IsNullOrEmpty(name)) res.stepData.name = name;
res.stepData.originalParseStr = plcAddrName;
res.stepData.buildSource = RegisterBuildSource.Manual;
return new StepData().Map(res.stepData);
} else if (res.state == ParseResultState.FailedHard) {
throw new Exception(res.hardFailReason);
}
}
throw new Exception("Wrong input format");
}
}
}

View File

@@ -0,0 +1,253 @@
using MewtocolNet;
using MewtocolNet.PublicEnums;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System;
using System.Linq;
using System.Reflection;
using static MewtocolNet.RegisterBuilding.BuilderPatterns.RBuild;
namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
/// <summary>
/// Contains useful tools for bunch register creation
/// </summary>
public class RBuild {
internal RegisterAssembler assembler;
public RBuild(MewtocolInterface plc) {
assembler = new RegisterAssembler(plc);
}
public class SBaseRB : StepBase {
internal RBuild builder;
}
#region String parse stage
internal Register Assemble(StepBase stp) => assembler.Assemble(stp.Data);
//struct constructor
public StructStp<T> Struct<T>(string fpAddr, string name = null) where T : struct {
var data = AddressTools.ParseAddress(fpAddr, name);
data.dotnetVarType = typeof(T);
return new StructStp<T>(data) {
builder = this,
};
}
//string constructor
public StringStp<string> String(string fpAddr, int sizeHint, string name = null) {
var data = AddressTools.ParseAddress(fpAddr, name);
data.dotnetVarType = typeof(string);
data.byteSizeHint = (uint)sizeHint;
return new StringStp<string>(data) {
builder = this,
};
}
#endregion
#region Typing stage
//structs can lead to arrays
public class StructStp<T> : ArrayStp<T> where T : struct {
internal StructStp(StepData data) {
this.Data = data;
this.Map(StepBaseTyper.AsType(this, typeof(T)));
}
public void Build() => builder.Assemble(this);
public void Build(out IRegister<T> reference) => reference = (IRegister<T>)builder.Assemble(this);
public StructStpOut<T> PollLevel(int level) {
Data.pollLevel = level;
return new StructStpOut<T>().Map(this);
}
}
public class StructStpOut<T> : SBaseRB where T : struct {
public void Build() => builder.Assemble(this);
public void Build(out IRegister<T> reference) => reference = (IRegister<T>)builder.Assemble(this);
}
//strings can lead to arrays
public class StringStp<T> : ArrayStp<T> where T : class {
internal StringStp(StepData data) {
this.Data = data;
this.Map(StepBaseTyper.AsType(this, typeof(T)));
}
public void Build() => builder.Assemble(this);
public void Build(out IStringRegister reference) => reference = (IStringRegister)builder.Assemble(this);
public StringOutStp PollLevel(int level) {
Data.pollLevel = level;
return new StringOutStp().Map(this);
}
}
public class StringOutStp : SBaseRB {
public void Build() => builder.Assemble(this);
public void Build(out IStringRegister reference) => reference = (IStringRegister)builder.Assemble(this);
}
//arrays
public class ArrayStp<T> : SBaseRB {
public TypedArr1D<T> AsArray(int i) {
Data.arrayIndicies = new int[] { i };
SetSizing();
return new TypedArr1D<T>().Map(this);
}
public TypedArr2D<T> AsArray(int i1, int i2) {
Data.arrayIndicies = new int[] { i1, i2 };
SetSizing();
return new TypedArr2D<T>().Map(this);
}
public TypedArr3D<T> AsArray(int i1, int i2, int i3) {
Data.arrayIndicies = new int[] { i1, i2, i3 };
SetSizing();
return new TypedArr3D<T>().Map(this);
}
private void SetSizing() {
var arr = Array.CreateInstance(Data.dotnetVarType, Data.arrayIndicies.ToArray());
Data.dotnetVarType = arr.GetType();
var itemCount = (uint)Data.arrayIndicies.Aggregate((a, x) => a * x);
if (typeof(T) == typeof(string)) {
var byteSize = Data.byteSizeHint.Value;
if (byteSize % 2 != 0) byteSize++;
Data.byteSizeHint = itemCount * (byteSize + 4);
} else {
var byteSize = (uint)typeof(T).DetermineTypeByteIntialSize();
Data.byteSizeHint = itemCount * byteSize;
}
}
}
#endregion
#region Typing size hint
//1D array
public class TypedArr1D<T> : TypedArr1DOut<T> {
public TypedArr1DOut<T> PollLevel(int level) {
Data.pollLevel = level;
return new TypedArr1DOut<T>().Map(this);
}
}
public class TypedArr1DOut<T> : SBaseRB {
public IArrayRegister<T> Build() => (IArrayRegister<T>)builder.Assemble(this);
public void Build(out IArrayRegister<T> reference) => reference = (IArrayRegister<T>)builder.Assemble(this);
}
//2D array
public class TypedArr2D<T> : TypedArr2DOut<T> {
public TypedArr2DOut<T> PollLevel(int level) {
Data.pollLevel = level;
return new TypedArr2DOut<T>().Map(this);
}
}
public class TypedArr2DOut<T> : SBaseRB {
public IArrayRegister2D<T> Build() => (IArrayRegister2D<T>)builder.Assemble(this);
public void Build(out IArrayRegister2D<T> reference) => reference = (IArrayRegister2D<T>)builder.Assemble(this);
}
//3D array
public class TypedArr3D<T> : SBaseRB {
public TypedArr3DOut<T> PollLevel(int level) {
Data.pollLevel = level;
return new TypedArr3DOut<T>().Map(this);
}
}
public class TypedArr3DOut<T> : SBaseRB {
public IArrayRegister3D<T> Build() => (IArrayRegister3D<T>)builder.Assemble(this);
public void Build(out IArrayRegister3D<T> reference) => reference = (IArrayRegister3D<T>)builder.Assemble(this);
}
#endregion
}
}

View File

@@ -0,0 +1,70 @@
using MewtocolNet.PublicEnums;
using MewtocolNet.RegisterAttributes;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
internal class RBuildFromAttributes {
internal RegisterAssembler assembler;
public RBuildFromAttributes(MewtocolInterface plc) {
assembler = new RegisterAssembler(plc);
}
public class SBaseRBDyn : StepBase {
internal RBuildFromAttributes builder;
}
//internal use only, adds a type definition (for use when building from attibute)
internal DynamicStp AddressFromAttribute(string dtAddr, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) {
var stpData = AddressTools.ParseAddress(dtAddr);
stpData.typeDef = typeDef;
stpData.buildSource = RegisterBuildSource.Attribute;
stpData.regCollection = regCol;
stpData.boundProperty = prop;
stpData.byteSizeHint = bytesizeHint;
return new DynamicStp {
builder = this,
Data = stpData,
};
}
//non generic
internal class DynamicStp : SBaseRBDyn {
public DynamicRegister AsType<T>() => new DynamicRegister().Map(StepBaseTyper.AsType<T>(this));
public DynamicRegister AsType(Type type) => new DynamicRegister().Map(StepBaseTyper.AsType(this, type));
public DynamicRegister AsType(PlcVarType type) => new DynamicRegister().Map(StepBaseTyper.AsType(this, type));
public DynamicRegister AsType(string type) => new DynamicRegister().Map(StepBaseTyper.AsType(this, type));
public DynamicRegister AsTypeArray<T>(params int[] indicies) => new DynamicRegister().Map(StepBaseTyper.AsTypeArray<T>(this, indicies));
}
internal class DynamicRegister : SBaseRBDyn {
public void PollLevel (int level) {
Data.pollLevel = level;
}
}
}
}

View File

@@ -1,104 +0,0 @@
using System;
using MewtocolNet.Registers;
using System.Threading.Tasks;
namespace MewtocolNet.RegisterBuilding {
/// <summary>
/// Anonymous register builder
/// </summary>
public class RBuildAnon : RBuildBase {
public RBuildAnon(MewtocolInterface plc) : base(plc) { }
/// <inheritdoc cref="RBuildMult.Address(string, string)"/>
public SAddress Address(string plcAddrName) {
return new SAddress {
attachedPlc = this.attachedPLC,
addrString = plcAddrName
};
}
public new class SAddress {
protected internal MewtocolInterface attachedPlc;
protected internal string addrString;
protected internal string name;
/// <summary>
/// Writes data to the register and bypasses the memory manager <br/>
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>True if success</returns>
public async Task<bool> WriteToAsync<T>(T value) {
throw new NotImplementedException();
//try {
// var tempRegister = AssembleTemporaryRegister<T>();
// return await tempRegister.WriteAsync(value);
//} catch {
// throw;
//}
}
/// <summary>
/// Reads data from the register and bypasses the memory manager <br/>
/// </summary>
/// <returns>The value read or null if failed</returns>
public async Task<T> ReadFromAsync<T>() {
throw new NotImplementedException();
//try {
// var tempRegister = AssembleTemporaryRegister<T>();
// return (T)await tempRegister.ReadAsync();
//} catch {
// throw;
//}
}
private Register AssembleTemporaryRegister<T>() {
var temp = new RBuildMult(attachedPlc).Address(addrString).AsType<T>();
var assembler = new RegisterAssembler(attachedPlc);
return assembler.Assemble(temp.Data);
}
}
}
public class RBuildSingle : RBuildBase {
public RBuildSingle(MewtocolInterface plc) : base(plc) { }
/// <inheritdoc cref="RBuildMult.Address(string, string)"/>
public SAddress Address(string plcAddrName, string name = null) {
var data = ParseAddress(plcAddrName, name);
return new SAddress {
Data = data,
builder = this,
};
}
}
}

View File

@@ -1,607 +0,0 @@
using MewtocolNet.PublicEnums;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
namespace MewtocolNet.RegisterBuilding {
public class RBuildBase {
protected internal MewtocolInterface attachedPLC;
public RBuildBase() { }
internal RBuildBase(MewtocolInterface plc) => attachedPLC = plc;
internal List<BaseStepData> unfinishedList = new List<BaseStepData>();
#region Parser stage
//methods to test the input string on
protected static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
(x) => TryBuildBoolean(x),
(x) => TryBuildNumericBased(x),
(x) => TryBuildByteRangeBased(x),
};
public class SBase {
public SBase() { }
internal SBase(StepData data, RBuildBase bldr) {
Data = data;
builder = bldr;
}
internal StepData Data;
internal RBuildBase builder;
}
internal protected struct ParseResult {
internal ParseResultState state;
internal string hardFailReason;
internal BaseStepData stepData;
}
//bool registers
private static ParseResult TryBuildBoolean(string plcAddrName) {
//regex to find special register values
var patternBool = new Regex(@"(?<prefix>X|Y|R)(?<area>[0-9]{0,3})(?<special>(?:[0-9]|[A-F]){1})?");
var match = patternBool.Match(plcAddrName);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
string special = match.Groups["special"].Value;
IOType regType;
uint areaAdd = 0;
byte specialAdd = 0x0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for boolean registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
//special address not given
if (string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) {
var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt);
if (isAreaInt && areaInt >= 0 && areaInt <= 9) {
//area address is actually meant as special address but 0-9
specialAdd = (byte)areaInt;
areaAdd = 0;
} else if (isAreaInt && areaInt > 9) {
//area adress is meant to be the actual area address
areaAdd = areaInt;
specialAdd = 0;
} else {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 1",
};
}
} else {
//special address parsed as hex num
if (!byte.TryParse(special, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out specialAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 2",
};
}
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new StepData {
regType = (RegisterType)(int)regType,
memAddress = areaAdd,
specialAddress = specialAdd,
}
};
}
// one to two word registers
private static ParseResult TryBuildNumericBased(string plcAddrName) {
var patternByte = new Regex(@"^(?<prefix>DT|DDT)(?<area>[0-9]{1,5})$");
var match = patternByte.Match(plcAddrName);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for numeric registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new StepData {
regType = regType,
memAddress = areaAdd,
}
};
}
// one to two word registers
private static ParseResult TryBuildByteRangeBased(string plcAddrName) {
var split = plcAddrName.Split('-');
if (split.Length > 2)
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'"
};
uint[] addresses = new uint[2];
for (int i = 0; i < split.Length; i++) {
string addr = split[i];
var patternByte = new Regex(@"(?<prefix>DT|DDT)(?<area>[0-9]{1,5})");
var match = patternByte.Match(addr);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
addresses[i] = areaAdd;
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new StepData {
regType = RegisterType.DT_BYTE_RANGE,
wasAddressStringRangeBased = true,
dotnetVarType = typeof(byte[]),
memAddress = addresses[0],
byteSizeHint = (addresses[1] - addresses[0] + 1) * 2
}
};
}
#endregion
#region Addressing stage
internal StepData ParseAddress(string plcAddrName, string name = null) {
foreach (var method in parseMethods) {
var res = method.Invoke(plcAddrName);
if (res.state == ParseResultState.Success) {
if (!string.IsNullOrEmpty(name)) res.stepData.name = name;
res.stepData.originalParseStr = plcAddrName;
res.stepData.buildSource = RegisterBuildSource.Manual;
return new StepData().Map(res.stepData);
} else if (res.state == ParseResultState.FailedHard) {
throw new Exception(res.hardFailReason);
}
}
throw new Exception("Wrong input format");
}
#endregion
#region Typing stage
public class SAddress : SBase {
/// <summary>
/// Sets the register as a dotnet <see cref="System"/> type for direct conversion
/// </summary>
/// <typeparam name="T">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </typeparam>
internal TypedRegister AsType<T>() {
if (!typeof(T).IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting");
}
Data.dotnetVarType = typeof(T);
return new TypedRegister().Map(this);
}
/// <summary>
/// Sets the register as a dotnet <see cref="System"/> type for direct conversion
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </summary>
/// <param name="type">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </param>
internal TypedRegister AsType(Type type) {
//was ranged syntax array build
if (Data.wasAddressStringRangeBased && type.IsArray && type.GetArrayRank() == 1) {
//invoke generic AsTypeArray
MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray));
MethodInfo generic = method.MakeGenericMethod(type);
var elementType = type.GetElementType();
if (type != typeof(byte[]) && !elementType.IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {elementType}, is not supported for PLC type casting");
}
int byteSizePerItem = elementType.DetermineTypeByteIntialSize();
//check if it fits without remainder
if (Data.byteSizeHint % byteSizePerItem != 0) {
throw new NotSupportedException($"The array element type {elementType} doesn't fit into the adress range");
}
return (TypedRegister)generic.Invoke(this, new object[] {
//element count
new int[] { (int)((Data.byteSizeHint / byteSizePerItem)) }
});
} else if (Data.wasAddressStringRangeBased) {
throw new NotSupportedException("DT range building is only allowed for 1 dimensional arrays");
}
//for internal only, relay to AsType from string
if (Data.buildSource == RegisterBuildSource.Attribute) {
if ((type.IsArray || type == typeof(string)) && Data.typeDef != null) {
return AsType(Data.typeDef);
} else if (type.IsArray && Data.typeDef == null) {
throw new NotSupportedException("Typedef parameter is needed for array types");
} else if (Data.typeDef != null) {
throw new NotSupportedException("Can't use the typedef parameter on non array or string types");
}
}
if (!type.IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting");
}
Data.dotnetVarType = type;
return new TypedRegister().Map(this);
}
/// <summary>
/// Sets the register type as a predefined <see cref="PlcVarType"/>
/// </summary>
internal TypedRegister AsType(PlcVarType type) {
Data.dotnetVarType = type.GetDefaultDotnetType();
return new TypedRegister().Map(this);
}
/// <summary>
/// Sets the register type from the plc type string <br/>
/// <c>Supported types:</c>
/// <list type="bullet">
/// <item><term>BOOL</term><description>Boolean R/X/Y registers</description></item>
/// <item><term>INT</term><description>16 bit signed integer</description></item>
/// <item><term>UINT</term><description>16 bit un-signed integer</description></item>
/// <item><term>DINT</term><description>32 bit signed integer</description></item>
/// <item><term>UDINT</term><description>32 bit un-signed integer</description></item>
/// <item><term>REAL</term><description>32 bit floating point</description></item>
/// <item><term>TIME</term><description>32 bit time interpreted as <see cref="TimeSpan"/></description></item>
/// <item><term>STRING</term><description>String of chars, the interface will automatically get the length</description></item>
/// <item><term>STRING[N]</term><description>String of chars, pre capped to N</description></item>
/// <item><term>WORD</term><description>16 bit word interpreted as <see cref="ushort"/></description></item>
/// <item><term>DWORD</term><description>32 bit double word interpreted as <see cref="uint"/></description></item>
/// </list>
/// </summary>
internal TypedRegister AsType(string type) {
var regexString = new Regex(@"^STRING *\[(?<len>[0-9]*)\]$", RegexOptions.IgnoreCase);
var regexArray = new Regex(@"^ARRAY *\[(?<S1>[0-9]*)..(?<E1>[0-9]*)(?:\,(?<S2>[0-9]*)..(?<E2>[0-9]*))?(?:\,(?<S3>[0-9]*)..(?<E3>[0-9]*))?\] *OF {1,}(?<t>.*)$", RegexOptions.IgnoreCase);
var stringMatch = regexString.Match(type);
var arrayMatch = regexArray.Match(type);
if (Enum.TryParse<PlcVarType>(type, out var parsed)) {
Data.dotnetVarType = parsed.GetDefaultDotnetType();
} else if (stringMatch.Success) {
Data.dotnetVarType = typeof(string);
Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value);
} else if (arrayMatch.Success) {
//invoke generic AsTypeArray
string arrTypeString = arrayMatch.Groups["t"].Value;
Type dotnetArrType = null;
var stringMatchInArray = regexString.Match(arrTypeString);
if (Enum.TryParse<PlcVarType>(arrTypeString, out var parsedArrType) && parsedArrType != PlcVarType.STRING) {
dotnetArrType = parsedArrType.GetDefaultDotnetType();
} else if (stringMatchInArray.Success) {
dotnetArrType = typeof(string);
//Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value);
} else {
throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized");
}
var indices = new List<int>();
for (int i = 1; i < 4; i++) {
var arrStart = arrayMatch.Groups[$"S{i}"]?.Value;
var arrEnd = arrayMatch.Groups[$"E{i}"]?.Value;
if (string.IsNullOrEmpty(arrStart) || string.IsNullOrEmpty(arrEnd)) break;
var arrStartInt = int.Parse(arrStart);
var arrEndInt = int.Parse(arrEnd);
indices.Add(arrEndInt - arrStartInt + 1);
}
var arr = Array.CreateInstance(dotnetArrType, indices.ToArray());
var arrType = arr.GetType();
MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray));
MethodInfo generic = method.MakeGenericMethod(arrType);
var tmp = (TypedRegister)generic.Invoke(this, new object[] {
indices.ToArray()
});
tmp.builder = builder;
tmp.Data = Data;
return tmp;
} else {
throw new NotSupportedException($"The FP type '{type}' was not recognized");
}
return new TypedRegister().Map(this);
}
/// <summary>
/// Sets the register as a (multidimensional) array targeting a PLC array
/// </summary>
/// <typeparam name="T">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </typeparam>
/// <param name="indicies">
/// Indicies for multi dimensional arrays, for normal arrays just one INT
/// </param>
/// <example>
/// <b>One dimensional arrays:</b><br/>
/// ARRAY [0..2] OF INT = <c>AsTypeArray&lt;short[]&gt;(3)</c><br/>
/// ARRAY [5..6] OF DWORD = <c>AsTypeArray&lt;DWord[]&gt;(2)</c><br/>
/// <br/>
/// <b>Multi dimensional arrays:</b><br/>
/// ARRAY [0..2, 0..3, 0..4] OF INT = <c>AsTypeArray&lt;short[,,]&gt;(3,4,5)</c><br/>
/// ARRAY [5..6, 0..2] OF DWORD = <c>AsTypeArray&lt;DWord[,]&gt;(2, 3)</c><br/>
/// </example>
internal TypedRegister AsTypeArray<T>(params int[] indicies) {
if (!typeof(T).IsArray)
throw new NotSupportedException($"The type {typeof(T)} was no array");
var arrRank = typeof(T).GetArrayRank();
var elBaseType = typeof(T).GetElementType();
if (arrRank > 3)
throw new NotSupportedException($"4+ dimensional arrays are not supported");
if (typeof(T) != typeof(byte[]) && !elBaseType.IsAllowedPlcCastingType())
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC array type casting");
if (arrRank != indicies.Length)
throw new NotSupportedException($"All dimensional array indicies must be set");
Data.dotnetVarType = typeof(T);
int byteSizePerItem = elBaseType.DetermineTypeByteIntialSize();
int calcedTotalByteSize = indicies.Aggregate((a, x) => a * x) * byteSizePerItem;
Data.byteSizeHint = (uint)calcedTotalByteSize;
Data.arrayIndicies = indicies;
if (Data.byteSizeHint % byteSizePerItem != 0) {
throw new NotSupportedException($"The array element type {elBaseType} doesn't fit into the adress range");
}
return new TypedRegister().Map(this);
}
}
#endregion
#region Typing size hint
public class TypedRegister : SBase {
internal OptionsRegister SizeHint(int hint) {
Data.byteSizeHint = (uint)hint;
return new OptionsRegister().Map(this);
}
internal OptionsRegister PollLevel(int level) {
Data.pollLevel = level;
return new OptionsRegister().Map(this);
}
}
#endregion
#region Options stage
public class OptionsRegister : SBase {
internal OptionsRegister() { }
internal OptionsRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
/// <summary>
/// Sets the poll level of the register
/// </summary>
public OptionsRegister PollLevel(int level) {
Data.pollLevel = level;
return this;
}
}
#endregion
}
}

View File

@@ -1,375 +0,0 @@
using MewtocolNet.PublicEnums;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System;
using System.Reflection;
using static MewtocolNet.RegisterBuilding.RBuildMult;
namespace MewtocolNet.RegisterBuilding {
/// <summary>
/// Contains useful tools for bunch register creation
/// </summary>
public class RBuildMult : RBuildBase {
public RBuildMult(MewtocolInterface plc) : base(plc) { }
#region String parse stage
//at runtime constructor
/// <summary>
/// Starts the register builder for a new mewtocol address <br/>
/// Examples:
/// <code>Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName")</code>
/// </summary>
/// <param name="dtAddr">Address name formatted as FP-Address like in FP-Winpro</param>
/// <param name="name">Custom name for the register to referr to it later</param>
public AddressStp Address(string dtAddr, string name = null) {
var data = ParseAddress(dtAddr, name);
unfinishedList.Add(data);
return new AddressStp {
Data = data,
builder = this,
};
}
//struct constructor
public StructStp<T> Struct<T>(string dtAddr, string name = null) where T : struct {
var data = ParseAddress(dtAddr, name);
data.dotnetVarType = typeof(T);
unfinishedList.Add(data);
return new StructStp<T>(data) {
builder = this,
};
}
public StringStp<T> String<T>(string dtAddr, int sizeHint, string name = null) where T : class {
var data = ParseAddress(dtAddr, name);
data.dotnetVarType = typeof(T);
data.byteSizeHint = (uint)sizeHint;
unfinishedList.Add(data);
if (typeof(T).IsArray) {
return new StringStp<T>(data, true) {
Data = data,
builder = this,
};
}
return new StringStp<T>(data) {
builder = this,
};
}
public ArrayStp<T> Array<T>(string dtAddr, string name = null) where T : class {
var data = ParseAddress(dtAddr, name);
data.dotnetVarType = typeof(T);
unfinishedList.Add(data);
if (typeof(T).IsArray) {
return new ArrayStp<T>(data, true) {
Data = data,
builder = this,
};
}
return new ArrayStp<T>(data) {
builder = this,
};
}
//internal use only, adds a type definition (for use when building from attibute)
internal AddressStp AddressFromAttribute(string dtAddr, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) {
var built = Address(dtAddr);
built.Data.typeDef = typeDef;
built.Data.buildSource = RegisterBuildSource.Attribute;
built.Data.regCollection = regCol;
built.Data.boundProperty = prop;
built.Data.byteSizeHint = bytesizeHint;
return built;
}
#endregion
#region Typing stage
//non generic
public new class AddressStp : RBuildBase.SAddress {
public new TypedRegister AsType<T>() => new TypedRegister().Map(base.AsType<T>());
public new TypedRegister AsType(Type type) => new TypedRegister().Map(base.AsType(type));
public new TypedRegister AsType(PlcVarType type) => new TypedRegister().Map(base.AsType(type));
public new TypedRegister AsType(string type) => new TypedRegister().Map(base.AsType(type));
public new TypedRegister AsTypeArray<T>(params int[] indicies) => new TypedRegister().Map(base.AsTypeArray<T>(indicies));
}
//structs
public class StructStp<T> : RBuildBase.SAddress where T : struct {
internal StructStp(StepData data) {
this.Data = data;
this.Map(AsType(typeof(T)));
}
internal StructStp(StepData data, bool arrayed) {
this.Data = data;
}
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IRegister<T>)o));
}
public OutStruct<T> PollLevel(int level) {
Data.pollLevel = level;
return new OutStruct<T>().Map(this);
}
}
//arrays
public class ArrayStp<T> : RBuildBase.SAddress {
internal ArrayStp(StepData data) {
Data = data;
this.Map(AsType(typeof(T)));
}
internal ArrayStp(StepData data, bool arrayed) {
Data = data;
}
public TypedRegisterArray<T> Indices(params int[] indices) {
if (typeof(T).GetElementType() == typeof(string) && Data.byteSizeHint == null) {
throw new NotSupportedException($"For string arrays use {nameof(ArrayStp<T>.StrHint)} before setting the indices");
}
Data.arrayIndicies = indices;
return new TypedRegisterArray<T>().Map(this);
}
public TypedRegisterStringArray<T> StrHint(int hint) {
Data.byteSizeHint = (uint)hint;
return new TypedRegisterStringArray<T>().Map(this);
}
}
//strings
public class StringStp<T> : RBuildBase.SAddress where T : class {
internal StringStp(StepData data) {
this.Data = data;
this.Map(AsType(typeof(T)));
}
internal StringStp(StepData data, bool arrayed) {
this.Data = data;
}
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IStringRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IStringRegister<T>)o));
}
}
#endregion
#region Typing size hint
public new class TypedRegister : RBuildBase.TypedRegister {
public new OptionsRegister SizeHint(int hint) => new OptionsRegister().Map(base.SizeHint(hint));
///<inheritdoc cref="RBuildBase.OptionsRegister.PollLevel(int)"/>
public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IRegister> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IRegister)o));
}
}
public class TypedRegisterString<T> : RBuildBase.TypedRegister where T : class {
public new OptionsRegister SizeHint(int hint) => new OptionsRegister().Map(base.SizeHint(hint));
///<inheritdoc cref="RBuildBase.OptionsRegister.PollLevel(int)"/>
public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IStringRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IStringRegister<T>)o));
}
}
public class TypedRegisterArray<T> : RBuildBase.TypedRegister {
public new OutArray<T> PollLevel(int level) => new OutArray<T>().Map(base.PollLevel(level));
public void Out(Action<IArrayRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IArrayRegister<T>)o));
}
}
public class TypedRegisterStringArray<T> : RBuildBase.TypedRegister {
public OptionsRegisterArray<T> Indices(params int[] indices) {
Data.arrayIndicies = indices;
return new OptionsRegisterArray<T>().Map(this);
}
public new OutArray<T> PollLevel(int level) => new OutArray<T>().Map(base.PollLevel(level));
}
#endregion
#region Options stage
public new class OptionsRegister : RBuildBase.OptionsRegister {
///<inheritdoc cref="RBuildBase.OptionsRegister.PollLevel(int)"/>
public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IRegister> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IRegister)o));
}
}
public class OptionsRegisterArray<T> : RBuildBase.OptionsRegister {
///<inheritdoc cref="RBuildBase.OptionsRegister.PollLevel(int)"/>
public new OutArray<T> PollLevel(int level) => new OutArray<T>().Map(base.PollLevel(level));
public void Out(Action<IArrayRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IArrayRegister<T>)o));
}
}
#endregion
public class OutRegister : SBase {
public void Out(Action<IRegister> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IRegister)o));
}
}
public class OutStruct<T> : SBase where T : struct {
public void Out(Action<IRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IRegister<T>)o));
}
}
public class OutArray<T> : SBase {
public void Out(Action<IArrayRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IArrayRegister<T>)o));
}
}
}
}

View File

@@ -1,4 +1,5 @@
using MewtocolNet.Registers; using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -12,22 +13,22 @@ namespace MewtocolNet.RegisterBuilding {
/// This waits for the memory manager to size all dynamic registers correctly /// This waits for the memory manager to size all dynamic registers correctly
/// </summary> /// </summary>
/// <returns>The generated <see cref="IRegister"/></returns> /// <returns>The generated <see cref="IRegister"/></returns>
public static IRegister AddRegister(this IPlc plc, Action<RBuildSingle> builder) { //public static IRegister AddRegister(this IPlc plc, Action<RBuildSingle> builder) {
var assembler = new RegisterAssembler((MewtocolInterface)plc); // var assembler = new RegisterAssembler((MewtocolInterface)plc);
var regBuilder = new RBuildSingle((MewtocolInterface)plc); // var regBuilder = new RBuildSingle((MewtocolInterface)plc);
builder.Invoke(regBuilder); // builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder); // var registers = assembler.AssembleAll(regBuilder);
var interf = (MewtocolInterface)plc; // var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray()); // interf.AddRegisters(registers.ToArray());
return registers.First(); // return registers.First();
} //}
/// <summary> /// <summary>
/// Adds multiple registers to the plc stack at once <br/> /// Adds multiple registers to the plc stack at once <br/>
@@ -38,22 +39,22 @@ namespace MewtocolNet.RegisterBuilding {
/// use <see cref="AddRegistersAsync"/> /// use <see cref="AddRegistersAsync"/>
/// for this case /// for this case
/// </summary> /// </summary>
public static IPlc AddRegisters (this IPlc plc, Action<RBuildMult> builder) { //public static IPlc AddRegisters (this IPlc plc, Action<RBuild> builder) {
var assembler = new RegisterAssembler((MewtocolInterface)plc); // var assembler = new RegisterAssembler((MewtocolInterface)plc);
var regBuilder = new RBuildMult((MewtocolInterface)plc); // var regBuilder = new RBuild((MewtocolInterface)plc);
builder.Invoke(regBuilder); // builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder); // var registers = assembler.AssembleAll(regBuilder);
var interf = (MewtocolInterface)plc; // var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray()); // interf.AddRegisters(registers.ToArray());
return plc; // return plc;
} //}
} }

View File

@@ -1,4 +1,5 @@
using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers; using MewtocolNet.Registers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -13,33 +14,15 @@ namespace MewtocolNet.RegisterBuilding {
internal MewtocolInterface onInterface; internal MewtocolInterface onInterface;
internal List<Register> assembled = new List<Register>();
internal RegisterAssembler(MewtocolInterface interf) { internal RegisterAssembler(MewtocolInterface interf) {
onInterface = interf; onInterface = interf;
} }
internal List<Register> AssembleAll(RBuildBase rBuildData, bool flagAutoGenerated = false) { internal Register Assemble(StepData data) {
List<Register> generatedInstances = new List<Register>();
foreach (var data in rBuildData.unfinishedList) {
var generatedInstance = Assemble(data);
generatedInstance.autoGenerated = flagAutoGenerated;
data?.InvokeBuilt(generatedInstance);
generatedInstances.Add(generatedInstance);
}
return generatedInstances;
}
internal Register Assemble(BaseStepData data) {
Register generatedInstance = null; Register generatedInstance = null;
@@ -69,7 +52,7 @@ namespace MewtocolNet.RegisterBuilding {
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
Type paramedClass = typeof(ArrayRegister<>).MakeGenericType(data.dotnetVarType); Type paramedClass = typeof(ArrayRegister<>).MakeGenericType(data.dotnetVarType.GetElementType());
ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] { ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] {
typeof(uint), typeof(uint),
typeof(uint), typeof(uint),
@@ -86,10 +69,10 @@ namespace MewtocolNet.RegisterBuilding {
generatedInstance = instance; generatedInstance = instance;
} else if (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType()) { } else if (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType() && data.dotnetVarType != typeof(string)) {
//------------------------------------------- //-------------------------------------------
//as single register //as struct register
uint numericSize = (uint)data.dotnetVarType.DetermineTypeByteIntialSize(); uint numericSize = (uint)data.dotnetVarType.DetermineTypeByteIntialSize();
@@ -101,18 +84,6 @@ namespace MewtocolNet.RegisterBuilding {
} }
} }
if(data.dotnetVarType == typeof(string)) {
if(data.byteSizeHint == null)
throw new NotSupportedException($"Can't create a STRING register without a string size hint");
if(data.byteSizeHint < 0)
throw new NotSupportedException($"Can't create a STRING register with a string size hint < 0");
numericSize = 4 + data.byteSizeHint.Value;
}
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
Type paramedClass = typeof(StructRegister<>).MakeGenericType(data.dotnetVarType); Type paramedClass = typeof(StructRegister<>).MakeGenericType(data.dotnetVarType);
@@ -130,6 +101,29 @@ namespace MewtocolNet.RegisterBuilding {
generatedInstance = instance; generatedInstance = instance;
} else if (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType()) {
//-------------------------------------------
//as string register
uint numericSize = 0;
if (data.dotnetVarType == typeof(string)) {
if (data.byteSizeHint == null)
throw new NotSupportedException($"Can't create a STRING register without a string size hint");
if (data.byteSizeHint < 0)
throw new NotSupportedException($"Can't create a STRING register with a string size hint < 0");
numericSize = data.byteSizeHint.Value;
}
var instance = (Register)new StringRegister(data.memAddress, numericSize, data.name);
generatedInstance = instance;
} else if (data.regType.IsBoolean()) { } else if (data.regType.IsBoolean()) {
//------------------------------------------- //-------------------------------------------
@@ -162,6 +156,10 @@ namespace MewtocolNet.RegisterBuilding {
generatedInstance.underlyingSystemType = data.dotnetVarType; generatedInstance.underlyingSystemType = data.dotnetVarType;
generatedInstance.pollLevel = data.pollLevel; generatedInstance.pollLevel = data.pollLevel;
if (data.regCollection != null)
generatedInstance.autoGenerated = true;
assembled.Add(generatedInstance);
return generatedInstance; return generatedInstance;
} }

View File

@@ -0,0 +1,234 @@
using MewtocolNet.PublicEnums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
namespace MewtocolNet.RegisterBuilding {
internal static class StepBaseTyper {
/// <summary>
/// Sets the register as a dotnet <see cref="System"/> type for direct conversion
/// </summary>
/// <typeparam name="T">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </typeparam>
internal static StepBase AsType<T>(this StepBase b) {
if (!typeof(T).IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting");
}
b.Data.dotnetVarType = typeof(T);
return b;
}
/// <summary>
/// Sets the register as a dotnet <see cref="System"/> type for direct conversion
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </summary>
/// <param name="type">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </param>
internal static StepBase AsType(this StepBase b, Type type) {
//for internal only, relay to AsType from string
if (b.Data.buildSource == RegisterBuildSource.Attribute) {
if ((type.IsArray || type == typeof(string)) && b.Data.typeDef != null) {
return b.AsType(b.Data.typeDef);
} else if (type.IsArray && b.Data.typeDef == null) {
throw new NotSupportedException("Typedef parameter is needed for array types");
} else if (b.Data.typeDef != null) {
throw new NotSupportedException("Can't use the typedef parameter on non array or string types");
}
}
if (!type.IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting");
}
b.Data.dotnetVarType = type;
return b;
}
/// <summary>
/// Sets the register type as a predefined <see cref="PlcVarType"/>
/// </summary>
internal static StepBase AsType(this StepBase b, PlcVarType type) {
b.Data.dotnetVarType = type.GetDefaultDotnetType();
return b;
}
/// <summary>
/// Sets the register type from the plc type string <br/>
/// <c>Supported types:</c>
/// <list type="bullet">
/// <item><term>BOOL</term><description>Boolean R/X/Y registers</description></item>
/// <item><term>INT</term><description>16 bit signed integer</description></item>
/// <item><term>UINT</term><description>16 bit un-signed integer</description></item>
/// <item><term>DINT</term><description>32 bit signed integer</description></item>
/// <item><term>UDINT</term><description>32 bit un-signed integer</description></item>
/// <item><term>REAL</term><description>32 bit floating point</description></item>
/// <item><term>TIME</term><description>32 bit time interpreted as <see cref="TimeSpan"/></description></item>
/// <item><term>STRING</term><description>String of chars, the interface will automatically get the length</description></item>
/// <item><term>STRING[N]</term><description>String of chars, pre capped to N</description></item>
/// <item><term>WORD</term><description>16 bit word interpreted as <see cref="ushort"/></description></item>
/// <item><term>DWORD</term><description>32 bit double word interpreted as <see cref="uint"/></description></item>
/// </list>
/// </summary>
internal static StepBase AsType(this StepBase b, string type) {
var regexString = new Regex(@"^STRING *\[(?<len>[0-9]*)\]$", RegexOptions.IgnoreCase);
var regexArray = new Regex(@"^ARRAY *\[(?<S1>[0-9]*)..(?<E1>[0-9]*)(?:\,(?<S2>[0-9]*)..(?<E2>[0-9]*))?(?:\,(?<S3>[0-9]*)..(?<E3>[0-9]*))?\] *OF {1,}(?<t>.*)$", RegexOptions.IgnoreCase);
var stringMatch = regexString.Match(type);
var arrayMatch = regexArray.Match(type);
if (Enum.TryParse<PlcVarType>(type, out var parsed)) {
b.Data.dotnetVarType = parsed.GetDefaultDotnetType();
} else if (stringMatch.Success) {
b.Data.dotnetVarType = typeof(string);
b.Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value);
} else if (arrayMatch.Success) {
//invoke generic AsTypeArray
string arrTypeString = arrayMatch.Groups["t"].Value;
Type dotnetArrType = null;
var stringMatchInArray = regexString.Match(arrTypeString);
if (Enum.TryParse<PlcVarType>(arrTypeString, out var parsedArrType) && parsedArrType != PlcVarType.STRING) {
dotnetArrType = parsedArrType.GetDefaultDotnetType();
} else if (stringMatchInArray.Success) {
dotnetArrType = typeof(string);
//Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value);
} else {
throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized");
}
var indices = new List<int>();
for (int i = 1; i < 4; i++) {
var arrStart = arrayMatch.Groups[$"S{i}"]?.Value;
var arrEnd = arrayMatch.Groups[$"E{i}"]?.Value;
if (string.IsNullOrEmpty(arrStart) || string.IsNullOrEmpty(arrEnd)) break;
var arrStartInt = int.Parse(arrStart);
var arrEndInt = int.Parse(arrEnd);
indices.Add(arrEndInt - arrStartInt + 1);
}
var arr = Array.CreateInstance(dotnetArrType, indices.ToArray());
var arrType = arr.GetType();
MethodInfo method = typeof(StepBaseTyper).GetMethod(nameof(AsTypeArray));
MethodInfo generic = method.MakeGenericMethod(arrType);
var tmp = (StepBase)generic.Invoke(null, new object[] {
b,
indices.ToArray()
});
return tmp;
} else {
throw new NotSupportedException($"The FP type '{type}' was not recognized");
}
return b;
}
/// <summary>
/// Sets the register as a (multidimensional) array targeting a PLC array
/// </summary>
/// <typeparam name="T">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </typeparam>
/// <param name="indicies">
/// Indicies for multi dimensional arrays, for normal arrays just one INT
/// </param>
/// <example>
/// <b>One dimensional arrays:</b><br/>
/// ARRAY [0..2] OF INT = <c>AsTypeArray&lt;short[]&gt;(3)</c><br/>
/// ARRAY [5..6] OF DWORD = <c>AsTypeArray&lt;DWord[]&gt;(2)</c><br/>
/// <br/>
/// <b>Multi dimensional arrays:</b><br/>
/// ARRAY [0..2, 0..3, 0..4] OF INT = <c>AsTypeArray&lt;short[,,]&gt;(3,4,5)</c><br/>
/// ARRAY [5..6, 0..2] OF DWORD = <c>AsTypeArray&lt;DWord[,]&gt;(2, 3)</c><br/>
/// </example>
internal static StepBase AsTypeArray<T>(this StepBase b, params int[] indicies) {
if (!typeof(T).IsArray)
throw new NotSupportedException($"The type {typeof(T)} was no array");
var arrRank = typeof(T).GetArrayRank();
var elBaseType = typeof(T).GetElementType();
if (arrRank > 3)
throw new NotSupportedException($"4+ dimensional arrays are not supported");
if (typeof(T) != typeof(byte[]) && !elBaseType.IsAllowedPlcCastingType())
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC array type casting");
if (arrRank != indicies.Length)
throw new NotSupportedException($"All dimensional array indicies must be set");
b.Data.dotnetVarType = typeof(T);
int byteSizePerItem = elBaseType.DetermineTypeByteIntialSize();
int calcedTotalByteSize = indicies.Aggregate((a, x) => a * x) * byteSizePerItem;
b.Data.byteSizeHint = (uint)calcedTotalByteSize;
b.Data.arrayIndicies = indicies;
if (b.Data.byteSizeHint % byteSizePerItem != 0) {
throw new NotSupportedException($"The array element type {elBaseType} doesn't fit into the adress range");
}
return b;
}
}
}

View File

@@ -6,9 +6,15 @@ using System.Reflection;
namespace MewtocolNet.RegisterBuilding { namespace MewtocolNet.RegisterBuilding {
internal class BaseStepData { public class StepBase {
internal Action<object> registerOut; internal StepData Data;
}
public class StepBase<T> : StepBase { }
internal class StepData {
internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous; internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous;
@@ -33,40 +39,6 @@ namespace MewtocolNet.RegisterBuilding {
internal string typeDef; internal string typeDef;
internal void InvokeBuilt(Register reg) {
registerOut?.Invoke(reg);
//var selftype = this.GetType();
//if ((selftype.IsGenericType && selftype.GetGenericTypeDefinition() == typeof(StepData<>))) {
// var field = selftype.GetField("registerOut", BindingFlags.NonPublic | BindingFlags.Instance);
// var generic = typeof(IRegister<>).MakeGenericType()
// var action = Action.CreateDelegate(typeof(IRegister<T>));
// field.SetValue(this,);
//}
}
}
internal class StepData<T> : BaseStepData {
//for referencing the output at builder level
//internal Action<IRegister<T>> registerOut;
}
internal class StepData : BaseStepData {
//for referencing the output at builder level
//internal Action<IRegister> registerOut;
} }
} }

View File

@@ -1,25 +0,0 @@
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
public interface IStringRegister<T> : IRegister where T : class {
/// <summary>
/// The current value of the register
/// </summary>
T Value { get; }
/// <summary>
/// Reads the register value async from the plc
/// </summary>
/// <returns>The register value</returns>
Task<T> ReadAsync();
/// <summary>
/// Writes the register content async to the plc
/// </summary>
/// <returns>True if successfully set</returns>
Task<bool> WriteAsync(T data);
}
}

View File

@@ -26,8 +26,9 @@ namespace MewtocolNet.Registers {
internal Type underlyingSystemType; internal Type underlyingSystemType;
internal IMemoryArea underlyingMemory; internal IMemoryArea underlyingMemory;
internal bool autoGenerated; internal bool autoGenerated;
internal bool isAnonymous;
internal object lastValue = null; internal protected object lastValue = null;
internal string name; internal string name;
internal uint memoryAddress; internal uint memoryAddress;
internal int pollLevel = 0; internal int pollLevel = 0;
@@ -189,7 +190,7 @@ namespace MewtocolNet.Registers {
public override string ToString() { public override string ToString() {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append(GetMewName()); sb.Append(GetRegisterWordRangeString());
if (Name != null) sb.Append($" ({Name})"); if (Name != null) sb.Append($" ({Name})");
sb.Append($" [{this.GetType().Name}({underlyingSystemType.Name})]"); sb.Append($" [{this.GetType().Name}({underlyingSystemType.Name})]");
if (ValueObj != null) sb.Append($" Val: {GetValueString()}"); if (ValueObj != null) sb.Append($" Val: {GetValueString()}");

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -10,7 +11,10 @@ namespace MewtocolNet.Registers {
/// <summary> /// <summary>
/// Defines a register containing a string /// Defines a register containing a string
/// </summary> /// </summary>
public class ArrayRegister<T> : Register, IArrayRegister<T> { public class ArrayRegister<T> : Register, IArrayRegister<T>, IArrayRegister2D<T>, IArrayRegister3D<T> {
internal int byteSizePerItem;
internal uint reservedByteSize;
internal int[] indices; internal int[] indices;
@@ -21,87 +25,124 @@ namespace MewtocolNet.Registers {
/// </summary> /// </summary>
public uint AddressLength => addressLength; public uint AddressLength => addressLength;
public T[] Value => (T[])ValueObj; T[] IArrayRegister<T>.Value => (T[])ValueObj;
T[,] IArrayRegister2D<T>.Value => (T[,])ValueObj;
T[,,] IArrayRegister3D<T>.Value => (T[,,])ValueObj;
public int Count => ((Array)ValueObj).Length;
public T this[int i1, int i2, int i3] => (T)((Array)ValueObj).GetValue(i1, i2, i3);
public T this[int i1, int i2] => (T)((Array)ValueObj).GetValue(i1, i2);
public T this[int i] => (T)((Array)ValueObj).GetValue(i);
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")] [Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public ArrayRegister() => public ArrayRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indicies, string _name = null) { internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indices, string _name = null) {
if (_reservedByteSize % 2 != 0)
throw new ArgumentException(nameof(_reservedByteSize), "Reserved byte size must be even");
name = _name; name = _name;
memoryAddress = _address; memoryAddress = _address;
indices = _indicies; indices = _indices;
//calc mem length int itemCount = _indices.Aggregate((a, x) => a * x);
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too byteSizePerItem = (int)_reservedByteSize / itemCount;
var byteSize = _reservedByteSize; reservedByteSize = _reservedByteSize;
if (byteSize % 2 != 0) byteSize++;
RegisterType = RegisterType.DT_BYTE_RANGE; RegisterType = RegisterType.DT_BYTE_RANGE;
addressLength = Math.Max((byteSize / 2), 1); addressLength = Math.Max((_reservedByteSize / 2), 1);
CheckAddressOverflow(memoryAddress, addressLength); CheckAddressOverflow(memoryAddress, addressLength);
underlyingSystemType = typeof(T).MakeArrayType(indices.Length);
lastValue = null; lastValue = null;
} }
public override string GetValueString() { public IEnumerator<T> GetEnumerator() => ((Array)ValueObj).OfType<T>().GetEnumerator();
if (ValueObj == null) return "null"; IEnumerator IEnumerable.GetEnumerator() => ((Array)ValueObj).OfType<T>().GetEnumerator();
if(typeof(T) == typeof(byte[])) { async Task<T[]> IArrayRegister<T>.ReadAsync() => (T[])(object)await ReadAsync();
return ((byte[])ValueObj).ToHexString("-"); async Task<T[,]> IArrayRegister2D<T>.ReadAsync() => (T[,])(object)await ReadAsync();
} async Task<T[,,]> IArrayRegister3D<T>.ReadAsync() => (T[,,])(object)await ReadAsync();
StringBuilder sb = new StringBuilder(); async Task IArrayRegister<T>.WriteAsync(T[] data) => await WriteAsync(data);
var valueIenum = (IEnumerable)ValueObj;
foreach (var el in valueIenum) { async Task IArrayRegister2D<T>.WriteAsync(T[,] data) => await WriteAsync(data);
sb.Append($"{el}, "); async Task IArrayRegister3D<T>.WriteAsync(T[,,] data) => await WriteAsync(data);
}
return ArrayToString((Array)ValueObj);
}
/// <inheritdoc/> /// <inheritdoc/>
public override string GetRegisterString() => "DT"; private async Task WriteAsync(object value) {
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/>
public async Task<bool> WriteAsync(T value) {
var encoded = PlcValueParser.EncodeArray(this, value); var encoded = PlcValueParser.EncodeArray(this, value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
if (res) { if (res) {
if(isAnonymous) {
//find the underlying memory //find the underlying memory
var matchingReg = attachedInterface.memoryManager.GetAllRegisters() var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this)); .FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null) if (matchingReg != null) matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
} else {
underlyingMemory.SetUnderlyingBytes(this, encoded);
}
AddSuccessWrite(); AddSuccessWrite();
UpdateHoldingValue(value); UpdateHoldingValue(value);
} }
return res; }
private async Task WriteEntriesAsync(int start, object value) {
var encoded = PlcValueParser.EncodeArray(this, value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
if (res) {
if (isAnonymous) {
//find the underlying memory
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null) matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
} else {
underlyingMemory.SetUnderlyingBytes(this, encoded);
}
AddSuccessWrite();
UpdateHoldingValue(value);
}
} }
/// <inheritdoc/> /// <inheritdoc/>
async Task<T[]> IArrayRegister<T>.ReadAsync() { private async Task<object> ReadAsync() {
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) throw new Exception(); if (res == null) throw new Exception();
@@ -112,15 +153,18 @@ namespace MewtocolNet.Registers {
if (matchingReg != null) if (matchingReg != null)
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
return (T[])SetValueFromBytes(res); return SetValueFromBytes(res);
} }
internal override object SetValueFromBytes(byte[] bytes) { internal override object SetValueFromBytes(byte[] bytes) {
if (bytes.Length != reservedByteSize)
throw new ArgumentException(nameof(bytes), "Bytes were not equal the size of registers reserved byte size");
AddSuccessRead(); AddSuccessRead();
var parsed = PlcValueParser.ParseArray<T>(this, indices, bytes); var parsed = PlcValueParser.ParseArray(this, bytes);
UpdateHoldingValue(parsed); UpdateHoldingValue(parsed);
return parsed; return parsed;
@@ -130,13 +174,22 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/> /// <inheritdoc/>
internal override void UpdateHoldingValue(object val) { internal override void UpdateHoldingValue(object val) {
bool changeTriggerBitArr = val is BitArray bitArr && if (val == null && lastValue == null) return;
lastValue is BitArray bitArr2 &&
(bitArr.ToBitString() != bitArr2.ToBitString());
bool changeTriggerGeneral = (lastValue?.ToString() != val?.ToString()); bool sequenceDifference = false;
if (changeTriggerBitArr || changeTriggerGeneral) { if(val == null && lastValue != null) sequenceDifference = true;
if(val != null && lastValue == null) sequenceDifference = true;
if (val != null && lastValue != null) {
var val1 = ((Array)val).OfType<T>();
var val2 = ((Array)lastValue).OfType<T>();
sequenceDifference = !Enumerable.SequenceEqual(val1, val2);
}
if (sequenceDifference) {
var beforeVal = lastValue; var beforeVal = lastValue;
var beforeValStr = GetValueString(); var beforeValStr = GetValueString();
@@ -150,6 +203,25 @@ namespace MewtocolNet.Registers {
} }
public override string GetValueString() {
if (ValueObj == null) return "null";
if (underlyingSystemType == typeof(byte[])) {
return ((byte[])ValueObj).ToHexString("-");
}
return ArrayToString((Array)ValueObj);
}
/// <inheritdoc/>
public override string GetRegisterString() => "DT";
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => AddressLength;
private string ArrayToString(Array array) { private string ArrayToString(Array array) {
@@ -195,6 +267,7 @@ namespace MewtocolNet.Registers {
} }
} }
} }

View File

@@ -0,0 +1,158 @@
using MewtocolNet.Logging;
using System;
using System.Collections;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a number
/// </summary>
public class StringRegister : Register, IStringRegister {
internal int reservedStringLength;
internal uint byteLength;
internal uint addressLength;
/// <summary>
/// The rgisters memory length
/// </summary>
public uint AddressLength => addressLength;
public string Value => (string)ValueObj;
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public StringRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
internal StringRegister(uint _address, uint _reservedByteSize, string _name = null) {
memoryAddress = _address;
name = _name;
reservedStringLength = (int)_reservedByteSize;
Resize(_reservedByteSize);
RegisterType = RegisterType.DT_BYTE_RANGE;
CheckAddressOverflow(memoryAddress, addressLength);
lastValue = null;
}
private void Resize (uint reservedByteSize) {
if (reservedByteSize % 2 != 0) reservedByteSize++;
reservedByteSize += 4;
addressLength = reservedByteSize / 2;
byteLength = reservedByteSize;
}
/// <inheritdoc/>
public override string GetAsPLC() => Value;
/// <inheritdoc/>
public override string GetValueString() => ValueObj?.ToString() ?? "null";
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/>
public async Task WriteAsync(string value) {
//trim the size if the input was larger
if(value.Length > reservedStringLength) {
value = value.Substring(0, reservedStringLength);
}
var encoded = PlcValueParser.Encode(this, value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
if (res) {
//find the underlying memory
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null)
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
AddSuccessWrite();
UpdateHoldingValue(value);
}
}
/// <inheritdoc/>
public async Task<string> ReadAsync() {
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return null;
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null) {
if (matchingReg is StringRegister sreg && this is StringRegister selfSreg) {
sreg.addressLength = selfSreg.addressLength;
}
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
}
return (string)SetValueFromBytes(res);
}
internal override object SetValueFromBytes (byte[] bytes) {
//if string correct the sizing of the byte hint was wrong
var reservedSize = BitConverter.ToInt16(bytes, 0);
if (reservedSize != byteLength - 4)
throw new NotSupportedException(
$"The STRING register at {GetMewName()} is not correctly sized, " +
$"the size should be STRING[{reservedSize}] instead of STRING[{byteLength - 4}]"
);
AddSuccessRead();
var parsed = PlcValueParser.Parse<string>(this, bytes);
UpdateHoldingValue(parsed);
return parsed;
}
internal override void UpdateHoldingValue(object val) {
if (lastValue?.ToString() != val?.ToString()) {
var beforeVal = lastValue;
var beforeValStr = GetValueString();
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
}
}
}
}

View File

@@ -16,8 +16,6 @@ namespace MewtocolNet.Registers {
/// <typeparam name="T">The type of the numeric value</typeparam> /// <typeparam name="T">The type of the numeric value</typeparam>
public class StructRegister<T> : Register, IRegister<T> where T : struct { public class StructRegister<T> : Register, IRegister<T> where T : struct {
internal uint byteLength;
internal uint addressLength; internal uint addressLength;
/// <summary> /// <summary>
@@ -35,26 +33,20 @@ namespace MewtocolNet.Registers {
memoryAddress = _address; memoryAddress = _address;
name = _name; name = _name;
Resize(_reservedByteSize);
addressLength = _reservedByteSize / 2;
if (_reservedByteSize % 2 != 0) addressLength++;
if (_reservedByteSize == 2) RegisterType = RegisterType.DT; if (_reservedByteSize == 2) RegisterType = RegisterType.DT;
if(_reservedByteSize == 4) RegisterType = RegisterType.DDT; if(_reservedByteSize == 4) RegisterType = RegisterType.DDT;
if (typeof(T) == typeof(string)) RegisterType = RegisterType.DT_BYTE_RANGE;
CheckAddressOverflow(memoryAddress, addressLength); CheckAddressOverflow(memoryAddress, addressLength);
underlyingSystemType = typeof(T);
lastValue = null; lastValue = null;
} }
private void Resize (uint reservedByteSize) {
addressLength = reservedByteSize / 2;
if (reservedByteSize % 2 != 0) addressLength++;
byteLength = reservedByteSize;
}
/// <inheritdoc/> /// <inheritdoc/>
public override string GetAsPLC() { public override string GetAsPLC() {
@@ -92,7 +84,7 @@ namespace MewtocolNet.Registers {
public override uint GetRegisterAddressLen() => AddressLength; public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> WriteAsync(T value) { public async Task WriteAsync(T value) {
var encoded = PlcValueParser.Encode(this, (T)value); var encoded = PlcValueParser.Encode(this, (T)value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
@@ -111,27 +103,19 @@ namespace MewtocolNet.Registers {
} }
return res;
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<T?> ReadAsync() { public async Task<T> ReadAsync() {
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return (T?)(object)null; if (res == null) throw new Exception($"Failed to read the register {this}");
var matchingReg = attachedInterface.memoryManager.GetAllRegisters() var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this)); .FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null) { if (matchingReg != null) {
//if (matchingReg is StructRegister<string> sreg && this.GetType() == typeof(StructRegister<string>)) {
// sreg.addressLength = selfSreg.addressLength;
//}
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
} }
@@ -142,16 +126,6 @@ namespace MewtocolNet.Registers {
internal override object SetValueFromBytes (byte[] bytes) { internal override object SetValueFromBytes (byte[] bytes) {
//if string correct the sizing of the byte hint was wrong
if (typeof(T) == typeof(string)) {
var reservedSize = BitConverter.ToInt16(bytes, 0);
if (reservedSize != byteLength - 4)
throw new NotSupportedException(
$"The STRING register at {GetMewName()} is not correctly sized, " +
$"the size should be STRING[{reservedSize}] instead of STRING[{byteLength - 4}]"
);
}
AddSuccessRead(); AddSuccessRead();
var parsed = PlcValueParser.Parse<T>(this, bytes); var parsed = PlcValueParser.Parse<T>(this, bytes);

View File

@@ -0,0 +1,43 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Provides an abstruct enumerable interface for one dimensional array registers
/// </summary>
public interface IArrayRegister<T> : IReadOnlyList<T>, IRegister {
/// <summary>
/// Reads the register value async from the plc
/// </summary>
/// <returns>The register value</returns>
Task<T[]> ReadAsync();
/// <summary>
/// Writes a whole array to the plc
/// </summary>
/// <returns>True if successfully set</returns>
Task WriteAsync(T[] data);
///// <summary>
///// Writes a single item to the array, this saves bandwidth
///// </summary>
///// <param name="i">Index of the element to write</param>
///// <param name="data">The value to overwrite</param>
///// <returns>True if successfully set</returns>
//Task WriteEntryAsync(int i, T data);
T[] Value { get; }
/// <summary>
/// The current value of the register at the position
/// </summary>
new T this[int i] { get; }
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Provides an abstruct enumerable interface for two dimensional array registers
/// </summary>
public interface IArrayRegister2D<T> : IReadOnlyList<T>, IRegister {
/// <summary>
/// Reads the register value async from the plc
/// </summary>
/// <returns>The register value</returns>
Task<T[,]> ReadAsync();
/// <summary>
/// Writes a whole array to the plc
/// </summary>
/// <returns>True if successfully set</returns>
Task WriteAsync(T[,] data);
T[,] Value { get; }
/// <summary>
/// The current value of the register at the position
/// </summary>
T this[int i1, int i2] { get; }
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Provides an abstruct enumerable interface for three dimensional array registers
/// </summary>
public interface IArrayRegister3D<T> : IReadOnlyList<T>, IRegister {
/// <summary>
/// Reads the register value async from the plc
/// </summary>
/// <returns>The register value</returns>
Task<T[,,]> ReadAsync();
/// <summary>
/// Writes a whole array to the plc
/// </summary>
/// <returns>True if successfully set</returns>
Task WriteAsync(T[,,] data);
T[,,] Value { get; }
/// <summary>
/// The current value of the register at the position
/// </summary>
T this[int i1, int i2, int i3] { get; }
}
}

View File

@@ -5,7 +5,7 @@ using MewtocolNet.Events;
namespace MewtocolNet.Registers { namespace MewtocolNet.Registers {
/// <summary> /// <summary>
/// An interface for all register types /// An interface for all struct register types
/// </summary> /// </summary>
public interface IRegister<T> : IRegister where T : struct { public interface IRegister<T> : IRegister where T : struct {
@@ -18,13 +18,13 @@ namespace MewtocolNet.Registers {
/// Reads the register value async from the plc /// Reads the register value async from the plc
/// </summary> /// </summary>
/// <returns>The register value</returns> /// <returns>The register value</returns>
Task<T?> ReadAsync(); Task<T> ReadAsync();
/// <summary> /// <summary>
/// Writes the register content async to the plc /// Writes the register content async to the plc
/// </summary> /// </summary>
/// <returns>True if successfully set</returns> /// <returns>True if successfully set</returns>
Task<bool> WriteAsync(T data); Task WriteAsync(T data);
} }

View File

@@ -2,24 +2,24 @@
namespace MewtocolNet.Registers { namespace MewtocolNet.Registers {
public interface IArrayRegister<T> : IRegister { public interface IStringRegister : IRegister {
/// <summary> /// <summary>
/// The current value of the register /// The current value of the register
/// </summary> /// </summary>
T[] Value { get; } string Value { get; }
/// <summary> /// <summary>
/// Reads the register value async from the plc /// Reads the register value async from the plc
/// </summary> /// </summary>
/// <returns>The register value</returns> /// <returns>The register value</returns>
Task<T[]> ReadAsync(); Task<string> ReadAsync();
/// <summary> /// <summary>
/// Writes the register content async to the plc /// Writes the register content async to the plc
/// </summary> /// </summary>
/// <returns>True if successfully set</returns> /// <returns>True if successfully set</returns>
Task<bool> WriteAsync(T data); Task WriteAsync(string data);
} }

View File

@@ -5,6 +5,10 @@ namespace MewtocolNet.SetupClasses {
internal class PollLevelConfig { internal class PollLevelConfig {
internal bool skipsAll;
internal bool skipAllButFirst;
internal TimeSpan? delay; internal TimeSpan? delay;
internal int? skipNth; internal int? skipNth;

View File

@@ -130,48 +130,48 @@ namespace MewtocolNet.TypeConversion {
//default string DT Range conversion Example bytes: (04 00 03 00 XX XX XX) //default string DT Range conversion Example bytes: (04 00 03 00 XX XX XX)
//first 4 bytes are reserved size (2 bytes) and used size (2 bytes) //first 4 bytes are reserved size (2 bytes) and used size (2 bytes)
//the remaining bytes are the ascii bytes for the string //the remaining bytes are the ascii bytes for the string
//new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) { new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
// HoldingRegisterType = typeof(StructRegister<string>), HoldingRegisterType = typeof(StringRegister),
// PlcVarType = PlcVarType.STRING, PlcVarType = PlcVarType.STRING,
// FromRaw = (reg, bytes) => { FromRaw = (reg, bytes) => {
// if(bytes.Length == 4) return string.Empty; if(bytes.Length == 4) return string.Empty;
// if(bytes == null || bytes.Length < 4) { if(bytes == null || bytes.Length < 4) {
// throw new Exception("Failed to convert string bytes, response not long enough"); throw new Exception("Failed to convert string bytes, response not long enough");
// } }
// //get actual showed size //get actual showed size
// short actualLen = BitConverter.ToInt16(bytes, 2); short actualLen = BitConverter.ToInt16(bytes, 2);
// //skip 4 bytes because they only describe the length //skip 4 bytes because they only describe the length
// string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray()); string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray());
// return gotVal; return gotVal;
// }, },
// ToRaw = (reg, value) => { ToRaw = (reg, value) => {
// int padLen = value.Length; int padLen = value.Length;
// if(value.Length % 2 != 0) padLen++; if(value.Length % 2 != 0) padLen++;
// var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0')); var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0'));
// List<byte> finalBytes = new List<byte>(); List<byte> finalBytes = new List<byte>();
// short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4); short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4);
// short used = (short)value.Length; short used = (short)value.Length;
// finalBytes.AddRange(BitConverter.GetBytes(reserved)); finalBytes.AddRange(BitConverter.GetBytes(reserved));
// finalBytes.AddRange(BitConverter.GetBytes(used)); finalBytes.AddRange(BitConverter.GetBytes(used));
// finalBytes.AddRange(strBytes); finalBytes.AddRange(strBytes);
// return finalBytes.ToArray(); return finalBytes.ToArray();
// }, },
//}, },
}; };

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
namespace MewtocolNet { namespace MewtocolNet {
@@ -36,10 +37,10 @@ namespace MewtocolNet {
} }
internal static T ParseArray <T>(Register register, int[] indices, byte[] bytes) { internal static object ParseArray <T>(ArrayRegister<T> register, byte[] bytes) {
//if byte array directly return the bytes //if byte array directly return the bytes
if (typeof(T) == typeof(byte[])) return (T)(object)bytes; if (typeof(T) == typeof(byte[])) return bytes;
IPlcTypeConverter converter; IPlcTypeConverter converter;
Type underlyingElementType; Type underlyingElementType;
@@ -47,11 +48,11 @@ namespace MewtocolNet {
//special case for enums //special case for enums
if (typeof(T).IsEnum) { if (typeof(T).IsEnum) {
underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType(); underlyingElementType = typeof(T).GetEnumUnderlyingType();
} else { } else {
underlyingElementType = typeof(T).GetElementType(); underlyingElementType = typeof(T);
} }
@@ -61,17 +62,18 @@ namespace MewtocolNet {
throw new Exception($"A converter for the type {underlyingElementType} doesn't exist"); throw new Exception($"A converter for the type {underlyingElementType} doesn't exist");
//parse the array from one to n dimensions //parse the array from one to n dimensions
var outArray = Array.CreateInstance(underlyingElementType, indices); var outArray = Array.CreateInstance(typeof(T), register.indices);
int sizePerItem = 0; int sizePerItem = 0;
if(underlyingElementType == typeof(string)) { if(underlyingElementType == typeof(string)) {
throw new NotImplementedException(); sizePerItem = register.byteSizePerItem;
} else { } else {
sizePerItem = underlyingElementType.DetermineTypeByteIntialSize(); sizePerItem = underlyingElementType.DetermineTypeByteIntialSize();
} }
var iterateItems = indices.Aggregate((a, x) => a * x); var iterateItems = register.indices.Aggregate((a, x) => a * x);
var indexer = new int[indices.Length]; var indexer = new int[register.indices.Length];
for (int i = 0; i < iterateItems; i++) { for (int i = 0; i < iterateItems; i++) {
int j = i * sizePerItem; int j = i * sizePerItem;
@@ -79,9 +81,9 @@ namespace MewtocolNet {
var currentItem = bytes.Skip(j).Take(sizePerItem).ToArray(); var currentItem = bytes.Skip(j).Take(sizePerItem).ToArray();
var value = converter.FromRawData(register, currentItem); var value = converter.FromRawData(register, currentItem);
for (int remainder = i, k = indices.Length - 1; k >= 0; k--) { for (int remainder = i, k = register.indices.Length - 1; k >= 0; k--) {
int currentDimension = indices[k]; int currentDimension = register.indices[k];
indexer[k] = remainder % currentDimension; indexer[k] = remainder % currentDimension;
remainder = remainder / currentDimension; remainder = remainder / currentDimension;
@@ -91,7 +93,7 @@ namespace MewtocolNet {
} }
return (T)(object)outArray; return (object)outArray;
} }
@@ -120,10 +122,10 @@ namespace MewtocolNet {
} }
internal static byte[] EncodeArray<T>(Register register, T value) { internal static byte[] EncodeArray<T>(ArrayRegister<T> register, object value) {
//if byte array directly return the bytes //if byte array directly return the bytes
if (typeof(T) == typeof(byte[])) return (byte[])(object)value; if (value.GetType() == typeof(byte[])) return (byte[])value;
IPlcTypeConverter converter; IPlcTypeConverter converter;
Type underlyingElementType; Type underlyingElementType;
@@ -131,11 +133,11 @@ namespace MewtocolNet {
//special case for enums //special case for enums
if (typeof(T).IsEnum) { if (typeof(T).IsEnum) {
underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType(); underlyingElementType = typeof(T).GetEnumUnderlyingType();
} else { } else {
underlyingElementType = typeof(T).GetElementType(); underlyingElementType = typeof(T);
} }
@@ -145,8 +147,9 @@ namespace MewtocolNet {
throw new Exception($"A converter for the type {underlyingElementType} doesn't exist"); throw new Exception($"A converter for the type {underlyingElementType} doesn't exist");
int sizePerItem = 0; int sizePerItem = 0;
if (underlyingElementType == typeof(string)) { if (underlyingElementType == typeof(string)) {
throw new NotImplementedException(); sizePerItem = register.byteSizePerItem;
} else { } else {
sizePerItem = underlyingElementType.DetermineTypeByteIntialSize(); sizePerItem = underlyingElementType.DetermineTypeByteIntialSize();
} }
@@ -158,12 +161,19 @@ namespace MewtocolNet {
var encoded = converter.ToRawData(register, item); var encoded = converter.ToRawData(register, item);
if(encoded.Length > register.byteSizePerItem)
throw new ArgumentOutOfRangeException(nameof(value), "Input mismatched register target size");
encoded.CopyTo(encodedData, i); encoded.CopyTo(encodedData, i);
i += sizePerItem; i += sizePerItem;
} }
if (encodedData.Length != register.reservedByteSize)
throw new ArgumentOutOfRangeException(nameof(value), "Input mismatched register target size");
return encodedData; return encodedData;
} }

View File

@@ -106,12 +106,24 @@ namespace MewtocolNet.UnderlyingRegisters {
private void TestPollLevelExistence(Register reg) { private void TestPollLevelExistence(Register reg) {
if (!pollLevelConfigs.ContainsKey(1)) { if (!pollLevelConfigs.ContainsKey(MewtocolNet.PollLevel.Always)) {
pollLevelConfigs.Add(1, new PollLevelConfig { pollLevelConfigs.Add(MewtocolNet.PollLevel.Always, new PollLevelConfig {
skipNth = 1, skipNth = 1,
}); });
} }
if (!pollLevelConfigs.ContainsKey(MewtocolNet.PollLevel.FirstIteration)) {
pollLevelConfigs.Add(MewtocolNet.PollLevel.FirstIteration, new PollLevelConfig {
skipAllButFirst = true
});
}
if (!pollLevelConfigs.ContainsKey(MewtocolNet.PollLevel.Never)) {
pollLevelConfigs.Add(MewtocolNet.PollLevel.Never, new PollLevelConfig {
skipsAll = true,
});
}
if (!pollLevels.Any(x => x.level == reg.pollLevel)) { if (!pollLevels.Any(x => x.level == reg.pollLevel)) {
pollLevels.Add(new PollLevel(wrAreaSize, dtAreaSize) { pollLevels.Add(new PollLevel(wrAreaSize, dtAreaSize) {
@@ -294,10 +306,14 @@ namespace MewtocolNet.UnderlyingRegisters {
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
var lvlConfig = pollLevelConfigs[pollLevel.level];
if (lvlConfig.skipsAll) continue;
if (lvlConfig.skipAllButFirst && pollIteration > 0) continue;
//determine to skip poll levels, first iteration is always polled //determine to skip poll levels, first iteration is always polled
if (pollIteration > 0 && pollLevel.level > 1) { if (pollIteration > 0 && pollLevel.level > 1) {
var lvlConfig = pollLevelConfigs[pollLevel.level];
var skipIterations = lvlConfig.skipNth; var skipIterations = lvlConfig.skipNth;
var skipDelay = lvlConfig.delay; var skipDelay = lvlConfig.delay;
@@ -360,14 +376,32 @@ namespace MewtocolNet.UnderlyingRegisters {
foreach (var pollLevel in pollLevels) { foreach (var pollLevel in pollLevels) {
sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ===="); if (pollLevel.level == MewtocolNet.PollLevel.Always) {
sb.AppendLine(); sb.AppendLine($"\n> ==== Poll lvl ALWAYS ====\n");
sb.AppendLine($"> Poll each iteration");
}else if (pollLevel.level == MewtocolNet.PollLevel.FirstIteration) {
sb.AppendLine($"\n> ==== Poll lvl FIRST ITERATION ====\n");
sb.AppendLine($"> Poll only on the first iteration");
} else if (pollLevel.level == MewtocolNet.PollLevel.Never) {
sb.AppendLine($"\n> ==== Poll lvl NEVER ====\n");
sb.AppendLine($"> Poll never");
} else {
sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ====\n");
if (pollLevelConfigs.ContainsKey(pollLevel.level) && pollLevelConfigs[pollLevel.level].delay != null) { if (pollLevelConfigs.ContainsKey(pollLevel.level) && pollLevelConfigs[pollLevel.level].delay != null) {
sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms"); sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms");
} else if (pollLevelConfigs.ContainsKey(pollLevel.level)) { } else if (pollLevelConfigs.ContainsKey(pollLevel.level)) {
sb.AppendLine($"> Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations"); sb.AppendLine($"> Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations");
} }
}
sb.AppendLine($"> Level read time: {pollLevel.lastReadTimeMs}ms"); sb.AppendLine($"> Level read time: {pollLevel.lastReadTimeMs}ms");
sb.AppendLine($"> Optimization distance: {maxOptimizationDistance}"); sb.AppendLine($"> Optimization distance: {maxOptimizationDistance}");
sb.AppendLine(); sb.AppendLine();

View File

@@ -14,124 +14,6 @@ namespace MewtocolTests {
this.output = output; this.output = output;
} }
private void Test(IRegisterInternal reg, uint expectAddr, string expectPlcName) {
Assert.NotNull(reg);
Assert.StartsWith("auto_prop_register_", reg.Name);
Assert.Null(reg.Value);
Assert.Equal(expectAddr, reg.MemoryAddress);
Assert.Equal(expectPlcName, reg.GetMewName());
output.WriteLine(reg.ToString());
}
//actual tests
[Fact(DisplayName = "Boolean generation")]
public void BooleanGen() {
var interf = Mewtocol.Ethernet("192.168.0.1")
.WithRegisterCollections(x =>
x.AddCollection(new TestBoolRegisters())
).Build();
output.WriteLine(((MewtocolInterface)interf).memoryManager.ExplainLayout());
var register1 = interf.GetRegister("auto_prop_register_1");
var register2 = interf.GetRegister("auto_prop_register_2");
var register3 = interf.GetRegister("auto_prop_register_3");
Test((IRegisterInternal)register1, 0, "XD");
Test((IRegisterInternal)register2, 85, "R85A");
Test((IRegisterInternal)register3, 85, "R85B");
}
[Fact(DisplayName = "Number 16 bit generation")]
public void N16BitGen () {
var interf = Mewtocol.Ethernet("192.168.0.1")
.WithRegisterCollections(x =>
x.AddCollection(new Nums16Bit())
).Build();
var register1 = interf.GetRegister("auto_prop_register_1");
var register2 = interf.GetRegister("auto_prop_register_2");
var register3 = interf.GetRegister("auto_prop_register_3");
//test generic properties
Test((IRegisterInternal)register1, 50, "DT50");
Test((IRegisterInternal)register2, 342, "DT342");
Test((IRegisterInternal)register3, 899, "DT899");
}
[Fact(DisplayName = "Number 32 bit generation")]
public void N32BitGen () {
var interf = Mewtocol.Ethernet("192.168.0.1")
.WithRegisterCollections(x => x
.AddCollection(new Nums32Bit())
).Build();
output.WriteLine(((MewtocolInterface)interf).memoryManager.ExplainLayout());
var register1 = interf.GetRegister("auto_prop_register_1");
var register2 = interf.GetRegister("auto_prop_register_2");
var register3 = interf.GetRegister("auto_prop_register_3");
//only one generated because same type
var register4 = interf.GetRegister("auto_prop_register_4");
var register6 = interf.GetRegister("auto_prop_register_5");
var register7 = interf.GetRegister("auto_prop_register_6");
//test generic properties
Test((IRegisterInternal)register1, 7000, "DDT7000");
Test((IRegisterInternal)register2, 7002, "DDT7002");
Test((IRegisterInternal)register3, 7004, "DDT7004");
Test((IRegisterInternal)register4, 7006, "DDT7006");
Test((IRegisterInternal)register6, 7008, "DDT7008");
Test((IRegisterInternal)register7, 7010, "DDT7010");
}
[Fact(DisplayName = "String generation")]
public void StringGen() {
var interf = Mewtocol.Ethernet("192.168.0.1")
.WithRegisterCollections(x =>
x.AddCollection(new TestStringRegisters())
).Build();
var register1 = interf.GetRegister("auto_prop_register_1");
//test generic properties
Test((IRegisterInternal)register1, 7005, "DT7005");
}
[Fact(DisplayName = "Byte Array generation")]
public void ByteArrGen() {
var interf = Mewtocol.Ethernet("192.168.0.1")
.WithRegisterCollections(x =>
x.AddCollection(new TestBitwiseRegisters())
).Build();
var register1 = interf.GetRegister("auto_prop_register_1");
//var register2 = interf.GetRegister("auto_prop_register_2");
//test generic properties
Test((IRegisterInternal)register1, 7000, "DT7000");
//Test((IRegisterInternal)register2, 7001, "DT7001");
}
} }
} }

View File

@@ -1,22 +0,0 @@
using MewtocolNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolTests.EncapsulatedTests;
internal class RegisterReadWriteTest {
public IRegister TargetRegister { get; set; }
public object IntialValue { get; set; }
public object IntermediateValue { get; set; }
public object AfterWriteValue { get; set; }
public string RegisterPlcAddressName { get; set; }
}

View File

@@ -4,7 +4,7 @@ using System.Collections;
namespace MewtocolTests.EncapsulatedTests { namespace MewtocolTests.EncapsulatedTests {
public enum CurrentState : short { public enum CurrentState16 : short {
Undefined = 0, Undefined = 0,
State1 = 1, State1 = 1,
State2 = 2, State2 = 2,
@@ -51,7 +51,7 @@ namespace MewtocolTests.EncapsulatedTests {
public ushort UInt16Type { get; set; } public ushort UInt16Type { get; set; }
[Register("DT50")] [Register("DT50")]
public CurrentState Enum16Type { get; set; } public CurrentState16 Enum16Type { get; set; }
} }
@@ -95,8 +95,6 @@ namespace MewtocolTests.EncapsulatedTests {
public class TestBitwiseRegisters : RegisterCollection { public class TestBitwiseRegisters : RegisterCollection {
[Register("DT7000")]
public BitArray BitArr16 { get; set; }
//[Register("DT7001")] //[Register("DT7001")]
//public BitArray BitArr32 { get; set; } //public BitArray BitArr32 { get; set; }

View File

@@ -1,36 +0,0 @@
using MewtocolNet;
using MewtocolNet.DocAttributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace MewtocolTests;
public class SkippedChecks {
private readonly ITestOutputHelper output;
public SkippedChecks(ITestOutputHelper output) {
this.output = output;
}
[Fact]
public void BuildBCCFrameGeneration() {
var toSuccess = new List<Type> {
typeof(PlcCodeTestedAttribute),
typeof(PlcEXRTAttribute),
typeof(PlcLegacyAttribute),
};
Assert.NotNull(toSuccess);
}
}

View File

@@ -66,18 +66,18 @@ namespace MewtocolTests {
} }
[Fact(DisplayName = nameof(MewtocolHelpers.ParseDTByteString))] [Fact(DisplayName = nameof(MewtocolHelpers.ParseDTRawStringAsBytes))]
public void ParseDTByteStringGeneration() { public void ParseDTByteStringGeneration() {
var testList = new List<string>() { var testList = new List<byte[]>() {
"1112", new byte[] {0x11, 0x12},
"1C2C", new byte[] {0x1C, 0x2C},
"FFFF", new byte[] {0xFF, 0xFF},
}; };
foreach (var item in testList) { foreach (var item in testList) {
Assert.Equal(item, $"%01$RD{item}".BCC_Mew().ParseDTByteString()); Assert.Equal(item, $"%01$RD{item.ToHexString()}".BCC_Mew().ParseDTRawStringAsBytes());
} }

View File

@@ -37,59 +37,6 @@ namespace MewtocolTests
}; };
private List<RegisterReadWriteTest> testRegisterRW = new() {
new RegisterReadWriteTest {
TargetRegister = new BoolRegister(IOType.R, 0xA, 10),
RegisterPlcAddressName = "R10A",
IntermediateValue = false,
AfterWriteValue = true,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<short>(3000),
RegisterPlcAddressName = "DT3000",
IntermediateValue = (short)0,
AfterWriteValue = (short)-513,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<CurrentState>(3001),
RegisterPlcAddressName = "DT3001",
IntermediateValue = CurrentState.Undefined,
AfterWriteValue = CurrentState.State4,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<CurrentState32>(3002),
RegisterPlcAddressName = "DDT3002",
IntermediateValue = CurrentState32.Undefined,
AfterWriteValue = CurrentState32.StateBetween,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<TimeSpan>(3004),
RegisterPlcAddressName = "DDT3004",
IntermediateValue = TimeSpan.Zero,
AfterWriteValue = TimeSpan.FromSeconds(11),
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<TimeSpan>(3006),
RegisterPlcAddressName = "DDT3006",
IntermediateValue = TimeSpan.Zero,
AfterWriteValue = PlcFormat.ParsePlcTime("T#50m"),
},
new RegisterReadWriteTest {
TargetRegister = new StringRegister(40),
RegisterPlcAddressName = "DT40",
IntermediateValue = "Hello",
AfterWriteValue = "TestV",
},
new RegisterReadWriteTest {
TargetRegister = RegBuilder.Factory.FromPlcRegName("DT3008").AsBits(5).Build(),
RegisterPlcAddressName = "DT3008",
IntermediateValue = new BitArray(new bool[] { false, false, false, false, false }),
AfterWriteValue = new BitArray(new bool[] { false, true, false, false, false }),
},
};
public TestLivePLC(ITestOutputHelper output) { public TestLivePLC(ITestOutputHelper output) {
this.output = output; this.output = output;
@@ -141,58 +88,6 @@ namespace MewtocolTests
} }
[Fact(DisplayName = "Reading / Writing registers from PLC (Ethernet)")]
public async void TestRegisterReadWriteAsync() {
Logger.LogLevel = LogLevel.Verbose;
Logger.OnNewLogMessage((d, l, m) => {
output.WriteLine($"{d:HH:mm:ss:fff} {m}");
});
var plc = testPlcInformationData[0];
output.WriteLine($"\n\n --- Testing: {plc.PLCName} ---\n");
var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort).Build();
foreach (var testRW in testRegisterRW) {
client.AddRegister(testRW.TargetRegister);
}
await client.ConnectAsync();
Assert.True(client.IsConnected);
//cycle run mode to reset registers to inital
await client.SetOperationModeAsync(false);
await client.SetOperationModeAsync(true);
foreach (var testRW in testRegisterRW) {
var testRegister = client.Registers.First(x => x.PLCAddressName == testRW.RegisterPlcAddressName);
//test inital val
Assert.Null(testRegister.Value);
await testRegister.ReadAsync();
Assert.Equal(testRW.IntermediateValue, testRegister.Value);
await testRegister.WriteAsync(testRW.AfterWriteValue);
await testRegister.ReadAsync();
//test after write val
Assert.Equal(testRW.AfterWriteValue, testRegister.Value);
}
client.Disconnect();
}
} }
} }

View File

@@ -0,0 +1,89 @@
using MewtocolNet;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests;
using System.Collections;
using Xunit;
using Xunit.Abstractions;
namespace MewtocolTests;
public class TestPublicBuilderPattern {
private readonly ITestOutputHelper output;
public TestPublicBuilderPattern(ITestOutputHelper output) => this.output = output;
private void TestStruct<T> (string buildAddr, uint expectAddr, uint expectByteSize) where T : struct {
using var interf = (MewtocolInterface)Mewtocol.Ethernet("192.168.115.210").Build();
var builder = new RBuild(interf);
var comparer = new StructRegister<T>(expectAddr, expectByteSize) {
attachedInterface = interf,
pollLevel = 1,
};
//test building to the internal list
builder.Struct<T>(buildAddr).Build();
var generated = builder.assembler.assembled.First();
Assert.Equivalent(comparer, generated);
builder.assembler.assembled.Clear();
output.WriteLine(generated.Explain());
//test building with direct out
builder.Struct<T>(buildAddr).Build(out var testRef);
Assert.Equivalent(comparer, testRef);
builder.assembler.assembled.Clear();
output.WriteLine(((Register)testRef).Explain());
comparer.pollLevel++;
//test building to the internal list with poll level
builder.Struct<T>(buildAddr).PollLevel(2).Build();
var generated2 = builder.assembler.assembled.First();
Assert.Equivalent(comparer, generated2);
builder.assembler.assembled.Clear();
output.WriteLine(generated2.Explain());
//test building direct out with poll level
builder.Struct<T>(buildAddr).PollLevel(2).Build(out var testRef2);
Assert.Equivalent(comparer, testRef2);
builder.assembler.assembled.Clear();
output.WriteLine(((Register)testRef2).Explain());
}
//16 bit structs
[Fact(DisplayName = "[16 Bit] short")]
public void TestStruct_1() => TestStruct<short>("DT100", 100, 2);
[Fact(DisplayName = "[16 Bit] ushort")]
public void TestStruct_2() => TestStruct<ushort>("DT101", 101, 2);
[Fact(DisplayName = "[16 Bit] Word")]
public void TestStruct_3() => TestStruct<Word>("DT102", 102, 2);
[Fact(DisplayName = "[16 Bit] Enum")]
public void TestStruct_4() => TestStruct<CurrentState16>("DT103", 103, 2);
//32 bit structs
[Fact(DisplayName = "[32 Bit] int")]
public void TestStruct_5() => TestStruct<int>("DT104", 104, 4);
[Fact(DisplayName = "[32 Bit] uint")]
public void TestStruct_6() => TestStruct<uint>("DT105", 105, 4);
[Fact(DisplayName = "[32 Bit] DWord")]
public void TestStruct_7() => TestStruct<DWord>("DT106", 106, 4);
[Fact(DisplayName = "[32 Bit] Enum")]
public void TestStruct_8() => TestStruct<CurrentState32>("DT107", 107, 4);
[Fact(DisplayName = "[32 Bit] TimeSpan")]
public void TestStruct_9() => TestStruct<TimeSpan>("DT108", 108, 4);
}

View File

@@ -0,0 +1,63 @@
using MewtocolNet;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests;
using System.Collections;
using System.Collections.Generic;
using Xunit;
using Xunit.Abstractions;
namespace MewtocolTests;
public class TestPublicBuilderPatternArray {
private readonly ITestOutputHelper output;
public TestPublicBuilderPatternArray(ITestOutputHelper output) => this.output = output;
private void TestArray1D<T> (string buildAddr, int indices1, uint expectAddr, uint expectByteSize) where T : struct {
using var interf = (MewtocolInterface)Mewtocol.Ethernet("192.168.115.210").Build();
var builder = new RBuild(interf);
var comparer = new ArrayRegister<T>(expectAddr, expectByteSize, new int[] { indices1 }) {
attachedInterface = interf,
pollLevel = 1
};
//test building to the internal list
builder.Struct<T>(buildAddr).AsArray(indices1).Build();
var generated = builder.assembler.assembled.First();
Assert.Equivalent(comparer, generated);
builder.assembler.assembled.Clear();
output.WriteLine(generated.Explain());
////test building with direct out
//builder.Struct<T>(buildAddr).AsArray(indices1).Build(out var testRef);
//Assert.Equivalent(comparer, testRef);
//builder.assembler.assembled.Clear();
//output.WriteLine(((Register)testRef).Explain());
//comparer.pollLevel++;
////test building to the internal list with poll level
//builder.Struct<T>(buildAddr).AsArray(indices1).PollLevel(2).Build();
//var generated2 = builder.assembler.assembled.First();
//Assert.Equivalent(comparer, generated2);
//builder.assembler.assembled.Clear();
//output.WriteLine(generated2.Explain());
////test building direct out with poll level
//builder.Struct<T>(buildAddr).AsArray(indices1).PollLevel(2).Build(out var testRef2);
//Assert.Equivalent(comparer, testRef2);
//builder.assembler.assembled.Clear();
//output.WriteLine(((Register)testRef2).Explain());
}
//16 bit structs
}

View File

@@ -1,5 +1,6 @@
using MewtocolNet; using MewtocolNet;
using MewtocolNet.RegisterBuilding; using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers; using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests; using MewtocolTests.EncapsulatedTests;
using System.Collections; using System.Collections;
@@ -123,137 +124,9 @@ public class TestRegisterBuilder {
foreach (var item in dict) { foreach (var item in dict) {
output.WriteLine($"Expected: {item.Key}");
var built = RegBuilder.Factory.FromPlcRegName(item.Key).AsPlcType(PlcVarType.BOOL).Build();
output.WriteLine($"{(built?.ToString(true) ?? "null")}\n");
Assert.Equivalent(item.Value, built);
} }
} }
[Fact(DisplayName = "Parsing as Bool Register (Casted)")]
public void TestRegisterBuildingBoolCasted () {
var expect = new BoolRegister(IOType.R, 0x1, 0);
var expect2 = new BoolRegister(IOType.Y, 0xA, 103);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").AsPlcType(PlcVarType.BOOL).Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").AsType<bool>().Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").AsPlcType(PlcVarType.BOOL).Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").AsType<bool>().Build());
}
[Fact(DisplayName = "Parsing as Bool Register (Auto)")]
public void TestRegisterBuildingBoolAuto () {
var expect = new BoolRegister(IOType.R, 0x1, 0);
var expect2 = new BoolRegister(IOType.Y, 0xA, 103);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").Build());
}
[Fact(DisplayName = "Parsing as Number Register (Casted)")]
public void TestRegisterBuildingNumericCasted () {
var expect = new NumberRegister<short>(303);
var expect2 = new NumberRegister<int>(10002);
var expect3 = new NumberRegister<float>(404);
var expect4 = new NumberRegister<TimeSpan>(400);
var expect5 = new NumberRegister<CurrentState>(203);
var expect6 = new NumberRegister<CurrentState32>(204);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType<short>().Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsPlcType(PlcVarType.DINT).Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsType<int>().Build());
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT404").AsPlcType(PlcVarType.REAL).Build());
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT404").AsType<float>().Build());
Assert.Equivalent(expect4, RegBuilder.Factory.FromPlcRegName("DDT400").AsPlcType(PlcVarType.TIME).Build());
Assert.Equivalent(expect4, RegBuilder.Factory.FromPlcRegName("DDT400").AsType<TimeSpan>().Build());
Assert.Equivalent(expect5, RegBuilder.Factory.FromPlcRegName("DT203").AsType<CurrentState>().Build());
Assert.Equivalent(expect6, RegBuilder.Factory.FromPlcRegName("DT204").AsType<CurrentState32>().Build());
}
[Fact(DisplayName = "Parsing as Number Register (Auto)")]
public void TestRegisterBuildingNumericAuto () {
var expect = new NumberRegister<short>(201);
var expect2 = new NumberRegister<int>(10002);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT201").Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").Build());
}
[Fact(DisplayName = "Parsing as Bytes Register (Casted)")]
public void TestRegisterBuildingByteRangeCasted () {
var expect = new BytesRegister(305, (uint)35);
Assert.Equal((uint)18, expect.AddressLength);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT305").AsBytes(35).Build());
}
[Fact(DisplayName = "Parsing as Bytes Register (Auto)")]
public void TestRegisterBuildingByteRangeAuto () {
var expect = new BytesRegister(300, (uint)20 * 2);
var actual = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT300-DT319").Build();
Assert.Equal((uint)20, expect.AddressLength);
Assert.Equivalent(expect, actual);
}
[Fact(DisplayName = "Parsing as Bit Array")]
public void TestRegisterBuildingBitArray () {
var expect1 = new BytesRegister(311, (ushort)5);
var expect2 = new BytesRegister(312, (ushort)16);
var expect3 = new BytesRegister(313, (ushort)32);
var actual1 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT311").AsBits(5).Build();
var actual2 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT312").AsBits(16).Build();
var actual3 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT313").AsBits(32).Build();
Assert.Equivalent(expect1, actual1);
Assert.Equivalent(expect2, actual2);
Assert.Equivalent(expect3, actual3);
Assert.Equal((uint)1, actual1.AddressLength);
Assert.Equal((uint)1, actual2.AddressLength);
Assert.Equal((uint)2, actual3.AddressLength);
}
[Fact(DisplayName = "Parsing as String Register")]
public void TestRegisterBuildingString () {
var expect1 = new StringRegister(314);
var actual1 = (StringRegister)RegBuilder.Factory.FromPlcRegName("DT314").AsType<string>().Build();
Assert.Equivalent(expect1, actual1);
Assert.Equal((uint)0, actual1.WordsSize);
}
} }

View File

@@ -13,113 +13,22 @@ namespace MewtocolTests {
this.output = output; this.output = output;
} }
[Fact(DisplayName = "Numeric mewtocol query building")] [Fact(DisplayName = "Non allowed Struct Address (Overflow address)")]
public void NumericRegisterMewtocolIdentifiers() { public void OverFlowStructRegister() {
List<IRegisterInternal> registers = new List<IRegisterInternal> {
new NumberRegister<short>(50),
new NumberRegister<ushort>(50),
new NumberRegister<int>(50),
new NumberRegister<uint>(50),
new NumberRegister<float>(50),
new NumberRegister<TimeSpan>(50),
new BytesRegister(50, (uint)30),
new BytesRegister(50, (uint)31),
};
List<string> expectedIdents = new List<string> {
"D0005000050", //single word register
"D0005000050", //single word register
"D0005000051", //double word register
"D0005000051", //double word register
"D0005000051", //double word register
"D0005000051", //double word register
"D0005000064", //variable len register even bytes
"D0005000065", //variable len register odd bytes
};
//test mewtocol idents
for (int i = 0; i < registers.Count; i++) {
IRegisterInternal? reg = registers[i];
string expect = expectedIdents[i];
Assert.Equal(expect, reg.BuildMewtocolQuery());
}
}
[Fact(DisplayName = "PLC register naming convention test")]
public void PLCRegisterIdentifiers() {
List<IRegisterInternal> registers = new List<IRegisterInternal> {
//numeric ones
new NumberRegister<short>(50, _name: null),
new NumberRegister<ushort>(60, _name : null),
new NumberRegister<int>(70, _name : null),
new NumberRegister<uint>(80, _name : null),
new NumberRegister<float>(90, _name : null),
new NumberRegister<TimeSpan>(100, _name : null),
//boolean
new BoolRegister(IOType.R, 0, 100),
new BoolRegister(IOType.R, 0, 0),
new BoolRegister(IOType.X, 5),
new BoolRegister(IOType.X, 0xA),
new BoolRegister(IOType.X, 0xF, 109),
new BoolRegister(IOType.Y, 0xC, 75),
//string
new BytesRegister(999, 5),
};
List<string> expcectedIdents = new List<string> {
//numeric ones
"DT50",
"DT60",
"DDT70",
"DDT80",
"DDT90",
"DDT100",
//boolean
"R100",
"R0",
"X5",
"XA",
"X109F",
"Y75C",
//string
"DT999"
};
//test mewtocol idents
for (int i = 0; i < registers.Count; i++) {
IRegisterInternal? reg = registers[i];
string expect = expcectedIdents[i];
Assert.Equal(expect, reg.GetMewName());
}
}
[Fact(DisplayName = "Non allowed (Overflow address)")]
public void OverFlowRegisterAddress() {
var ex = Assert.Throws<NotSupportedException>(() => { var ex = Assert.Throws<NotSupportedException>(() => {
new NumberRegister<short>(100000, _name: null); new StructRegister<short>(100000, 2);
}); });
output.WriteLine(ex.Message.ToString()); output.WriteLine(ex.Message.ToString());
}
[Fact(DisplayName = "Non allowed Boolean Address (Overflow address )")]
public void OverFlowBoolRegister() {
var ex1 = Assert.Throws<NotSupportedException>(() => { var ex1 = Assert.Throws<NotSupportedException>(() => {
new BoolRegister(IOType.R, _areaAdress: 512); new BoolRegister(IOType.R, _areaAdress: 512);
@@ -136,27 +45,6 @@ namespace MewtocolTests {
output.WriteLine(ex2.Message.ToString()); output.WriteLine(ex2.Message.ToString());
var ex3 = Assert.Throws<NotSupportedException>(() => {
new BytesRegister(100000, 5);
});
output.WriteLine(ex3.Message.ToString());
}
[Fact(DisplayName = "Non allowed (Wrong data type)")]
public void WrongDataTypeRegister() {
var ex = Assert.Throws<NotSupportedException>(() => {
new NumberRegister<double>(100, _name: null);
});
output.WriteLine(ex.Message.ToString());
} }
} }