mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Fix some of the old tests
- add new examples - fix struct builder - complete array interfaces
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
14
Examples.BasicEthernet/Examples.BasicEthernet.csproj
Normal file
14
Examples.BasicEthernet/Examples.BasicEthernet.csproj
Normal 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>
|
||||||
72
Examples.BasicEthernet/Program.cs
Normal file
72
Examples.BasicEthernet/Program.cs
Normal 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}");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
145
Examples.BasicRegisterReadWrite/Program.cs
Normal file
145
Examples.BasicRegisterReadWrite/Program.cs
Normal 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]}");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
14
Examples.Polling/Examples.Polling.csproj
Normal file
14
Examples.Polling/Examples.Polling.csproj
Normal 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>
|
||||||
90
Examples.Polling/Program.cs
Normal file
90
Examples.Polling/Program.cs
Normal 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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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,17 +58,8 @@ 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) {
|
||||||
|
|
||||||
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
|
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
|
||||||
@@ -107,8 +94,6 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetIntialPlcByteSize() => 4;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,17 +58,8 @@ 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) {
|
||||||
|
|
||||||
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
|
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
12
MewtocolNet/Logging/LoggerTargets.cs
Normal file
12
MewtocolNet/Logging/LoggerTargets.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MewtocolNet.Logging {
|
||||||
|
[Flags]
|
||||||
|
public enum LoggerTargets {
|
||||||
|
|
||||||
|
None = 0,
|
||||||
|
Console = 1,
|
||||||
|
Trace = 2,
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
17
MewtocolNet/PollLevel.cs
Normal file
17
MewtocolNet/PollLevel.cs
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
266
MewtocolNet/RegisterBuilding/AddressTools.cs
Normal file
266
MewtocolNet/RegisterBuilding/AddressTools.cs
Normal 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");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
253
MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs
Normal file
253
MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<short[]>(3)</c><br/>
|
|
||||||
/// ARRAY [5..6] OF DWORD = <c>AsTypeArray<DWord[]>(2)</c><br/>
|
|
||||||
/// <br/>
|
|
||||||
/// <b>Multi dimensional arrays:</b><br/>
|
|
||||||
/// ARRAY [0..2, 0..3, 0..4] OF INT = <c>AsTypeArray<short[,,]>(3,4,5)</c><br/>
|
|
||||||
/// ARRAY [5..6, 0..2] OF DWORD = <c>AsTypeArray<DWord[,]>(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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
}
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
@@ -100,18 +83,6 @@ namespace MewtocolNet.RegisterBuilding {
|
|||||||
throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported");
|
throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
234
MewtocolNet/RegisterBuilding/StepBaseTyper.cs
Normal file
234
MewtocolNet/RegisterBuilding/StepBaseTyper.cs
Normal 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<short[]>(3)</c><br/>
|
||||||
|
/// ARRAY [5..6] OF DWORD = <c>AsTypeArray<DWord[]>(2)</c><br/>
|
||||||
|
/// <br/>
|
||||||
|
/// <b>Multi dimensional arrays:</b><br/>
|
||||||
|
/// ARRAY [0..2, 0..3, 0..4] OF INT = <c>AsTypeArray<short[,,]>(3,4,5)</c><br/>
|
||||||
|
/// ARRAY [5..6, 0..2] OF DWORD = <c>AsTypeArray<DWord[,]>(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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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()}");
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|
||||||
//find the underlying memory
|
if(isAnonymous) {
|
||||||
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
|
|
||||||
.FirstOrDefault(x => x.IsSameAddressAndType(this));
|
|
||||||
|
|
||||||
if (matchingReg != null)
|
//find the underlying memory
|
||||||
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
|
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
|
||||||
|
.FirstOrDefault(x => x.IsSameAddressAndType(this));
|
||||||
|
|
||||||
|
if (matchingReg != null) 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 {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
158
MewtocolNet/Registers/Classes/StringRegister.cs
Normal file
158
MewtocolNet/Registers/Classes/StringRegister.cs
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
43
MewtocolNet/Registers/IArrayRegister.cs
Normal file
43
MewtocolNet/Registers/IArrayRegister.cs
Normal 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; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
32
MewtocolNet/Registers/IArrayRegister2D.cs
Normal file
32
MewtocolNet/Registers/IArrayRegister2D.cs
Normal 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; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
32
MewtocolNet/Registers/IArrayRegister3D.cs
Normal file
32
MewtocolNet/Registers/IArrayRegister3D.cs
Normal 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; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
// },
|
},
|
||||||
//},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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($"\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) {
|
||||||
|
sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms");
|
||||||
|
} else if (pollLevelConfigs.ContainsKey(pollLevel.level)) {
|
||||||
|
sb.AppendLine($"> Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations");
|
||||||
|
}
|
||||||
|
|
||||||
sb.AppendLine();
|
|
||||||
if (pollLevelConfigs.ContainsKey(pollLevel.level) && pollLevelConfigs[pollLevel.level].delay != null) {
|
|
||||||
sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms");
|
|
||||||
} else if (pollLevelConfigs.ContainsKey(pollLevel.level)) {
|
|
||||||
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();
|
||||||
|
|||||||
@@ -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");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
89
MewtocolTests/TestPublicBuilderPattern.cs
Normal file
89
MewtocolTests/TestPublicBuilderPattern.cs
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
63
MewtocolTests/TestPublicBuilderPatternArrays.cs
Normal file
63
MewtocolTests/TestPublicBuilderPatternArrays.cs
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user