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.Text;
|
||||
using MewtocolNet;
|
||||
using MewtocolNet.DocAttributes;
|
||||
|
||||
Thread.CurrentThread.CurrentUICulture = 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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewTerminal", "MewTerminal\MewTerminal.csproj", "{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|x86.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
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
|
||||
SolutionGuid = {4ABB8137-CD8F-4691-9802-9ED371012F47}
|
||||
EndGlobalSection
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace MewtocolNet.ComCassette {
|
||||
|
||||
var interfacesTasks = new List<Task<List<CassetteInformation>>>();
|
||||
|
||||
var usableInterfaces = GetUseableNetInterfaces();
|
||||
var usableInterfaces = Mewtocol.GetUseableNetInterfaces();
|
||||
|
||||
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) {
|
||||
|
||||
var cassettesFound = new List<CassetteInformation>();
|
||||
|
||||
@@ -13,17 +13,13 @@ namespace MewtocolNet {
|
||||
|
||||
internal uint value;
|
||||
|
||||
public uint Value {
|
||||
get => value;
|
||||
set {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
public uint Value => value;
|
||||
|
||||
public DWord(uint bytes) {
|
||||
value = bytes;
|
||||
bitLength = Marshal.SizeOf(value) * 8;
|
||||
}
|
||||
|
||||
public DWord(byte[] bytes) {
|
||||
bytes = bytes.Take(4).ToArray();
|
||||
value = BitConverter.ToUInt32(bytes, 0);
|
||||
@@ -62,16 +58,7 @@ namespace MewtocolNet {
|
||||
|
||||
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) {
|
||||
|
||||
@@ -107,8 +94,6 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
public int GetIntialPlcByteSize() => 4;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,17 +13,13 @@ namespace MewtocolNet {
|
||||
|
||||
internal ushort value;
|
||||
|
||||
public ushort Value {
|
||||
get => value;
|
||||
set {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
public ushort Value => value;
|
||||
|
||||
public Word(ushort bytes) {
|
||||
value = bytes;
|
||||
bitLength = Marshal.SizeOf(value) * 8;
|
||||
}
|
||||
|
||||
public Word(byte[] bytes) {
|
||||
bytes = bytes.Take(2).ToArray();
|
||||
value = BitConverter.ToUInt16(bytes, 0);
|
||||
@@ -53,7 +49,7 @@ namespace MewtocolNet {
|
||||
public static bool operator !=(Word a, Word b) => a.value != b.value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bit value at the given position
|
||||
/// Gets the bit value at the given position
|
||||
/// </summary>
|
||||
public bool this[int bitIndex] {
|
||||
get {
|
||||
@@ -62,16 +58,7 @@ namespace MewtocolNet {
|
||||
|
||||
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) {
|
||||
|
||||
|
||||
@@ -57,11 +57,6 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
int ConnectTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides an anonymous interface for register reading and writing without memory management
|
||||
/// </summary>
|
||||
RBuildAnon Register { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to establish a connection with the device asynchronously
|
||||
/// </summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// The host ip endpoint, leave it null to use an automatic interface
|
||||
/// </summary>
|
||||
IPEndPoint HostEndpoint { get; set; }
|
||||
IPEndPoint HostEndpoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Configures the serial interface
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MewtocolNet.Logging {
|
||||
|
||||
@@ -12,8 +13,58 @@ namespace MewtocolNet.Logging {
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
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>
|
||||
/// Gets invoked whenever a new log message is ready
|
||||
/// </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.RegisterBuilding;
|
||||
using MewtocolNet.RegisterBuilding.BuilderPatterns;
|
||||
using MewtocolNet.SetupClasses;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet {
|
||||
namespace MewtocolNet
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Builder helper for mewtocol interfaces
|
||||
@@ -24,11 +28,11 @@ namespace MewtocolNet {
|
||||
/// <param name="port"></param>
|
||||
/// <param name="station">Plc station number 0xEE for direct communication</param>
|
||||
/// <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();
|
||||
instance.ConfigureConnection(ip, port, station);
|
||||
return new PostInit<IPlcEthernet> {
|
||||
return new PostInitEth<IPlcEthernet> {
|
||||
intf = instance
|
||||
};
|
||||
|
||||
@@ -41,11 +45,11 @@ namespace MewtocolNet {
|
||||
/// <param name="port"></param>
|
||||
/// <param name="station">Plc station number 0xEE for direct communication</param>
|
||||
/// <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();
|
||||
instance.ConfigureConnection(ip, port, station);
|
||||
return new PostInit<IPlcEthernet> {
|
||||
return new PostInitEth<IPlcEthernet> {
|
||||
intf = instance
|
||||
};
|
||||
|
||||
@@ -63,8 +67,6 @@ namespace MewtocolNet {
|
||||
/// <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) {
|
||||
|
||||
TestPortName(portName);
|
||||
|
||||
var instance = new MewtocolInterfaceSerial();
|
||||
instance.ConfigureConnection(portName, (int)baudRate, (int)dataBits, parity, stopBits, station);
|
||||
return new PostInit<IPlcSerial> {
|
||||
@@ -81,8 +83,6 @@ namespace MewtocolNet {
|
||||
/// <returns></returns>
|
||||
public static PostInit<IPlcSerial> SerialAuto(string portName, int station = 0xEE) {
|
||||
|
||||
TestPortName(portName);
|
||||
|
||||
var instance = new MewtocolInterfaceSerial();
|
||||
instance.ConfigureConnection(portName, station);
|
||||
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))
|
||||
throw new NotSupportedException($"The port {portName} is no valid port");
|
||||
var addressInfo = netIf.GetIPProperties().UnicastAddresses
|
||||
.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>
|
||||
public PollLevelConfigurator SetLevel(int level, TimeSpan interval) {
|
||||
|
||||
if (level <= 1)
|
||||
throw new NotSupportedException($"The poll level {level} is not configurable");
|
||||
if (level == PollLevel.Always || level == PollLevel.Never || level == PollLevel.FirstIteration)
|
||||
throw new NotSupportedException("The poll level is reserved for the library");
|
||||
|
||||
if (!levelConfigs.ContainsKey(level)) {
|
||||
levelConfigs.Add(level, new PollLevelConfig {
|
||||
@@ -133,8 +173,8 @@ namespace MewtocolNet {
|
||||
|
||||
public PollLevelConfigurator SetLevel(int level, int skipNth) {
|
||||
|
||||
if (level <= 1)
|
||||
throw new NotSupportedException($"The poll level {level} is not configurable");
|
||||
if (level == PollLevel.Always || level == PollLevel.Never || level == PollLevel.FirstIteration)
|
||||
throw new NotSupportedException("The poll level is reserved for the library");
|
||||
|
||||
if (!levelConfigs.ContainsKey(level)) {
|
||||
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> {
|
||||
|
||||
internal T intf;
|
||||
@@ -269,18 +359,16 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// A builder for attaching register collections
|
||||
/// </summary>
|
||||
public PostInit<T> WithRegisters(Action<RBuildMult> builder) {
|
||||
public PostInit<T> WithRegisters(Action<RBuild> builder) {
|
||||
|
||||
try {
|
||||
|
||||
var plc = (MewtocolInterface)(object)intf;
|
||||
var assembler = new RegisterAssembler(plc);
|
||||
var regBuilder = new RBuildMult(plc);
|
||||
var regBuilder = new RBuild(plc);
|
||||
|
||||
builder.Invoke(regBuilder);
|
||||
|
||||
var registers = assembler.AssembleAll(regBuilder);
|
||||
plc.AddRegisters(registers.ToArray());
|
||||
plc.AddRegisters(regBuilder.assembler.assembled.ToArray());
|
||||
|
||||
return this;
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace MewtocolNet {
|
||||
private protected AsyncQueue queue = new AsyncQueue();
|
||||
private protected Stopwatch speedStopwatchUpstr;
|
||||
private protected Stopwatch speedStopwatchDownstr;
|
||||
private protected Task firstPollTask = new Task(() => { });
|
||||
private protected Task firstPollTask;
|
||||
|
||||
private protected bool wasInitialStatusReceived;
|
||||
private protected MewtocolVersion mewtocolVersion;
|
||||
@@ -144,6 +144,8 @@ namespace MewtocolNet {
|
||||
|
||||
private protected MewtocolInterface() {
|
||||
|
||||
Logger.Start();
|
||||
|
||||
memoryManager = new MemoryAreaManager(this);
|
||||
|
||||
Connected += MewtocolInterface_Connected;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using MewtocolNet.RegisterBuilding.BuilderPatterns;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -38,9 +39,6 @@ namespace MewtocolNet {
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RBuildAnon Register => new RBuildAnon(this);
|
||||
|
||||
#region Register Polling
|
||||
|
||||
/// <summary>
|
||||
@@ -191,7 +189,7 @@ namespace MewtocolNet {
|
||||
if (registerCollections.Count != 0)
|
||||
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) {
|
||||
|
||||
@@ -218,7 +216,7 @@ namespace MewtocolNet {
|
||||
if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel;
|
||||
|
||||
//add builder item
|
||||
var stp1 = regBuild
|
||||
regBuild
|
||||
.AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, byteHint)
|
||||
.AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType)
|
||||
.PollLevel(pollLevel);
|
||||
@@ -242,8 +240,8 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
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/>
|
||||
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;
|
||||
SerialBaudRate = _baudRate;
|
||||
SerialDataBits = _dataBits;
|
||||
@@ -95,6 +98,9 @@ namespace MewtocolNet {
|
||||
|
||||
internal void ConfigureConnectionAuto() {
|
||||
|
||||
if (IsConnected)
|
||||
throw new NotSupportedException("Can't change the connection settings while the PLC is connected");
|
||||
|
||||
autoSerial = true;
|
||||
|
||||
}
|
||||
@@ -106,6 +112,10 @@ namespace MewtocolNet {
|
||||
/// <inheritdoc/>
|
||||
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() {
|
||||
onTryingConfig();
|
||||
}
|
||||
@@ -115,6 +125,8 @@ namespace MewtocolNet {
|
||||
|
||||
try {
|
||||
|
||||
firstPollTask = new Task(() => { });
|
||||
|
||||
Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this);
|
||||
isConnectingStage = true;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MewtocolNet.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
@@ -23,7 +24,7 @@ namespace MewtocolNet {
|
||||
public int Port { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPEndPoint HostEndpoint { get; set; }
|
||||
public IPEndPoint HostEndpoint { get; internal set; }
|
||||
|
||||
internal MewtocolInterfaceTcp() : base() { }
|
||||
|
||||
@@ -32,6 +33,9 @@ namespace MewtocolNet {
|
||||
/// <inheritdoc/>
|
||||
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))
|
||||
throw new NotSupportedException($"The ip: {ip} is no valid ip address");
|
||||
|
||||
@@ -48,6 +52,9 @@ namespace MewtocolNet {
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
Port = port;
|
||||
|
||||
@@ -65,11 +72,22 @@ namespace MewtocolNet {
|
||||
|
||||
try {
|
||||
|
||||
firstPollTask = new Task(() => { });
|
||||
|
||||
Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this);
|
||||
isConnectingStage = true;
|
||||
|
||||
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) {
|
||||
ReceiveBufferSize = RecBufferSize,
|
||||
NoDelay = false,
|
||||
|
||||
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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -12,22 +13,22 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
/// This waits for the memory manager to size all dynamic registers correctly
|
||||
/// </summary>
|
||||
/// <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 regBuilder = new RBuildSingle((MewtocolInterface)plc);
|
||||
// var assembler = new RegisterAssembler((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>
|
||||
/// Adds multiple registers to the plc stack at once <br/>
|
||||
@@ -38,22 +39,22 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
/// use <see cref="AddRegistersAsync"/>
|
||||
/// for this case
|
||||
/// </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 regBuilder = new RBuildMult((MewtocolInterface)plc);
|
||||
// var assembler = new RegisterAssembler((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.RegisterBuilding.BuilderPatterns;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,33 +14,15 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
internal MewtocolInterface onInterface;
|
||||
|
||||
internal List<Register> assembled = new List<Register>();
|
||||
|
||||
internal RegisterAssembler(MewtocolInterface interf) {
|
||||
|
||||
onInterface = interf;
|
||||
|
||||
}
|
||||
|
||||
internal List<Register> AssembleAll(RBuildBase rBuildData, bool flagAutoGenerated = false) {
|
||||
|
||||
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) {
|
||||
internal Register Assemble(StepData data) {
|
||||
|
||||
Register generatedInstance = null;
|
||||
|
||||
@@ -69,7 +52,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
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[] {
|
||||
typeof(uint),
|
||||
typeof(uint),
|
||||
@@ -86,10 +69,10 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
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();
|
||||
|
||||
@@ -101,18 +84,6 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
}
|
||||
}
|
||||
|
||||
if(data.dotnetVarType == typeof(string)) {
|
||||
|
||||
if(data.byteSizeHint == null)
|
||||
throw new NotSupportedException($"Can't create a STRING register without a string size hint");
|
||||
|
||||
if(data.byteSizeHint < 0)
|
||||
throw new NotSupportedException($"Can't create a STRING register with a string size hint < 0");
|
||||
|
||||
numericSize = 4 + data.byteSizeHint.Value;
|
||||
|
||||
}
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
Type paramedClass = typeof(StructRegister<>).MakeGenericType(data.dotnetVarType);
|
||||
@@ -130,6 +101,29 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
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()) {
|
||||
|
||||
//-------------------------------------------
|
||||
@@ -162,6 +156,10 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
generatedInstance.underlyingSystemType = data.dotnetVarType;
|
||||
generatedInstance.pollLevel = data.pollLevel;
|
||||
|
||||
if (data.regCollection != null)
|
||||
generatedInstance.autoGenerated = true;
|
||||
|
||||
assembled.Add(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 {
|
||||
|
||||
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;
|
||||
|
||||
@@ -33,40 +39,6 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
|
||||
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 IMemoryArea underlyingMemory;
|
||||
internal bool autoGenerated;
|
||||
internal bool isAnonymous;
|
||||
|
||||
internal object lastValue = null;
|
||||
internal protected object lastValue = null;
|
||||
internal string name;
|
||||
internal uint memoryAddress;
|
||||
internal int pollLevel = 0;
|
||||
@@ -189,7 +190,7 @@ namespace MewtocolNet.Registers {
|
||||
public override string ToString() {
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(GetMewName());
|
||||
sb.Append(GetRegisterWordRangeString());
|
||||
if (Name != null) sb.Append($" ({Name})");
|
||||
sb.Append($" [{this.GetType().Name}({underlyingSystemType.Name})]");
|
||||
if (ValueObj != null) sb.Append($" Val: {GetValueString()}");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -10,7 +11,10 @@ namespace MewtocolNet.Registers {
|
||||
/// <summary>
|
||||
/// Defines a register containing a string
|
||||
/// </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;
|
||||
|
||||
@@ -21,87 +25,124 @@ namespace MewtocolNet.Registers {
|
||||
/// </summary>
|
||||
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")]
|
||||
public ArrayRegister() =>
|
||||
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;
|
||||
memoryAddress = _address;
|
||||
indices = _indicies;
|
||||
indices = _indices;
|
||||
|
||||
//calc mem length
|
||||
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
|
||||
var byteSize = _reservedByteSize;
|
||||
if (byteSize % 2 != 0) byteSize++;
|
||||
int itemCount = _indices.Aggregate((a, x) => a * x);
|
||||
byteSizePerItem = (int)_reservedByteSize / itemCount;
|
||||
reservedByteSize = _reservedByteSize;
|
||||
|
||||
RegisterType = RegisterType.DT_BYTE_RANGE;
|
||||
addressLength = Math.Max((byteSize / 2), 1);
|
||||
addressLength = Math.Max((_reservedByteSize / 2), 1);
|
||||
|
||||
CheckAddressOverflow(memoryAddress, addressLength);
|
||||
|
||||
underlyingSystemType = typeof(T).MakeArrayType(indices.Length);
|
||||
|
||||
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();
|
||||
var valueIenum = (IEnumerable)ValueObj;
|
||||
async Task IArrayRegister<T>.WriteAsync(T[] data) => await WriteAsync(data);
|
||||
|
||||
foreach (var el in valueIenum) {
|
||||
async Task IArrayRegister2D<T>.WriteAsync(T[,] data) => await WriteAsync(data);
|
||||
|
||||
sb.Append($"{el}, ");
|
||||
|
||||
}
|
||||
|
||||
return ArrayToString((Array)ValueObj);
|
||||
|
||||
}
|
||||
async Task IArrayRegister3D<T>.WriteAsync(T[,,] data) => await WriteAsync(data);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetRegisterString() => "DT";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override uint GetRegisterAddressLen() => AddressLength;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> WriteAsync(T value) {
|
||||
private async Task WriteAsync(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);
|
||||
if (matchingReg != null) matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
|
||||
|
||||
} else {
|
||||
|
||||
underlyingMemory.SetUnderlyingBytes(this, encoded);
|
||||
|
||||
}
|
||||
|
||||
AddSuccessWrite();
|
||||
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/>
|
||||
async Task<T[]> IArrayRegister<T>.ReadAsync() {
|
||||
private async Task<object> ReadAsync() {
|
||||
|
||||
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
|
||||
if (res == null) throw new Exception();
|
||||
@@ -112,15 +153,18 @@ namespace MewtocolNet.Registers {
|
||||
if (matchingReg != null)
|
||||
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
|
||||
|
||||
return (T[])SetValueFromBytes(res);
|
||||
return SetValueFromBytes(res);
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
var parsed = PlcValueParser.ParseArray<T>(this, indices, bytes);
|
||||
var parsed = PlcValueParser.ParseArray(this, bytes);
|
||||
UpdateHoldingValue(parsed);
|
||||
|
||||
return parsed;
|
||||
@@ -130,13 +174,22 @@ namespace MewtocolNet.Registers {
|
||||
/// <inheritdoc/>
|
||||
internal override void UpdateHoldingValue(object val) {
|
||||
|
||||
bool changeTriggerBitArr = val is BitArray bitArr &&
|
||||
lastValue is BitArray bitArr2 &&
|
||||
(bitArr.ToBitString() != bitArr2.ToBitString());
|
||||
if (val == null && lastValue == null) return;
|
||||
|
||||
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 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) {
|
||||
|
||||
@@ -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>
|
||||
public class StructRegister<T> : Register, IRegister<T> where T : struct {
|
||||
|
||||
internal uint byteLength;
|
||||
|
||||
internal uint addressLength;
|
||||
|
||||
/// <summary>
|
||||
@@ -35,26 +33,20 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
memoryAddress = _address;
|
||||
name = _name;
|
||||
Resize(_reservedByteSize);
|
||||
|
||||
addressLength = _reservedByteSize / 2;
|
||||
if (_reservedByteSize % 2 != 0) addressLength++;
|
||||
|
||||
if (_reservedByteSize == 2) RegisterType = RegisterType.DT;
|
||||
if(_reservedByteSize == 4) RegisterType = RegisterType.DDT;
|
||||
if (typeof(T) == typeof(string)) RegisterType = RegisterType.DT_BYTE_RANGE;
|
||||
|
||||
CheckAddressOverflow(memoryAddress, addressLength);
|
||||
|
||||
underlyingSystemType = typeof(T);
|
||||
lastValue = null;
|
||||
|
||||
}
|
||||
|
||||
private void Resize (uint reservedByteSize) {
|
||||
|
||||
addressLength = reservedByteSize / 2;
|
||||
if (reservedByteSize % 2 != 0) addressLength++;
|
||||
byteLength = reservedByteSize;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAsPLC() {
|
||||
|
||||
@@ -92,7 +84,7 @@ namespace MewtocolNet.Registers {
|
||||
public override uint GetRegisterAddressLen() => AddressLength;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> WriteAsync(T value) {
|
||||
public async Task WriteAsync(T value) {
|
||||
|
||||
var encoded = PlcValueParser.Encode(this, (T)value);
|
||||
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
|
||||
@@ -111,27 +103,19 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<T?> ReadAsync() {
|
||||
public async Task<T> ReadAsync() {
|
||||
|
||||
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()
|
||||
.FirstOrDefault(x => x.IsSameAddressAndType(this));
|
||||
|
||||
if (matchingReg != null) {
|
||||
|
||||
//if (matchingReg is StructRegister<string> sreg && this.GetType() == typeof(StructRegister<string>)) {
|
||||
|
||||
// sreg.addressLength = selfSreg.addressLength;
|
||||
|
||||
//}
|
||||
|
||||
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
|
||||
|
||||
}
|
||||
@@ -142,16 +126,6 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
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();
|
||||
|
||||
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 {
|
||||
|
||||
/// <summary>
|
||||
/// An interface for all register types
|
||||
/// An interface for all struct register types
|
||||
/// </summary>
|
||||
public interface IRegister<T> : IRegister where T : struct {
|
||||
|
||||
@@ -18,13 +18,13 @@ namespace MewtocolNet.Registers {
|
||||
/// Reads the register value async from the plc
|
||||
/// </summary>
|
||||
/// <returns>The register value</returns>
|
||||
Task<T?> ReadAsync();
|
||||
Task<T> ReadAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Writes the register content async to the plc
|
||||
/// </summary>
|
||||
/// <returns>True if successfully set</returns>
|
||||
Task<bool> WriteAsync(T data);
|
||||
Task WriteAsync(T data);
|
||||
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
|
||||
namespace MewtocolNet.Registers {
|
||||
|
||||
public interface IArrayRegister<T> : IRegister {
|
||||
public interface IStringRegister : IRegister {
|
||||
|
||||
/// <summary>
|
||||
/// The current value of the register
|
||||
/// </summary>
|
||||
T[] Value { get; }
|
||||
string Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reads the register value async from the plc
|
||||
/// </summary>
|
||||
/// <returns>The register value</returns>
|
||||
Task<T[]> ReadAsync();
|
||||
Task<string> ReadAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Writes the register content async to the plc
|
||||
/// </summary>
|
||||
/// <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 bool skipsAll;
|
||||
|
||||
internal bool skipAllButFirst;
|
||||
|
||||
internal TimeSpan? delay;
|
||||
|
||||
internal int? skipNth;
|
||||
|
||||
@@ -130,48 +130,48 @@ namespace MewtocolNet.TypeConversion {
|
||||
//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)
|
||||
//the remaining bytes are the ascii bytes for the string
|
||||
//new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
|
||||
// HoldingRegisterType = typeof(StructRegister<string>),
|
||||
// PlcVarType = PlcVarType.STRING,
|
||||
// FromRaw = (reg, bytes) => {
|
||||
new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
|
||||
HoldingRegisterType = typeof(StringRegister),
|
||||
PlcVarType = PlcVarType.STRING,
|
||||
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
|
||||
// short actualLen = BitConverter.ToInt16(bytes, 2);
|
||||
//get actual showed size
|
||||
short actualLen = BitConverter.ToInt16(bytes, 2);
|
||||
|
||||
// //skip 4 bytes because they only describe the length
|
||||
// string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray());
|
||||
//skip 4 bytes because they only describe the length
|
||||
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;
|
||||
// if(value.Length % 2 != 0) padLen++;
|
||||
int padLen = value.Length;
|
||||
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 used = (short)value.Length;
|
||||
short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4);
|
||||
short used = (short)value.Length;
|
||||
|
||||
// finalBytes.AddRange(BitConverter.GetBytes(reserved));
|
||||
// finalBytes.AddRange(BitConverter.GetBytes(used));
|
||||
// finalBytes.AddRange(strBytes);
|
||||
finalBytes.AddRange(BitConverter.GetBytes(reserved));
|
||||
finalBytes.AddRange(BitConverter.GetBytes(used));
|
||||
finalBytes.AddRange(strBytes);
|
||||
|
||||
// return finalBytes.ToArray();
|
||||
return finalBytes.ToArray();
|
||||
|
||||
// },
|
||||
//},
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
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 (typeof(T) == typeof(byte[])) return (T)(object)bytes;
|
||||
if (typeof(T) == typeof(byte[])) return bytes;
|
||||
|
||||
IPlcTypeConverter converter;
|
||||
Type underlyingElementType;
|
||||
@@ -47,11 +48,11 @@ namespace MewtocolNet {
|
||||
//special case for enums
|
||||
if (typeof(T).IsEnum) {
|
||||
|
||||
underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType();
|
||||
underlyingElementType = typeof(T).GetEnumUnderlyingType();
|
||||
|
||||
} 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");
|
||||
|
||||
//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;
|
||||
|
||||
if(underlyingElementType == typeof(string)) {
|
||||
throw new NotImplementedException();
|
||||
sizePerItem = register.byteSizePerItem;
|
||||
} else {
|
||||
sizePerItem = underlyingElementType.DetermineTypeByteIntialSize();
|
||||
}
|
||||
|
||||
var iterateItems = indices.Aggregate((a, x) => a * x);
|
||||
var indexer = new int[indices.Length];
|
||||
var iterateItems = register.indices.Aggregate((a, x) => a * x);
|
||||
var indexer = new int[register.indices.Length];
|
||||
for (int i = 0; i < iterateItems; i++) {
|
||||
|
||||
int j = i * sizePerItem;
|
||||
@@ -79,9 +81,9 @@ namespace MewtocolNet {
|
||||
var currentItem = bytes.Skip(j).Take(sizePerItem).ToArray();
|
||||
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;
|
||||
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 (typeof(T) == typeof(byte[])) return (byte[])(object)value;
|
||||
if (value.GetType() == typeof(byte[])) return (byte[])value;
|
||||
|
||||
IPlcTypeConverter converter;
|
||||
Type underlyingElementType;
|
||||
@@ -131,11 +133,11 @@ namespace MewtocolNet {
|
||||
//special case for enums
|
||||
if (typeof(T).IsEnum) {
|
||||
|
||||
underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType();
|
||||
underlyingElementType = typeof(T).GetEnumUnderlyingType();
|
||||
|
||||
} 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");
|
||||
|
||||
int sizePerItem = 0;
|
||||
|
||||
if (underlyingElementType == typeof(string)) {
|
||||
throw new NotImplementedException();
|
||||
sizePerItem = register.byteSizePerItem;
|
||||
} else {
|
||||
sizePerItem = underlyingElementType.DetermineTypeByteIntialSize();
|
||||
}
|
||||
@@ -158,12 +161,19 @@ namespace MewtocolNet {
|
||||
|
||||
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);
|
||||
|
||||
i += sizePerItem;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (encodedData.Length != register.reservedByteSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Input mismatched register target size");
|
||||
|
||||
return encodedData;
|
||||
|
||||
}
|
||||
|
||||
@@ -106,12 +106,24 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
private void TestPollLevelExistence(Register reg) {
|
||||
|
||||
if (!pollLevelConfigs.ContainsKey(1)) {
|
||||
pollLevelConfigs.Add(1, new PollLevelConfig {
|
||||
if (!pollLevelConfigs.ContainsKey(MewtocolNet.PollLevel.Always)) {
|
||||
pollLevelConfigs.Add(MewtocolNet.PollLevel.Always, new PollLevelConfig {
|
||||
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)) {
|
||||
|
||||
pollLevels.Add(new PollLevel(wrAreaSize, dtAreaSize) {
|
||||
@@ -294,10 +306,14 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
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
|
||||
if (pollIteration > 0 && pollLevel.level > 1) {
|
||||
|
||||
var lvlConfig = pollLevelConfigs[pollLevel.level];
|
||||
var skipIterations = lvlConfig.skipNth;
|
||||
var skipDelay = lvlConfig.delay;
|
||||
|
||||
@@ -360,14 +376,32 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
foreach (var pollLevel in pollLevels) {
|
||||
|
||||
sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ====");
|
||||
if (pollLevel.level == MewtocolNet.PollLevel.Always) {
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"\n> ==== Poll lvl ALWAYS ====\n");
|
||||
sb.AppendLine($"> Poll each iteration");
|
||||
|
||||
}else if (pollLevel.level == MewtocolNet.PollLevel.FirstIteration) {
|
||||
|
||||
sb.AppendLine($"\n> ==== Poll lvl FIRST ITERATION ====\n");
|
||||
sb.AppendLine($"> Poll only on the first iteration");
|
||||
|
||||
} else if (pollLevel.level == MewtocolNet.PollLevel.Never) {
|
||||
|
||||
sb.AppendLine($"\n> ==== Poll lvl NEVER ====\n");
|
||||
sb.AppendLine($"> Poll never");
|
||||
|
||||
} else {
|
||||
|
||||
sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ====\n");
|
||||
if (pollLevelConfigs.ContainsKey(pollLevel.level) && pollLevelConfigs[pollLevel.level].delay != null) {
|
||||
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($"> Optimization distance: {maxOptimizationDistance}");
|
||||
sb.AppendLine();
|
||||
|
||||
@@ -14,124 +14,6 @@ namespace MewtocolTests {
|
||||
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 {
|
||||
|
||||
public enum CurrentState : short {
|
||||
public enum CurrentState16 : short {
|
||||
Undefined = 0,
|
||||
State1 = 1,
|
||||
State2 = 2,
|
||||
@@ -51,7 +51,7 @@ namespace MewtocolTests.EncapsulatedTests {
|
||||
public ushort UInt16Type { get; set; }
|
||||
|
||||
[Register("DT50")]
|
||||
public CurrentState Enum16Type { get; set; }
|
||||
public CurrentState16 Enum16Type { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@@ -95,8 +95,6 @@ namespace MewtocolTests.EncapsulatedTests {
|
||||
|
||||
public class TestBitwiseRegisters : RegisterCollection {
|
||||
|
||||
[Register("DT7000")]
|
||||
public BitArray BitArr16 { get; set; }
|
||||
|
||||
//[Register("DT7001")]
|
||||
//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() {
|
||||
|
||||
var testList = new List<string>() {
|
||||
"1112",
|
||||
"1C2C",
|
||||
"FFFF",
|
||||
var testList = new List<byte[]>() {
|
||||
new byte[] {0x11, 0x12},
|
||||
new byte[] {0x1C, 0x2C},
|
||||
new byte[] {0xFF, 0xFF},
|
||||
};
|
||||
|
||||
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) {
|
||||
|
||||
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.RegisterBuilding;
|
||||
using MewtocolNet.RegisterBuilding.BuilderPatterns;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolTests.EncapsulatedTests;
|
||||
using System.Collections;
|
||||
@@ -123,137 +124,9 @@ public class TestRegisterBuilder {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Numeric mewtocol query building")]
|
||||
public void NumericRegisterMewtocolIdentifiers() {
|
||||
|
||||
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() {
|
||||
[Fact(DisplayName = "Non allowed Struct Address (Overflow address)")]
|
||||
public void OverFlowStructRegister() {
|
||||
|
||||
var ex = Assert.Throws<NotSupportedException>(() => {
|
||||
|
||||
new NumberRegister<short>(100000, _name: null);
|
||||
new StructRegister<short>(100000, 2);
|
||||
|
||||
});
|
||||
|
||||
output.WriteLine(ex.Message.ToString());
|
||||
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Non allowed Boolean Address (Overflow address )")]
|
||||
public void OverFlowBoolRegister() {
|
||||
|
||||
var ex1 = Assert.Throws<NotSupportedException>(() => {
|
||||
|
||||
new BoolRegister(IOType.R, _areaAdress: 512);
|
||||
@@ -136,27 +45,6 @@ namespace MewtocolTests {
|
||||
|
||||
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