Added performance improvements for cyclic polling by using single frame building of multiple registers

- cleaned and refactored codebase
This commit is contained in:
Felix Weiß
2023-06-27 20:44:11 +02:00
parent 7be52efb7e
commit a9bd2962b4
34 changed files with 1099 additions and 958 deletions

View File

@@ -7,20 +7,25 @@ using System.Collections;
using MewtocolNet.RegisterBuilding; using MewtocolNet.RegisterBuilding;
using System.Collections.Generic; using System.Collections.Generic;
using MewtocolNet.Registers; using MewtocolNet.Registers;
using System.Diagnostics;
using System.Text;
namespace Examples; namespace Examples;
public class ExampleScenarios { public class ExampleScenarios {
public static bool MewtocolLoggerEnabled = false;
public void SetupLogger () { public void SetupLogger () {
//attaching the logger //attaching the logger
Logger.LogLevel = LogLevel.Verbose; Logger.LogLevel = LogLevel.Info;
Logger.OnNewLogMessage((date, msg) => { Logger.OnNewLogMessage((date, level, msg) => {
if(MewtocolLoggerEnabled)
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); if (level == LogLevel.Error) Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
Console.ResetColor();
}); });
} }
@@ -61,7 +66,7 @@ public class ExampleScenarios {
foreach (var register in interf.Registers) { foreach (var register in interf.Registers) {
Console.WriteLine($"{register.GetCombinedName()} / {register.GetRegisterPLCName()} - Value: {register.GetValueString()}"); Console.WriteLine($"{register.ToString(true)}");
} }
@@ -186,10 +191,8 @@ public class ExampleScenarios {
[Scenario("Read register test")] [Scenario("Read register test")]
public async Task RunReadTest () { public async Task RunReadTest () {
Console.WriteLine("Starting auto enums and bitwise");
//setting up a new PLC interface and register collection //setting up a new PLC interface and register collection
MewtocolInterface interf = new MewtocolInterface("192.168.115.210").WithPoller(); MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
//auto add all built registers to the interface //auto add all built registers to the interface
var builder = RegBuilder.ForInterface(interf); var builder = RegBuilder.ForInterface(interf);
@@ -200,8 +203,9 @@ public class ExampleScenarios {
var shortReg = builder.FromPlcRegName("DT35").AsPlcType(PlcVarType.INT).Build(); var shortReg = builder.FromPlcRegName("DT35").AsPlcType(PlcVarType.INT).Build();
builder.FromPlcRegName("DDT36").AsPlcType(PlcVarType.DINT).Build(); builder.FromPlcRegName("DDT36").AsPlcType(PlcVarType.DINT).Build();
builder.FromPlcRegName("DT200").AsBytes(30).Build();
//builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build(); builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build();
//builder.FromPlcRegName("DT40").AsPlcType(PlcVarType.STRING).Build(); //builder.FromPlcRegName("DT40").AsPlcType(PlcVarType.STRING).Build();
//connect //connect
@@ -214,12 +218,20 @@ public class ExampleScenarios {
await interf.SetRegisterAsync(r0reg, !(bool)r0reg.Value); await interf.SetRegisterAsync(r0reg, !(bool)r0reg.Value);
await interf.SetRegisterAsync(shortReg, (short)new Random().Next(0, 100)); await interf.SetRegisterAsync(shortReg, (short)new Random().Next(0, 100));
var sw = Stopwatch.StartNew();
foreach (var reg in interf.Registers) { foreach (var reg in interf.Registers) {
Console.WriteLine($"Register {reg.GetRegisterPLCName()} val: {reg.Value}"); await reg.ReadAsync();
Console.WriteLine($"Register {reg.ToString()}");
} }
sw.Stop();
Console.WriteLine($"Total read time for registers: {sw.Elapsed.TotalMilliseconds:N0}ms");
Console.WriteLine(); Console.WriteLine();
await Task.Delay(1000); await Task.Delay(1000);
@@ -228,4 +240,69 @@ public class ExampleScenarios {
} }
[Scenario("Test multi frame")]
public async Task MultiFrameTest() {
var preLogLevel = Logger.LogLevel;
Logger.LogLevel = LogLevel.Error;
//setting up a new PLC interface and register collection
MewtocolInterface interf = new MewtocolInterface("192.168.115.210");
//auto add all built registers to the interface
var builder = RegBuilder.ForInterface(interf);
var r0reg = builder.FromPlcRegName("R0").Build();
builder.FromPlcRegName("R1").Build();
builder.FromPlcRegName("R1F").Build();
builder.FromPlcRegName("R60A").Build();
builder.FromPlcRegName("R61A").Build();
builder.FromPlcRegName("R62A").Build();
builder.FromPlcRegName("R63A").Build();
builder.FromPlcRegName("R64A").Build();
builder.FromPlcRegName("R65A").Build();
builder.FromPlcRegName("R66A").Build();
builder.FromPlcRegName("R67A").Build();
builder.FromPlcRegName("R68A").Build();
builder.FromPlcRegName("R69A").Build();
builder.FromPlcRegName("R70A").Build();
builder.FromPlcRegName("R71A").Build();
//connect
await interf.ConnectAsync();
Console.WriteLine("Poller cycle started");
var sw = Stopwatch.StartNew();
await interf.RunPollerCylceManual(false);
sw.Stop();
Console.WriteLine("Poller cycle finished");
Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms");
interf.Disconnect();
await Task.Delay(1000);
await interf.ConnectAsync();
sw = Stopwatch.StartNew();
Console.WriteLine("Poller cycle started");
await interf.RunPollerCylceManual(true);
sw.Stop();
Console.WriteLine("Poller cycle finished");
Console.WriteLine($"Multi frame excec time: {sw.ElapsedMilliseconds:N0}ms");
Logger.LogLevel = preLogLevel;
await Task.Delay(10000);
}
} }

View File

@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using MewtocolNet.Logging;
using System.Text.RegularExpressions;
namespace Examples; namespace Examples;
@@ -55,20 +57,23 @@ class Program {
Console.WriteLine("\nEnter a number to excecute a example"); Console.WriteLine("\nEnter a number to excecute a example");
Console.ResetColor(); Console.ResetColor();
Console.WriteLine("\nOther possible commands: \n\n" + Console.WriteLine("\nOther possible commands: \n");
"'toggle logger' - toggle the built in mewtocol logger on/off\n" + Console.WriteLine($"'logger <level>' - set loglevel to one of: {string.Join(", ", Enum.GetNames(typeof(LogLevel)).ToList())}");
"'exit' - to close this program \n" + Console.WriteLine("'exit' - to close this program");
"'clear' - to clear the output \n"); Console.WriteLine("'clear' - to clear the output");
Console.Write("> "); Console.Write("> ");
var line = Console.ReadLine(); var line = Console.ReadLine();
if (line == "toggle logger") { var loggerMatch = Regex.Match(line, @"logger (?<level>[a-zA-Z]{0,})");
ExampleScenarios.MewtocolLoggerEnabled = !ExampleScenarios.MewtocolLoggerEnabled; if (loggerMatch.Success && Enum.TryParse<LogLevel>(loggerMatch.Groups["level"].Value, out var loglevel)) {
Console.WriteLine(ExampleScenarios.MewtocolLoggerEnabled ? "Logger enabled" : "Logger disabled"); Logger.LogLevel = loglevel;
Console.WriteLine($"Loglevel changed to: {loglevel}");
} else if (line == "exit") { } else if (line == "exit") {

View File

@@ -7,6 +7,8 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MewtocolNet namespace MewtocolNet
@@ -35,6 +37,7 @@ namespace MewtocolNet
internal volatile bool pollerFirstCycle = false; internal volatile bool pollerFirstCycle = false;
internal bool usePoller = false; internal bool usePoller = false;
internal bool pollerUseMultiFrame = false;
#region Register Polling #region Register Polling
@@ -93,66 +96,140 @@ namespace MewtocolNet
pollerFirstCycle = true; pollerFirstCycle = true;
Task.Factory.StartNew(async () => { Task.Run(Poll);
Logger.Log("Poller is attaching", LogLevel.Info, this); }
int iteration = 0; /// <summary>
/// Runs a single poller cycle manually,
/// useful if you want to use a custom update frequency
/// </summary>
/// <returns></returns>
public async Task RunPollerCylceManual (bool useMultiFrame = false) {
pollerTaskStopped = false; if (useMultiFrame) {
pollerTaskRunning = true; await OnMultiFrameCycle();
pollerIsPaused = false; } else {
await OnSingleFrameCycle();
}
while (!pollerTaskStopped) { }
while (pollerTaskRunning) { //polls all registers one by one (slow)
internal async Task Poll () {
if (iteration >= Registers.Count + 1) { Logger.Log("Poller is attaching", LogLevel.Info, this);
iteration = 0;
//invoke cycle polled event
InvokePolledCycleDone();
continue;
}
if (iteration >= Registers.Count) { int iteration = 0;
await GetPLCInfoAsync();
iteration++;
continue;
}
var reg = Registers[iteration]; pollerTaskStopped = false;
pollerTaskRunning = true;
pollerIsPaused = false;
if(reg.IsAllowedRegisterGenericType()) { while (!pollerTaskStopped) {
var lastVal = reg.Value; if (iteration >= Registers.Count + 1) {
iteration = 0;
//invoke cycle polled event
InvokePolledCycleDone();
continue;
}
var rwReg = (IRegisterInternal)reg; if(pollerUseMultiFrame) {
await OnMultiFrameCycle();
} else {
await OnSingleFrameCycle();
}
var readout = await rwReg.ReadAsync(this); pollerFirstCycle = false;
if (lastVal != readout) { iteration++;
rwReg.SetValueFromPLC(readout); pollerIsPaused = !pollerTaskRunning;
InvokeRegisterChanged(reg);
} }
} pollerIsPaused = false;
iteration++; }
pollerFirstCycle = false;
await Task.Delay(pollerDelayMs); private async Task OnSingleFrameCycle () {
foreach (var reg in Registers) {
if (reg.IsAllowedRegisterGenericType()) {
var lastVal = reg.Value;
var rwReg = (IRegisterInternal)reg;
var readout = await rwReg.ReadAsync();
if (lastVal != readout) {
rwReg.SetValueFromPLC(readout);
InvokeRegisterChanged(reg);
} }
pollerIsPaused = !pollerTaskRunning; }
}
await GetPLCInfoAsync();
}
private async Task OnMultiFrameCycle () {
await UpdateRCPRegisters();
await GetPLCInfoAsync();
}
private async Task UpdateRCPRegisters () {
//build booleans
var rcpList = Registers.Where(x => x.GetType() == typeof(BoolRegister))
.Select(x => (BoolRegister)x)
.ToArray();
//one frame can only read 8 registers at a time
int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8);
int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8;
for (int i = 0; i < rcpFrameCount; i++) {
int toReadRegistersCount = 8;
if(i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder;
var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}");
for (int j = 0; j < toReadRegistersCount; j++) {
BoolRegister register = rcpList[i + j];
rcpString.Append(register.BuildMewtocolQuery());
} }
pollerIsPaused = false; string rcpRequest = rcpString.ToString();
var result = await SendCommandAsync(rcpRequest);
var resultBitArray = result.Response.ParseRCMultiBit();
}); for (int k = 0; k < resultBitArray.Length; k++) {
var register = rcpList[i + k];
if((bool)register.Value != resultBitArray[k]) {
register.SetValueFromPLC(resultBitArray[k]);
InvokeRegisterChanged(register);
}
}
}
} }
@@ -166,8 +243,6 @@ namespace MewtocolNet
#region Register Colleciton adding #region Register Colleciton adding
#region Register Collection
/// <summary> /// <summary>
/// Attaches a register collection object to /// Attaches a register collection object to
/// the interface that can be updated automatically. /// the interface that can be updated automatically.
@@ -212,7 +287,7 @@ namespace MewtocolNet
RegisterChanged += (reg) => { RegisterChanged += (reg) => {
//register is used bitwise //register is used bitwise
if (reg.IsUsedBitwise()) { if (reg.GetType() == typeof(BytesRegister)) {
for (int i = 0; i < props.Length; i++) { for (int i = 0; i < props.Length; i++) {
@@ -314,8 +389,6 @@ namespace MewtocolNet
#endregion #endregion
#endregion
#region Register Adding #region Register Adding
internal void AddRegister (RegisterBuildInfo buildInfo) { internal void AddRegister (RegisterBuildInfo buildInfo) {
@@ -323,7 +396,7 @@ namespace MewtocolNet
var builtRegister = buildInfo.Build(); var builtRegister = buildInfo.Build();
//is bitwise and the register list already contains that area register //is bitwise and the register list already contains that area register
if(builtRegister.IsUsedBitwise() && CheckDuplicateRegister(builtRegister, out var existing)) { if(builtRegister.GetType() == typeof(BytesRegister) && CheckDuplicateRegister(builtRegister, out var existing)) {
return; return;
@@ -335,11 +408,12 @@ namespace MewtocolNet
if(CheckDuplicateNameRegister(builtRegister)) if(CheckDuplicateNameRegister(builtRegister))
throw MewtocolException.DupeNameRegister(builtRegister); throw MewtocolException.DupeNameRegister(builtRegister);
builtRegister.attachedInterface = this;
Registers.Add(builtRegister); Registers.Add(builtRegister);
} }
public void AddRegister(IRegister register) { public void AddRegister (BaseRegister register) {
if (CheckDuplicateRegister(register)) if (CheckDuplicateRegister(register))
throw MewtocolException.DupeRegister(register); throw MewtocolException.DupeRegister(register);
@@ -347,29 +421,30 @@ namespace MewtocolNet
if (CheckDuplicateNameRegister(register)) if (CheckDuplicateNameRegister(register))
throw MewtocolException.DupeNameRegister(register); throw MewtocolException.DupeNameRegister(register);
register.attachedInterface = this;
Registers.Add(register); Registers.Add(register);
} }
private bool CheckDuplicateRegister (IRegister instance, out IRegister foundDupe) { private bool CheckDuplicateRegister (IRegisterInternal instance, out IRegisterInternal foundDupe) {
foundDupe = Registers.FirstOrDefault(x => x.CompareIsDuplicate(instance)); foundDupe = RegistersInternal.FirstOrDefault(x => x.CompareIsDuplicate(instance));
return Registers.Contains(instance) || foundDupe != null; return RegistersInternal.Contains(instance) || foundDupe != null;
} }
private bool CheckDuplicateRegister(IRegister instance) { private bool CheckDuplicateRegister(IRegisterInternal instance) {
var foundDupe = Registers.FirstOrDefault(x => x.CompareIsDuplicate(instance)); var foundDupe = RegistersInternal.FirstOrDefault(x => x.CompareIsDuplicate(instance));
return Registers.Contains(instance) || foundDupe != null; return RegistersInternal.Contains(instance) || foundDupe != null;
} }
private bool CheckDuplicateNameRegister(IRegister instance) { private bool CheckDuplicateNameRegister(IRegisterInternal instance) {
return Registers.Any(x => x.CompareIsNameDuplicate(instance)); return RegistersInternal.Any(x => x.CompareIsNameDuplicate(instance));
} }
@@ -387,25 +462,6 @@ namespace MewtocolNet
} }
/// <summary>
/// Gets a register that was added by its name
/// </summary>
/// <typeparam name="T">The type of register</typeparam>
/// <returns>A casted register or the <code>default</code> value</returns>
public T GetRegister<T>(string name) where T : IRegister {
try {
var reg = Registers.FirstOrDefault(x => x.Name == name);
return (T)reg;
} catch (InvalidCastException) {
return default(T);
}
}
#endregion #endregion
#region Register Reading #region Register Reading
@@ -413,9 +469,9 @@ namespace MewtocolNet
/// <summary> /// <summary>
/// Gets a list of all added registers /// Gets a list of all added registers
/// </summary> /// </summary>
public List<IRegister> GetAllRegisters() { public IEnumerable<IRegister> GetAllRegisters() {
return Registers; return Registers.Cast<IRegister>();
} }

View File

@@ -17,15 +17,15 @@ namespace MewtocolNet.Exceptions {
System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { } System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
public static MewtocolException DupeRegister (IRegister register) { internal static MewtocolException DupeRegister (IRegisterInternal register) {
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetRegisterPLCName()}"); return new MewtocolException($"The mewtocol interface already contains this register: {register.GetRegisterPLCName()}");
} }
public static MewtocolException DupeNameRegister (IRegister register) { internal static MewtocolException DupeNameRegister (IRegisterInternal register) {
return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.Name}"); return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetRegisterPLCName()}");
} }

View File

@@ -23,6 +23,11 @@ namespace MewtocolNet {
/// </summary> /// </summary>
string Name { get; } string Name { get; }
/// <summary>
/// Gets the register address name as in the plc
/// </summary>
string PLCAddressName { get; }
/// <summary> /// <summary>
/// The current value of the register /// The current value of the register
/// </summary> /// </summary>
@@ -33,73 +38,6 @@ namespace MewtocolNet {
/// </summary> /// </summary>
int MemoryAddress { get; } int MemoryAddress { get; }
/// <summary>
/// Gets the special address of the register or -1 if it has none
/// </summary>
/// <returns></returns>
byte? GetSpecialAddress();
/// <summary>
/// Indicates if the register is processed bitwise
/// </summary>
/// <returns>True if bitwise</returns>
bool IsUsedBitwise();
/// <summary>
/// Generates a string describing the starting memory address of the register
/// </summary>
string GetStartingMemoryArea();
/// <summary>
/// Gets the current value formatted as a readable string
/// </summary>
string GetValueString();
/// <summary>
/// Builds the identifier for the mewtocol query string
/// </summary>
/// <returns></returns>
string BuildMewtocolQuery();
/// <summary>
/// Builds a register string that prepends the memory address fe. DDT or DT, X, Y etc
/// </summary>
string GetRegisterString();
/// <summary>
/// Builds a combined name for the attached property to uniquely identify the property register binding
/// </summary>
/// <returns></returns>
string GetCombinedName();
/// <summary>
/// Gets the name of the class that contains the attached property
/// </summary>
/// <returns></returns>
string GetContainerName();
/// <summary>
/// Builds a register name after the PLC convention <br/>
/// Example <code>DDT100, XA, X6, Y1, DT3300</code>
/// </summary>
string GetRegisterPLCName();
/// <summary>
/// Clears the current value of the register and resets it to default
/// </summary>
void ClearValue();
/// <summary>
/// Triggers a notifychanged update event
/// </summary>
void TriggerNotifyChange();
/// <summary>
/// Gets the type of the class collection its attached property is in or null
/// </summary>
/// <returns>The class name or null if manually added</returns>
Type GetCollectionType();
/// <summary> /// <summary>
/// Builds a readable string with all important register informations /// Builds a readable string with all important register informations
/// </summary> /// </summary>
@@ -110,6 +48,18 @@ namespace MewtocolNet {
/// </summary> /// </summary>
string ToString(bool detailed); string ToString(bool detailed);
/// <summary>
/// Sets the register value in the plc async
/// </summary>
/// <returns>True if successful</returns>
Task<bool> SetValueAsync();
/// <summary>
/// Gets the register value from the plc async
/// </summary>
/// <returns>The value or null if failed</returns>
Task<object> GetValueAsync();
} }
} }

View File

@@ -5,13 +5,60 @@ using System.Threading.Tasks;
namespace MewtocolNet { namespace MewtocolNet {
internal interface IRegisterInternal { internal interface IRegisterInternal {
event Action<object> ValueChanged;
//props
MewtocolInterface AttachedInterface { get; }
RegisterType RegisterType { get; }
string Name { get; }
object Value { get; }
int MemoryAddress { get; }
// setters
void WithCollectionType(Type colType); void WithCollectionType(Type colType);
void SetValueFromPLC(object value); void SetValueFromPLC(object value);
Task<object> ReadAsync(MewtocolInterface interf); void ClearValue();
Task<bool> WriteAsync(MewtocolInterface interf, object data); // Accessors
Type GetCollectionType();
string GetRegisterString();
string GetCombinedName();
string GetContainerName();
string GetRegisterPLCName();
byte? GetSpecialAddress();
string GetStartingMemoryArea();
string GetValueString();
string BuildMewtocolQuery();
//others
void TriggerNotifyChange();
Task<object> ReadAsync();
Task<bool> WriteAsync(object data);
string ToString();
string ToString(bool detailed);
} }

View File

@@ -12,15 +12,15 @@ namespace MewtocolNet.Logging {
/// </summary> /// </summary>
public static LogLevel LogLevel { get; set; } public static LogLevel LogLevel { get; set; }
internal static Action<DateTime, string> LogInvoked; internal static Action<DateTime, LogLevel, string> LogInvoked;
/// <summary> /// <summary>
/// Gets invoked whenever a new log message is ready /// Gets invoked whenever a new log message is ready
/// </summary> /// </summary>
public static void OnNewLogMessage(Action<DateTime, string> onMsg) { public static void OnNewLogMessage(Action<DateTime, LogLevel, string> onMsg) {
LogInvoked += (t, m) => { LogInvoked += (t, l, m) => {
onMsg(t, m); onMsg(t, l, m);
}; };
} }
@@ -29,9 +29,9 @@ namespace MewtocolNet.Logging {
if ((int)loglevel <= (int)LogLevel) { if ((int)loglevel <= (int)LogLevel) {
if (sender == null) { if (sender == null) {
LogInvoked?.Invoke(DateTime.Now, message); LogInvoked?.Invoke(DateTime.Now, loglevel, message);
} else { } else {
LogInvoked?.Invoke(DateTime.Now, $"[{sender.GetConnectionPortInfo()}] {message}"); LogInvoked?.Invoke(DateTime.Now, loglevel, $"[{sender.GetConnectionPortInfo()}] {message}");
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -26,7 +27,7 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// Converts a string (after converting to upper case) to ascii bytes /// Converts a string (after converting to upper case) to ascii bytes
/// </summary> /// </summary>
internal static byte[] ToHexASCIIBytes(this string _str) { internal static byte[] BytesFromHexASCIIString(this string _str) {
ASCIIEncoding ascii = new ASCIIEncoding(); ASCIIEncoding ascii = new ASCIIEncoding();
byte[] bytes = ascii.GetBytes(_str.ToUpper()); byte[] bytes = ascii.GetBytes(_str.ToUpper());
@@ -73,31 +74,59 @@ namespace MewtocolNet {
} }
internal static string ParseDTString(this string _onString) { internal static BitArray ParseRCMultiBit(this string _onString) {
var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString); var res = new Regex(@"\%([0-9]{2})\$RC(?<bits>(?:0|1){0,8})(..)").Match(_onString);
if (res.Success) { if (res.Success) {
string val = res.Groups[2].Value;
return val.GetStringFromAsciiHex()?.Trim(); string val = res.Groups["bits"].Value;
return new BitArray(val.Select(c => c == '1').ToArray());
} }
return null; return null;
} }
internal static string ReverseByteOrder(this string _onString) { /// <summary>
/// Parses a return string from the PLC as a raw byte array <br/>
/// Example:
/// <code>
/// ↓Start ↓end
/// %01$RD0100020010\r
/// </code>
/// This will return the byte array:
/// <code>
/// [0x01, 0x00, 0x02, 0x00]
/// </code>
/// </summary>
/// <param name="_onString"></param>
/// <returns>A <see cref="T:byte[]"/> or null of failed</returns>
internal static byte[] ParseDTRawStringAsBytes (this string _onString) {
if (_onString == null) return null; var res = new Regex(@"\%([0-9]{2})\$RD(?<data>.*)(?<csum>..)..").Match(_onString);
if (res.Success) {
//split into 2 chars string val = res.Groups["data"].Value;
var stringBytes = _onString.SplitInParts(2).ToList(); var parts = val.SplitInParts(2).ToArray();
var bytes = new byte[parts.Length];
stringBytes.Reverse(); for (int i = 0; i < bytes.Length; i++) {
return string.Join("", stringBytes); bytes[i] = byte.Parse(parts[i], NumberStyles.HexNumber);
}
return bytes;
}
return null;
} }
internal static IEnumerable<String> SplitInParts(this string s, int partLength) { /// <summary>
/// Splits a string in even parts
/// </summary>
internal static IEnumerable<string> SplitInParts(this string s, int partLength) {
if (s == null) if (s == null)
throw new ArgumentNullException(nameof(s)); throw new ArgumentNullException(nameof(s));
@@ -109,63 +138,7 @@ namespace MewtocolNet {
} }
internal static string BuildDTString (this byte[] inBytes, short reservedSize) { internal static byte[] HexStringToByteArray (this string hex) {
StringBuilder sb = new StringBuilder();
//clamp string lenght
if (inBytes.Length > reservedSize) {
inBytes = inBytes.Take(reservedSize).ToArray();
}
//actual string content
var hexstring = inBytes.ToHexString();
var sizeBytes = BitConverter.GetBytes((short)(hexstring.Length / 2)).ToHexString();
if (hexstring.Length >= 2) {
var remainderBytes = (hexstring.Length / 2) % 2;
if (remainderBytes != 0) {
hexstring += "20";
}
}
var reservedSizeBytes = BitConverter.GetBytes(reservedSize).ToHexString();
//reserved string count bytes
sb.Append(reservedSizeBytes);
//string count actual bytes
sb.Append(sizeBytes);
sb.Append(hexstring);
return sb.ToString();
}
internal static string GetStringFromAsciiHex(this string input) {
if (input.Length % 2 != 0)
return null;
byte[] bytes = new byte[input.Length / 2];
for (int i = 0; i < input.Length; i += 2) {
String hex = input.Substring(i, 2);
bytes[i / 2] = Convert.ToByte(hex, 16);
}
return Encoding.ASCII.GetString(bytes);
}
internal static string GetAsciiHexFromString(this string input) {
var bytes = new ASCIIEncoding().GetBytes(input);
return bytes.ToHexString();
}
internal static byte[] HexStringToByteArray(this string hex) {
if (hex == null) if (hex == null)
return null; return null;
return Enumerable.Range(0, hex.Length) return Enumerable.Range(0, hex.Length)
@@ -174,13 +147,39 @@ namespace MewtocolNet {
.ToArray(); .ToArray();
} }
internal static string ToHexString(this byte[] arr) { /// <summary>
/// Converts a byte array to a hexadecimal string
/// </summary>
/// <param name="seperator">Seperator between the hex numbers</param>
/// <param name="arr">The byte array</param>
internal static string ToHexString (this byte[] arr, string seperator = "") {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.Length; i++) { for (int i = 0; i < arr.Length; i++) {
byte b = arr[i]; byte b = arr[i];
sb.Append(b.ToString("X2")); sb.Append(b.ToString("X2"));
if(i < arr.Length - 1) sb.Append(seperator);
} }
return sb.ToString();
}
internal static string AsPLC (this TimeSpan timespan) {
StringBuilder sb = new StringBuilder("T#");
int millis = timespan.Milliseconds;
int seconds = timespan.Seconds;
int minutes = timespan.Minutes;
int hours = timespan.Hours;
if (hours > 0) sb.Append($"{hours}h");
if (minutes > 0) sb.Append($"{minutes}m");
if (seconds > 0) sb.Append($"{seconds}s");
if (millis > 0) sb.Append($"{millis}ms");
return sb.ToString(); return sb.ToString();
} }
@@ -207,39 +206,6 @@ namespace MewtocolNet {
} }
internal static bool IsDoubleNumericRegisterType(this Type type) {
//Type[] singles = new Type[] {
// typeof(short),
// typeof(ushort),
//};
Type[] doubles = new Type[] {
typeof(int),
typeof(uint),
typeof(float),
typeof(TimeSpan),
};
return doubles.Contains(type);
}
internal static bool IsNumericSupportedType(this Type type) {
Type[] supported = new Type[] {
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(float),
typeof(TimeSpan),
};
return supported.Contains(type);
}
/// <summary> /// <summary>
/// Checks if the register type is boolean /// Checks if the register type is boolean
/// </summary> /// </summary>
@@ -267,7 +233,7 @@ namespace MewtocolNet {
} }
internal static bool CompareIsDuplicate (this IRegister reg1, IRegister compare) { internal static bool CompareIsDuplicate (this IRegisterInternal reg1, IRegisterInternal compare) {
bool valCompare = reg1.RegisterType == compare.RegisterType && bool valCompare = reg1.RegisterType == compare.RegisterType &&
reg1.MemoryAddress == compare.MemoryAddress && reg1.MemoryAddress == compare.MemoryAddress &&
@@ -277,7 +243,7 @@ namespace MewtocolNet {
} }
internal static bool CompareIsNameDuplicate(this IRegister reg1, IRegister compare) { internal static bool CompareIsNameDuplicate(this IRegisterInternal reg1, IRegisterInternal compare) {
return ( reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name; return ( reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name;

View File

@@ -1,3 +1,4 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Logging; using MewtocolNet.Logging;
using MewtocolNet.Queue; using MewtocolNet.Queue;
using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterAttributes;
@@ -113,7 +114,9 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// The registered data registers of the PLC /// The registered data registers of the PLC
/// </summary> /// </summary>
public List<IRegister> Registers { get; set; } = new List<IRegister>(); public List<BaseRegister> Registers { get; private set; } = new List<BaseRegister>();
internal IEnumerable<IRegisterInternal> RegistersInternal => Registers.Cast<IRegisterInternal>();
private string ip; private string ip;
private int port; private int port;
@@ -208,11 +211,13 @@ namespace MewtocolNet {
RegisterChanged += (o) => { RegisterChanged += (o) => {
string address = $"{o.GetRegisterString()}{o.GetStartingMemoryArea()}".PadRight(5, (char)32); var asInternal = (IRegisterInternal)o;
string address = $"{asInternal.GetRegisterString()}{asInternal.GetStartingMemoryArea()}".PadRight(5, (char)32);
Logger.Log($"{address} " + Logger.Log($"{address} " +
$"{(o.Name != null ? $"({o.Name}) " : "")}" + $"{(o.Name != null ? $"({o.Name}) " : "")}" +
$"changed to \"{o.GetValueString()}\"", LogLevel.Change, this); $"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this);
}; };
} }
@@ -305,9 +310,10 @@ namespace MewtocolNet {
/// Attaches a poller to the interface that continously /// Attaches a poller to the interface that continously
/// polls the registered data registers and writes the values to them /// polls the registered data registers and writes the values to them
/// </summary> /// </summary>
public MewtocolInterface WithPoller() { public MewtocolInterface WithPoller(bool useMultiFrame = false) {
usePoller = true; usePoller = true;
pollerUseMultiFrame = useMultiFrame;
return this; return this;
} }
@@ -397,7 +403,7 @@ namespace MewtocolNet {
for (int i = 0; i < Registers.Count; i++) { for (int i = 0; i < Registers.Count; i++) {
var reg = Registers[i]; var reg = (IRegisterInternal)Registers[i];
reg.ClearValue(); reg.ClearValue();
} }
@@ -423,22 +429,38 @@ namespace MewtocolNet {
queuedMessages++; queuedMessages++;
var response = await queue.Enqueue(() => SendSingleBlock(_msg)); TCPMessageResult tcpResult = TCPMessageResult.Waiting;
string response = "";
if (queuedMessages > 0) int lineFeedFails = 0;
queuedMessages--;
//recursively try to get a response on failed line feeds
while (tcpResult == TCPMessageResult.Waiting || tcpResult == TCPMessageResult.FailedLineFeed) {
if (lineFeedFails >= 5)
throw new MewtocolException($"The message ${_msg} had {lineFeedFails} linefeed fails");
var tempResponse = await queue.Enqueue(() => SendSingleBlock(_msg));
tcpResult = tempResponse.Item1;
response = tempResponse.Item2;
if(tcpResult == TCPMessageResult.FailedLineFeed) {
lineFeedFails++;
Logger.Log($"Linefeed fail, retrying...", LogLevel.Error);
}
if (queuedMessages > 0)
queuedMessages--;
if (tcpResult == TCPMessageResult.FailedWithException)
throw new MewtocolException("The connection to the device was terminated");
if (response == null) {
return new CommandResult {
Success = false,
Error = "0000",
ErrorDescription = "null result"
};
} }
//error catching //error catching
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase); Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
Match m = errorcheck.Match(response.ToString()); Match m = errorcheck.Match(response);
if (m.Success) { if (m.Success) {
string eCode = m.Groups[1].Value; string eCode = m.Groups[1].Value;
string eDes = CodeDescriptions.Error[Convert.ToInt32(eCode)]; string eDes = CodeDescriptions.Error[Convert.ToInt32(eCode)];
@@ -456,7 +478,7 @@ namespace MewtocolNet {
return new CommandResult { return new CommandResult {
Success = true, Success = true,
Error = "0000", Error = "0000",
Response = response.ToString() Response = response,
}; };
} catch { } catch {
@@ -469,16 +491,16 @@ namespace MewtocolNet {
} }
private async Task<string> SendSingleBlock(string _blockString) { private async Task<(TCPMessageResult, string)> SendSingleBlock(string _blockString) {
if (client == null || !client.Connected) { if (client == null || !client.Connected) {
await ConnectTCP(); await ConnectTCP();
} }
if (client == null || !client.Connected) if (client == null || !client.Connected)
return null; return (TCPMessageResult.NotConnected, null);
var message = _blockString.ToHexASCIIBytes(); var message = _blockString.BytesFromHexASCIIString();
//time measuring //time measuring
if (speedStopwatchUpstr == null) { if (speedStopwatchUpstr == null) {
@@ -542,13 +564,16 @@ namespace MewtocolNet {
} catch (IOException) { } catch (IOException) {
OnMajorSocketExceptionWhileConnected(); OnMajorSocketExceptionWhileConnected();
return null; return (TCPMessageResult.FailedWithException, null);
} catch (SocketException) { } catch (SocketException) {
OnMajorSocketExceptionWhileConnected(); OnMajorSocketExceptionWhileConnected();
return null; return (TCPMessageResult.FailedWithException, null);
} }
if (!string.IsNullOrEmpty(response.ToString())) {
string resString = response.ToString();
if (!string.IsNullOrEmpty(resString) && resString != "\r" ) {
Logger.Log($"<-- IN MSG: {response}", LogLevel.Critical, this); Logger.Log($"<-- IN MSG: {response}", LogLevel.Critical, this);
@@ -559,10 +584,12 @@ namespace MewtocolNet {
if (perSecUpstream <= 10000) if (perSecUpstream <= 10000)
BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero); BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
return response.ToString(); return (TCPMessageResult.Success, resString);
} else { } else {
return null;
return (TCPMessageResult.FailedLineFeed, null);
} }
} }

View File

@@ -159,10 +159,12 @@ namespace MewtocolNet {
#region Raw register reading / writing #region Raw register reading / writing
internal async Task<byte[]> ReadRawRegisterAsync (IRegister _toRead) { internal async Task<byte[]> ReadRawRegisterAsync (IRegisterInternal _toRead) {
var toreadType = _toRead.GetType();
//returns a byte array 1 long and with the byte beeing 0 or 1 //returns a byte array 1 long and with the byte beeing 0 or 1
if (_toRead.GetType() == typeof(BoolRegister)) { if (toreadType == typeof(BoolRegister)) {
string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}"; string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring); var result = await SendCommandAsync(requeststring);
@@ -177,7 +179,7 @@ namespace MewtocolNet {
} }
//returns a byte array 2 bytes or 4 bytes long depending on the data size //returns a byte array 2 bytes or 4 bytes long depending on the data size
if (_toRead.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) { if (toreadType.IsGenericType && _toRead.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring); var result = await SendCommandAsync(requeststring);
@@ -198,7 +200,7 @@ namespace MewtocolNet {
} }
//returns a byte array with variable size //returns a byte array with variable size
if (_toRead.GetType() == typeof(BytesRegister<>)) { if (toreadType == typeof(BytesRegister)) {
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring); var result = await SendCommandAsync(requeststring);
@@ -206,7 +208,9 @@ namespace MewtocolNet {
if (!result.Success) if (!result.Success)
throw new Exception($"Failed to load the byte data for: {_toRead}"); throw new Exception($"Failed to load the byte data for: {_toRead}");
return result.Response.ParseDTString().ReverseByteOrder().HexStringToByteArray(); var resBytes = result.Response.ParseDTRawStringAsBytes();
return resBytes;
} }
@@ -214,7 +218,7 @@ namespace MewtocolNet {
} }
internal async Task<bool> WriteRawRegisterAsync (IRegister _toWrite, byte[] data) { internal async Task<bool> WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) {
//returns a byte array 1 long and with the byte beeing 0 or 1 //returns a byte array 1 long and with the byte beeing 0 or 1
if (_toWrite.GetType() == typeof(BoolRegister)) { if (_toWrite.GetType() == typeof(BoolRegister)) {
@@ -235,7 +239,7 @@ namespace MewtocolNet {
} }
//returns a byte array with variable size //returns a byte array with variable size
if (_toWrite.GetType() == typeof(BytesRegister<>)) { if (_toWrite.GetType() == typeof(BytesRegister)) {
//string stationNum = GetStationNumber(); //string stationNum = GetStationNumber();
//string dataString = gotBytes.BuildDTString(_toWrite.ReservedSize); //string dataString = gotBytes.BuildDTString(_toWrite.ReservedSize);
@@ -259,7 +263,7 @@ namespace MewtocolNet {
var internalReg = (IRegisterInternal)register; var internalReg = (IRegisterInternal)register;
return await internalReg.WriteAsync(this, value); return await internalReg.WriteAsync(value);
} }

View File

@@ -17,52 +17,64 @@ namespace MewtocolNet
internal Type dotnetCastType; internal Type dotnetCastType;
internal Type collectionType; internal Type collectionType;
internal IRegister Build () { internal BaseRegister Build () {
RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault(); RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault();
PlcVarType plcType = dotnetCastType.ToPlcVarType(); Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType();
Type registerClassType = plcType.GetDefaultPlcVarType();
if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool) || dotnetCastType == typeof(BitArray))) { bool isBytesRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister);
if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool))) {
//------------------------------------------- //-------------------------------------------
//as numeric register with boolean bit target //as numeric register with boolean bit target
var type = typeof(NumberRegister<BitArray>);
var areaAddr = memoryAddress;
//create a new bregister instance //create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.Instance; var flags = BindingFlags.Public | BindingFlags.Instance;
//int _adress, string _name = null, bool isBitwise = false, Type _enumType = null //int _adress, int _reservedByteSize, string _name = null
var parameters = new object[] { areaAddr, name, true, null }; var parameters = new object[] { memoryAddress, memorySizeBytes, name };
var instance = (IRegister)Activator.CreateInstance(type, flags, null, parameters, null); var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null);
if (collectionType != null) if (collectionType != null)
((IRegisterInternal)instance).WithCollectionType(collectionType); instance.WithCollectionType(collectionType);
return instance; return instance;
} else if (regType.IsNumericDTDDT()) { } else if (regType.IsNumericDTDDT() && !isBytesRegister) {
//------------------------------------------- //-------------------------------------------
//as numeric register //as numeric register
var type = plcType.GetDefaultPlcVarType();
var areaAddr = memoryAddress; var areaAddr = memoryAddress;
//create a new bregister instance //create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.Instance; var flags = BindingFlags.Public | BindingFlags.Instance;
//int _adress, string _name = null, bool isBitwise = false, Type _enumType = null //int _adress, Type _enumType = null, string _name = null
var parameters = new object[] { areaAddr, name, false, null }; var parameters = new object[] { areaAddr, null, name };
var instance = (IRegister)Activator.CreateInstance(type, flags, null, parameters, null); var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null);
if(collectionType != null) if(collectionType != null)
((IRegisterInternal)instance).WithCollectionType(collectionType); instance.WithCollectionType(collectionType);
return instance;
}
if(isBytesRegister) {
//-------------------------------------------
//as byte range register
//create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.Instance;
//int _adress, int _reservedSize, string _name = null
var parameters = new object[] { memoryAddress, memorySizeBytes, name };
var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null);
if (collectionType != null)
instance.WithCollectionType(collectionType);
return instance; return instance;
@@ -89,26 +101,6 @@ namespace MewtocolNet
} }
if(regType == RegisterType.DT_RANGE) {
//-------------------------------------------
//as byte range register
var type = plcType.GetDefaultPlcVarType();
//create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.Instance;
//int _adress, int _reservedSize, string _name = null
var parameters = new object[] { memoryAddress, memorySizeBytes, name };
var instance = (IRegister)Activator.CreateInstance(type, flags, null, parameters, null);
if (collectionType != null)
((IRegisterInternal)instance).WithCollectionType(collectionType);
return instance;
}
throw new Exception("Failed to build register"); throw new Exception("Failed to build register");
} }

View File

@@ -14,10 +14,31 @@ namespace MewtocolNet.RegisterBuilding
if (!step.wasCasted) if (!step.wasCasted)
step.AutoType(); step.AutoType();
//fallbacks if no casting builder was given
step.GetFallbackDotnetType();
var builtReg = new RegisterBuildInfo {
name = step.Name,
specialAddress = step.SpecialAddress,
memoryAddress = step.MemAddress,
memorySizeBytes = step.MemByteSize,
registerType = step.RegType,
dotnetCastType = step.dotnetVarType,
}.Build();
step.AddToRegisterList(builtReg);
return builtReg;
}
private static void GetFallbackDotnetType (this RegisterBuilderStep step) {
bool isBoolean = step.RegType.IsBoolean(); bool isBoolean = step.RegType.IsBoolean();
bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null; bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null;
//fallbacks if no casting builder was given
if (isTypeNotDefined && step.RegType == RegisterType.DT) { if (isTypeNotDefined && step.RegType == RegisterType.DT) {
step.dotnetVarType = typeof(short); step.dotnetVarType = typeof(short);
@@ -31,35 +52,21 @@ namespace MewtocolNet.RegisterBuilding
step.dotnetVarType = typeof(bool); step.dotnetVarType = typeof(bool);
} else if (isTypeNotDefined && step.RegType == RegisterType.DT_RANGE) { } else if (isTypeNotDefined && step.RegType == RegisterType.DT_BYTE_RANGE) {
step.dotnetVarType = typeof(string); step.dotnetVarType = typeof(string);
} }
if(step.plcVarType != null) { if (step.plcVarType != null) {
step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType(); step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType();
} }
var builtReg = new RegisterBuildInfo {
name = step.Name,
specialAddress = step.SpecialAddress,
memoryAddress = step.MemAddress,
registerType = step.RegType,
dotnetCastType = step.dotnetVarType,
}.Build();
step.AddToRegisterList(builtReg);
return builtReg;
} }
private static void AddToRegisterList (this RegisterBuilderStep step, IRegister instance) { private static void AddToRegisterList (this RegisterBuilderStep step, BaseRegister instance) {
if (step.forInterface == null) return; if (step.forInterface == null) return;

View File

@@ -13,6 +13,7 @@ namespace MewtocolNet.RegisterBuilding {
internal string Name; internal string Name;
internal RegisterType RegType; internal RegisterType RegType;
internal int MemAddress; internal int MemAddress;
internal int MemByteSize;
internal byte? SpecialAddress; internal byte? SpecialAddress;
internal PlcVarType? plcVarType; internal PlcVarType? plcVarType;
@@ -63,6 +64,24 @@ namespace MewtocolNet.RegisterBuilding {
} }
public RegisterBuilderStep AsBytes (int byteLength) {
if (RegType != RegisterType.DT) {
throw new NotSupportedException($"Cant use the AsByte converter on a non DT register");
}
MemByteSize = byteLength;
dotnetVarType = typeof(byte[]);
plcVarType = null;
wasCasted = true;
return this;
}
internal RegisterBuilderStep AutoType() { internal RegisterBuilderStep AutoType() {
switch (RegType) { switch (RegType) {
@@ -77,7 +96,7 @@ namespace MewtocolNet.RegisterBuilding {
case RegisterType.DDT: case RegisterType.DDT:
dotnetVarType = typeof(int); dotnetVarType = typeof(int);
break; break;
case RegisterType.DT_RANGE: case RegisterType.DT_BYTE_RANGE:
dotnetVarType = typeof(string); dotnetVarType = typeof(string);
break; break;
} }

View File

@@ -30,7 +30,7 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// Area of a byte sequence longer than 2 words /// Area of a byte sequence longer than 2 words
/// </summary> /// </summary>
DT_RANGE = 5, DT_BYTE_RANGE = 5,
} }

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
public abstract class BaseRegister : IRegister, IRegisterInternal, INotifyPropertyChanged {
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
public event Action<object> ValueChanged;
internal MewtocolInterface attachedInterface;
internal object lastValue;
internal Type collectionType;
internal string name;
internal int memoryAddress;
/// <inheritdoc/>
public MewtocolInterface AttachedInterface => attachedInterface;
/// <inheritdoc/>
public object Value => lastValue;
/// <inheritdoc/>
public RegisterType RegisterType { get; protected set; }
/// <inheritdoc/>
public Type CollectionType => collectionType;
/// <inheritdoc/>
public string Name => name;
/// <inheritdoc/>
public string PLCAddressName => GetRegisterPLCName();
/// <inheritdoc/>
public int MemoryAddress => memoryAddress;
#region Trigger update notify
public event PropertyChangedEventHandler PropertyChanged;
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed);
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
#endregion
public virtual void ClearValue() => SetValueFromPLC(null);
public virtual void SetValueFromPLC(object val) {
lastValue = val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
}
public void WithCollectionType(Type colType) => collectionType = colType;
#region Default accessors
public Type GetCollectionType() => CollectionType;
public RegisterType GetRegisterType() => RegisterType;
public virtual string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
public virtual string GetStartingMemoryArea() => MemoryAddress.ToString();
public virtual byte? GetSpecialAddress() => null;
public virtual string GetValueString() => Value?.ToString() ?? "null";
public virtual string GetRegisterString() => RegisterType.ToString();
public virtual string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
public virtual string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
public virtual string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}";
#endregion
#region Read / Write
public virtual async Task<object> ReadAsync() => throw new NotImplementedException();
public virtual async Task<bool> WriteAsync(object data) => throw new NotImplementedException();
public virtual Task<bool> SetValueAsync() => throw new NotImplementedException();
public virtual Task<object> GetValueAsync() => throw new NotImplementedException();
#endregion
public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
public virtual string ToString(bool additional) {
if (!additional) return this.ToString();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"PLC Naming: {GetRegisterPLCName()}");
sb.AppendLine($"Name: {Name ?? "Not named"}");
sb.AppendLine($"Value: {GetValueString()}");
sb.AppendLine($"Register Type: {RegisterType}");
sb.AppendLine($"Memory Address: {MemoryAddress}");
return sb.ToString();
}
}
}

View File

@@ -8,45 +8,7 @@ namespace MewtocolNet.Registers {
/// <summary> /// <summary>
/// Defines a register containing a boolean /// Defines a register containing a boolean
/// </summary> /// </summary>
public class BoolRegister : IRegister, IRegisterInternal, INotifyPropertyChanged { public class BoolRegister : BaseRegister {
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
public event Action<object> ValueChanged;
/// <summary>
/// Triggers when a property on the register changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public RegisterType RegisterType { get; private set; }
internal Type collectionType;
/// <summary>
/// The type of collection the register is in or null of added manually
/// </summary>
public Type CollectionType => collectionType;
internal bool lastValue;
/// <summary>
/// The value of the register
/// </summary>
public object Value => lastValue;
internal string name;
/// <summary>
/// The register name or null of not defined
/// </summary>
public string Name => name;
internal int memoryAddress;
/// <summary>
/// The registers memory adress if not a special register
/// </summary>
public int MemoryAddress => memoryAddress;
internal byte specialAddress; internal byte specialAddress;
/// <summary> /// <summary>
@@ -77,6 +39,8 @@ namespace MewtocolNet.Registers {
if (_spAddress > 0xF) if (_spAddress > 0xF)
throw new NotSupportedException("Special address cant be greater 15 or 0xF"); throw new NotSupportedException("Special address cant be greater 15 or 0xF");
lastValue = false;
memoryAddress = _areaAdress; memoryAddress = _areaAdress;
specialAddress = _spAddress; specialAddress = _spAddress;
name = _name; name = _name;
@@ -85,14 +49,41 @@ namespace MewtocolNet.Registers {
} }
public void WithCollectionType (Type colType) => collectionType = colType; #region Read / Write
public byte? GetSpecialAddress() => SpecialAddress; public override void SetValueFromPLC(object val) {
/// <summary> lastValue = (bool)val;
/// Builds the register area name for the mewtocol protocol TriggerChangedEvnt(this);
/// </summary> TriggerNotifyChange();
public string BuildMewtocolQuery() {
}
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
var read = await attachedInterface.ReadRawRegisterAsync(this);
var parsed = PlcValueParser.Parse<bool>(read);
SetValueFromPLC(parsed);
return parsed;
}
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object data) {
return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode((bool)data));
}
#endregion
/// <inheritdoc/>
public override byte? GetSpecialAddress() => SpecialAddress;
/// <inheritdoc/>
public override string BuildMewtocolQuery() {
//(R|X|Y)(area add [3] + special add [1]) //(R|X|Y)(area add [3] + special add [1])
StringBuilder asciistring = new StringBuilder(); StringBuilder asciistring = new StringBuilder();
@@ -109,37 +100,11 @@ namespace MewtocolNet.Registers {
} }
public void SetValueFromPLC(object val) { /// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(false);
lastValue = (bool)val; /// <inheritdoc/>
TriggerChangedEvnt(this); public override string GetRegisterPLCName() {
TriggerNotifyChange();
}
public string GetStartingMemoryArea() {
return MemoryAddress.ToString();
}
public bool IsUsedBitwise() => false;
public Type GetCollectionType() => CollectionType;
public RegisterType GetRegisterType() => RegisterType;
public string GetValueString() => Value.ToString();
public void ClearValue() => SetValueFromPLC(false);
public string GetRegisterString() => RegisterType.ToString();
public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
public string GetRegisterPLCName() {
var spAdressEnd = SpecialAddress.ToString("X1"); var spAdressEnd = SpecialAddress.ToString("X1");
@@ -159,13 +124,8 @@ namespace MewtocolNet.Registers {
} }
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed); /// <inheritdoc/>
public override string ToString(bool additional) {
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
public string ToString(bool additional) {
if (!additional) return this.ToString(); if (!additional) return this.ToString();
@@ -181,19 +141,6 @@ namespace MewtocolNet.Registers {
} }
public async Task<object> ReadAsync (MewtocolInterface interf) {
var read = await interf.ReadRawRegisterAsync(this);
return PlcValueParser.Parse<bool>(read);
}
public async Task<bool> WriteAsync (MewtocolInterface interf, object data) {
return await interf.WriteRawRegisterAsync(this, PlcValueParser.Encode((bool)data));
}
} }
} }

View File

@@ -1,20 +0,0 @@
namespace MewtocolNet.Registers {
/// <summary>
/// Result for a boolean register
/// </summary>
public class BoolRegisterResult {
/// <summary>
/// The command result
/// </summary>
public CommandResult Result { get; set; }
/// <summary>
/// The used register
/// </summary>
public BoolRegister Register { get; set; }
}
}

View File

@@ -9,150 +9,80 @@ namespace MewtocolNet.Registers {
/// <summary> /// <summary>
/// Defines a register containing a string /// Defines a register containing a string
/// </summary> /// </summary>
public class BytesRegister<T> : IRegister, IRegisterInternal { public class BytesRegister : BaseRegister {
internal int addressLength;
/// <summary> /// <summary>
/// Gets called whenever the value was changed /// The rgisters memory length
/// </summary> /// </summary>
public event Action<object> ValueChanged; public int AddressLength => addressLength;
/// <summary>
/// Triggers when a property on the register changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public RegisterType RegisterType { get; private set; }
internal Type collectionType;
/// <summary>
/// The type of collection the register is in or null of added manually
/// </summary>
public Type CollectionType => collectionType;
internal string lastValue;
/// <summary>
/// The value of the register
/// </summary>
public object Value => lastValue;
internal string name;
/// <summary>
/// The register name or null of not defined
/// </summary>
public string Name => name;
internal int memoryAdress;
/// <summary>
/// The registers memory adress if not a special register
/// </summary>
public int MemoryAddress => memoryAdress;
internal int memoryLength;
/// <summary>
/// The registers memory length
/// </summary>
public int MemoryLength => memoryLength;
internal short ReservedSize { get; set; } internal short ReservedSize { get; set; }
/// <summary> /// <summary>
/// Defines a register containing a string /// Defines a register containing a string
/// </summary> /// </summary>
public BytesRegister(int _adress, int _reservedSize, string _name = null) { public BytesRegister(int _address, int _reservedByteSize, string _name = null) {
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
name = _name; name = _name;
memoryAdress = _adress; memoryAddress = _address;
ReservedSize = (short)_reservedSize; ReservedSize = (short)_reservedByteSize;
//calc mem length //calc mem length
var wordsize = (double)_reservedSize / 2; //because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
if (wordsize % 2 != 0) { var byteSize = _reservedByteSize;
wordsize++; if (_reservedByteSize % 2 != 0) byteSize++;
}
RegisterType = RegisterType.DT_RANGE; RegisterType = RegisterType.DT_BYTE_RANGE;
addressLength = (byteSize / 2) - 1;
memoryLength = (int)Math.Round(wordsize + 1);
} }
public void WithCollectionType(Type colType) => collectionType = colType; public override string GetValueString() => Value == null ? "null" : ((byte[])Value).ToHexString("-");
/// <summary> /// <inheritdoc/>
/// Builds the register identifier for the mewotocol protocol public override string BuildMewtocolQuery() {
/// </summary>
public string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D"); StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + MemoryLength).ToString().PadLeft(5, '0')); asciistring.Append((MemoryAddress + AddressLength).ToString().PadLeft(5, '0'));
return asciistring.ToString(); return asciistring.ToString();
} }
internal string BuildCustomIdent(int overwriteWordLength) { /// <inheritdoc/>
public override void SetValueFromPLC (object val) {
if (overwriteWordLength <= 0) lastValue = (byte[])val;
throw new Exception("overwriteWordLength cant be 0 or less");
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + overwriteWordLength - 1).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
public byte? GetSpecialAddress() => null;
public Type GetCollectionType() => CollectionType;
public bool IsUsedBitwise() => false;
public void SetValueFromPLC(object val) {
lastValue = (string)val;
TriggerChangedEvnt(this); TriggerChangedEvnt(this);
TriggerNotifyChange(); TriggerNotifyChange();
} }
public string GetStartingMemoryArea() => MemoryAddress.ToString(); /// <inheritdoc/>
public override string GetRegisterString() => "DT";
public string GetValueString() => Value?.ToString() ?? ""; /// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(null);
public string GetRegisterString() => "DT"; /// <inheritdoc/>
public override async Task<object> ReadAsync() {
public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}"; var read = await attachedInterface.ReadRawRegisterAsync(this);
var parsed = PlcValueParser.Parse<byte[]>(read);
public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}"; SetValueFromPLC(parsed);
return parsed;
public string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}";
public void ClearValue() => SetValueFromPLC(null);
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed);
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
public string ToString(bool additional) => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
public async Task<object> ReadAsync(MewtocolInterface interf) {
var read = await interf.ReadRawRegisterAsync(this);
return PlcValueParser.Parse<T>(read);
} }
public async Task<bool> WriteAsync(MewtocolInterface interf, object data) { /// <inheritdoc/>
public override async Task<bool> WriteAsync(object data) {
return await interf.WriteRawRegisterAsync(this, (byte[])data); return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data);
} }

View File

@@ -1,19 +0,0 @@
namespace MewtocolNet.Registers {
/// <summary>
/// The results of a string register operation
/// </summary>
public class BytesRegisterResult<T> {
/// <summary>
/// The command result
/// </summary>
public CommandResult Result { get; set; }
/// <summary>
/// The register definition used
/// </summary>
public BytesRegister<T> Register { get; set; }
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -11,131 +12,64 @@ namespace MewtocolNet.Registers {
/// Defines a register containing a number /// Defines a register containing a number
/// </summary> /// </summary>
/// <typeparam name="T">The type of the numeric value</typeparam> /// <typeparam name="T">The type of the numeric value</typeparam>
public class NumberRegister<T> : IRegister, IRegisterInternal { public class NumberRegister<T> : BaseRegister {
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
public event Action<object> ValueChanged;
/// <summary>
/// Triggers when a property on the register changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public RegisterType RegisterType { get; private set; }
internal Type collectionType;
/// <summary>
/// The type of collection the register is in or null of added manually
/// </summary>
public Type CollectionType => collectionType;
internal T lastValue;
/// <summary>
/// The value of the register
/// </summary>
public object Value => lastValue;
internal string name;
/// <summary>
/// The register name or null of not defined
/// </summary>
public string Name => name;
internal int memoryAdress;
/// <summary>
/// The registers memory adress if not a special register
/// </summary>
public int MemoryAddress => memoryAdress;
internal int memoryLength;
/// <summary>
/// The rgisters memory length
/// </summary>
public int MemoryLength => memoryLength;
internal bool isUsedBitwise { get; set; }
internal Type enumType { get; set; } internal Type enumType { get; set; }
/// <summary> /// <summary>
/// Defines a register containing a number /// Defines a register containing a number
/// </summary> /// </summary>
/// <param name="_adress">Memory start adress max 99999</param> /// <param name="_address">Memory start adress max 99999</param>
/// <param name="_name">Name of the register</param> /// <param name="_name">Name of the register</param>
public NumberRegister (int _adress, string _name = null) { public NumberRegister (int _address, string _name = null) {
if (_adress > 99999) if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
throw new NotSupportedException("Memory adresses cant be greater than 99999");
memoryAdress = _adress; memoryAddress = _address;
name = _name; name = _name;
Type numType = typeof(T);
if (numType == typeof(short)) {
memoryLength = 0;
} else if (numType == typeof(ushort)) {
memoryLength = 0;
} else if (numType == typeof(int)) {
memoryLength = 1;
} else if (numType == typeof(uint)) {
memoryLength = 1;
} else if (numType == typeof(float)) {
memoryLength = 1;
} else if (numType == typeof(TimeSpan)) {
memoryLength = 1;
} else {
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
}
//set register type Type numType = typeof(T);
if(memoryLength == 1) {
RegisterType = RegisterType.DDT; var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
} else { if (!allowedTypes.Contains(numType))
RegisterType = RegisterType.DT; throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
}
var areaLen = (Marshal.SizeOf(numType) / 2) - 1;
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
lastValue = default(T);
} }
public NumberRegister (int _adress, string _name = null, bool isBitwise = false, Type _enumType = null) { /// <summary>
/// Defines a register containing a number
/// </summary>
/// <param name="_address">Memory start adress max 99999</param>
/// <param name="_enumType">Enum type to parse as</param>
/// <param name="_name">Name of the register</param>
public NumberRegister(int _address, Type _enumType, string _name = null) {
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
memoryAdress = _adress;
memoryAddress = _address;
name = _name; name = _name;
Type numType = typeof(T); Type numType = typeof(T);
if (numType == typeof(short)) {
memoryLength = 0; var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
} else if (numType == typeof(ushort)) { if (!allowedTypes.Contains(numType))
memoryLength = 0;
} else if (numType == typeof(int)) {
memoryLength = 1;
} else if (numType == typeof(uint)) {
memoryLength = 1;
} else if (numType == typeof(float)) {
memoryLength = 1;
} else if (numType == typeof(TimeSpan)) {
memoryLength = 1;
} else {
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
}
//set register type var areaLen = (Marshal.SizeOf(numType) / 2) - 1;
if (memoryLength == 1) { RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
RegisterType = RegisterType.DDT;
} else {
RegisterType = RegisterType.DT;
}
isUsedBitwise = isBitwise;
enumType = _enumType; enumType = _enumType;
lastValue = default(T);
} }
public void WithCollectionType(Type colType) => collectionType = colType; /// <inheritdoc/>
public override void SetValueFromPLC(object val) {
public void SetValueFromPLC(object val) {
lastValue = (T)val; lastValue = (T)val;
TriggerChangedEvnt(this); TriggerChangedEvnt(this);
@@ -143,20 +77,34 @@ namespace MewtocolNet.Registers {
} }
public byte? GetSpecialAddress() => null; /// <inheritdoc/>
public override string BuildMewtocolQuery() {
public string GetStartingMemoryArea() => MemoryAddress.ToString(); StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
public Type GetCollectionType() => CollectionType; int offsetAddress = 0;
if(RegisterType == RegisterType.DDT)
offsetAddress = 1;
public bool IsUsedBitwise() => isUsedBitwise; asciistring.Append((MemoryAddress + offsetAddress).ToString().PadLeft(5, '0'));
return asciistring.ToString();
public string GetValueString() { }
/// <inheritdoc/>
public override string GetValueString() {
if(typeof(T) == typeof(TimeSpan)) {
return $"{Value} [{((TimeSpan)Value).AsPLC()}]";
}
//is number or bitwise //is number or bitwise
if (enumType == null) { if (enumType == null) {
return $"{Value}{(isUsedBitwise ? $" [{GetBitwise().ToBitString()}]" : "")}"; return $"{Value}";
} }
@@ -204,6 +152,27 @@ namespace MewtocolNet.Registers {
} }
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(default(T));
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
var read = await attachedInterface.ReadRawRegisterAsync(this);
var parsed = PlcValueParser.Parse<T>(read);
SetValueFromPLC(parsed);
return parsed;
}
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object data) {
return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode((T)data));
}
/// <summary> /// <summary>
/// Gets the register bitwise if its a 16 or 32 bit int /// Gets the register bitwise if its a 16 or 32 bit int
/// </summary> /// </summary>
@@ -230,76 +199,6 @@ namespace MewtocolNet.Registers {
} }
public string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + MemoryLength).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
public string GetRegisterString() {
if (Value is short) {
return "DT";
}
if (Value is ushort) {
return "DT";
}
if (Value is int) {
return "DDT";
}
if (Value is uint) {
return "DDT";
}
if (Value is float) {
return "DDT";
}
if (Value is TimeSpan) {
return "DDT";
}
throw new NotSupportedException("Numeric type is not supported");
}
public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}";
public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}";
public string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}";
public void ClearValue() => SetValueFromPLC(default(T));
internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed);
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
public RegisterType GetRegisterType() => RegisterType;
public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
public string ToString(bool additional) => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
public async Task<object> ReadAsync(MewtocolInterface interf) {
var read = await interf.ReadRawRegisterAsync(this);
return PlcValueParser.Parse<T>(read);
}
public async Task<bool> WriteAsync(MewtocolInterface interf, object data) {
return await interf.WriteRawRegisterAsync(this, PlcValueParser.Encode((T)data));
}
} }
} }

View File

@@ -1,35 +0,0 @@
namespace MewtocolNet.Registers {
/// <summary>
/// Result for a read/write operation
/// </summary>
/// <typeparam name="T">The type of the numeric value</typeparam>
public class NumberRegisterResult<T> {
/// <summary>
/// Command result
/// </summary>
public CommandResult Result { get; set; }
/// <summary>
/// The used register
/// </summary>
public NumberRegister<T> Register { get; set; }
/// <summary>
/// Trys to get the value of there is one
/// </summary>
public bool TryGetValue(out T value) {
if (Result.Success) {
value = (T)Register.Value;
return true;
}
value = default;
return false;
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a string
/// </summary>
public class StringRegister : BaseRegister {
internal int addressLength;
/// <summary>
/// The rgisters memory length
/// </summary>
public int AddressLength => addressLength;
internal short ReservedSize { get; set; }
/// <summary>
/// Defines a register containing a string
/// </summary>
public StringRegister (int _adress, int _reservedByteSize, string _name = null) {
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
name = _name;
memoryAddress = _adress;
ReservedSize = (short)_reservedByteSize;
//calc mem length
var wordsize = (double)_reservedByteSize / 2;
if (wordsize % 2 != 0) {
wordsize++;
}
RegisterType = RegisterType.DT_BYTE_RANGE;
addressLength = (int)Math.Round(wordsize + 1);
}
/// <inheritdoc/>
public override string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + AddressLength).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
/// <inheritdoc/>
public override void SetValueFromPLC (object val) {
lastValue = (byte[])val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
}
/// <inheritdoc/>
public override string GetRegisterString() => "DT";
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(null);
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
var read = await attachedInterface.ReadRawRegisterAsync(this);
return PlcValueParser.Parse<byte[]>(read);
}
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object data) {
return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data);
}
}
}

View File

@@ -0,0 +1,12 @@
namespace MewtocolNet {
internal enum TCPMessageResult {
Waiting,
Success,
NotConnected,
FailedWithException,
FailedLineFeed,
}
}

View File

@@ -1,5 +1,6 @@
using MewtocolNet.Registers; using MewtocolNet.Registers;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
@@ -19,10 +20,13 @@ namespace MewtocolNet.TypeConversion {
{ PlcVarType.TIME, RegisterType.DDT }, { PlcVarType.TIME, RegisterType.DDT },
{ PlcVarType.WORD, RegisterType.DT }, { PlcVarType.WORD, RegisterType.DT },
{ PlcVarType.DWORD, RegisterType.DDT }, { PlcVarType.DWORD, RegisterType.DDT },
{ PlcVarType.STRING, RegisterType.DT_RANGE }, { PlcVarType.STRING, RegisterType.DT_BYTE_RANGE },
}; };
/// <summary>
/// All conversions for reading dataf from and to the plc
/// </summary>
internal static List<IPlcTypeConverter> items = new List<IPlcTypeConverter> { internal static List<IPlcTypeConverter> items = new List<IPlcTypeConverter> {
new PlcTypeConversion<bool>(RegisterType.R) { new PlcTypeConversion<bool>(RegisterType.R) {
@@ -116,6 +120,64 @@ namespace MewtocolNet.TypeConversion {
}, },
}, },
new PlcTypeConversion<float>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<float>),
PlcVarType = PlcVarType.REAL,
FromRaw = bytes => {
var val = BitConverter.ToUInt32(bytes, 0);
byte[] floatVals = BitConverter.GetBytes(val);
float finalFloat = BitConverter.ToSingle(floatVals, 0);
return finalFloat;
},
ToRaw = value => {
return BitConverter.GetBytes(value);
},
},
new PlcTypeConversion<TimeSpan>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<TimeSpan>),
PlcVarType = PlcVarType.TIME,
FromRaw = bytes => {
var vallong = BitConverter.ToUInt32(bytes, 0);
var valMillis = vallong * 10;
var ts = TimeSpan.FromMilliseconds(valMillis);
return ts;
},
ToRaw = value => {
var tLong = (uint)(value.TotalMilliseconds / 10);
return BitConverter.GetBytes(tLong);
},
},
new PlcTypeConversion<byte[]>(RegisterType.DT) {
HoldingRegisterType = typeof(BytesRegister),
FromRaw = bytes => bytes,
ToRaw = value => value,
},
new PlcTypeConversion<BitArray>(RegisterType.DT) {
HoldingRegisterType = typeof(BytesRegister),
PlcVarType = PlcVarType.WORD,
FromRaw = bytes => {
BitArray bitAr = new BitArray(bytes);
return bitAr;
},
ToRaw = value => {
byte[] ret = new byte[(value.Length - 1) / 8 + 1];
value.CopyTo(ret, 0);
return ret;
},
},
}; };

View File

@@ -41,9 +41,12 @@ namespace MewtocolNet {
public static RegisterType? GetDefaultRegisterType (Type type) => public static RegisterType? GetDefaultRegisterType (Type type) =>
conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetPlcRegisterType(); conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetPlcRegisterType();
public static Type GetDefaultPlcVarType (this PlcVarType type) => public static Type GetDefaultRegisterHoldingType (this PlcVarType type) =>
conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetHoldingRegisterType(); conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetHoldingRegisterType();
public static Type GetDefaultRegisterHoldingType (this Type type) =>
conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetHoldingRegisterType();
public static Type GetDefaultDotnetType (this PlcVarType type) => public static Type GetDefaultDotnetType (this PlcVarType type) =>
conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetDotnetType(); conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetDotnetType();

View File

@@ -91,7 +91,7 @@ namespace MewtocolTests {
} }
private void TestBasicGeneration(IRegister reg, string propName, object expectValue, int expectAddr, string expectPlcName) { private void TestBasicGeneration(IRegisterInternal reg, string propName, object expectValue, int expectAddr, string expectPlcName) {
Assert.NotNull(reg); Assert.NotNull(reg);
Assert.Equal(propName, reg.Name); Assert.Equal(propName, reg.Name);
@@ -115,7 +115,7 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1)); var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1));
//test generic properties //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestBool1), false, 85, "R85"); TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBool1), false, 85, "R85");
} }
@@ -128,7 +128,7 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD)); var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD));
//test generic properties //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestBoolInputXD), false, 0, "XD"); TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBoolInputXD), false, 0, "XD");
} }
@@ -141,7 +141,7 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16)); var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16));
//test generic properties //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestInt16), (short)0, 899, "DT899"); TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestInt16), (short)0, 899, "DT899");
} }
@@ -154,7 +154,7 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16)); var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16));
//test generic properties //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestUInt16), (ushort)0, 342, "DT342"); TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt16), (ushort)0, 342, "DT342");
} }
@@ -167,7 +167,7 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32)); var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32));
//test generic properties //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestInt32), (int)0, 7001, "DDT7001"); TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestInt32), (int)0, 7001, "DDT7001");
} }
@@ -180,7 +180,7 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32)); var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32));
//test generic properties //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestUInt32), (uint)0, 765, "DDT765"); TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt32), (uint)0, 765, "DDT765");
} }
@@ -193,70 +193,7 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32)); var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32));
//test generic properties //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestFloat32), (float)0, 7003, "DDT7003"); TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestFloat32), (float)0, 7003, "DDT7003");
}
[Fact(DisplayName = "String generation")]
public void StringGen() {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestString2));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestString2), null!, 7005, "DT7005");
Assert.Equal(5, ((BytesRegister<string>)register).ReservedSize);
Assert.Equal(4, ((BytesRegister<string>)register).MemoryLength);
}
[Fact(DisplayName = "BitArray 16bit generation")]
public void BitArray16Gen() {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister($"Auto_Bitwise_DT7010");
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DT7010", (short)0, 7010, "DT7010");
Assert.True(((NumberRegister<short>)register).isUsedBitwise);
Assert.Equal(0, ((NumberRegister<short>)register).MemoryLength);
}
[Fact(DisplayName = "BitArray 32bit generation")]
public void BitArray32Gen() {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister($"Auto_Bitwise_DDT8010");
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DDT8010", (int)0, 8010, "DDT8010");
Assert.True(((NumberRegister<int>)register).isUsedBitwise);
Assert.Equal(1, ((NumberRegister<int>)register).MemoryLength);
}
[Fact(DisplayName = "BitArray single bool generation")]
public void BitArraySingleBool16Gen() {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister($"Auto_Bitwise_DT1204");
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DT1204", (short)0, 1204, "DT1204");
Assert.True(((NumberRegister<short>)register).isUsedBitwise);
} }
@@ -269,35 +206,25 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime)); var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime));
//test generic properties //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestTime), TimeSpan.Zero, 7012, "DDT7012"); TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestTime), TimeSpan.Zero, 7012, "DDT7012");
} }
[Fact(DisplayName = "Enum16 generation")] //[Fact(DisplayName = "String generation")]
public void Enum16Gen() { //public void StringGen() {
var interf = new MewtocolInterface("192.168.0.1"); // var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller(); // interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestEnum16)); // var register = interf.GetRegister(nameof(TestRegisterCollection.TestString2));
//test generic properties // //test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestEnum16), (short)TestRegisterCollection.CurrentState.Undefined, 50, "DT50"); // TestBasicGeneration(register, nameof(TestRegisterCollection.TestString2), null!, 7005, "DT7005");
} // Assert.Equal(5, ((BytesRegister<string>)register).ReservedSize);
// Assert.Equal(4, ((BytesRegister<string>)register).MemoryLength);
[Fact(DisplayName = "Enum32 generation")] //}
public void Enum32Gen() {
var interf = new MewtocolInterface("192.168.0.1");
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
var register = interf.GetRegister(nameof(TestRegisterCollection.TestEnum32));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestEnum32), (int)TestRegisterCollection.CurrentState.Undefined, 51, "DDT51");
}
} }

View File

@@ -0,0 +1,17 @@
using MewtocolNet;
namespace MewtocolTests.EncapsulatedTests;
public class ExpectedPlcInformationData {
public string PLCName { get; set; }
public string PLCIP { get; set; }
public int PLCPort { get; set; }
public CpuType Type { get; set; }
public int ProgCapacity { get; set; }
}

View File

@@ -0,0 +1,20 @@
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 AfterWriteValue { get; set; }
public string RegisterPlcAddressName { get; set; }
}

View File

@@ -43,7 +43,7 @@ namespace MewtocolTests {
} }
[Fact(DisplayName = nameof(MewtocolHelpers.ToHexASCIIBytes))] [Fact(DisplayName = nameof(MewtocolHelpers.BytesFromHexASCIIString))]
public void ToHexASCIIBytesGeneration() { public void ToHexASCIIBytesGeneration() {
string test = "Hello, world!"; string test = "Hello, world!";
@@ -62,7 +62,7 @@ namespace MewtocolTests {
0x4C, 0x4C,
0x44, 0x44,
0x21 0x21
}, test.ToHexASCIIBytes()); }, test.BytesFromHexASCIIString());
} }

View File

@@ -1,17 +1,21 @@
using MewtocolNet; using MewtocolNet;
using MewtocolNet.Logging; using MewtocolNet.Logging;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace MewtocolTests { namespace MewtocolTests
{
public class TestLivePLC { public class TestLivePLC {
private readonly ITestOutputHelper output; private readonly ITestOutputHelper output;
private List<ExpectedTestData> testData = new() { private List<ExpectedPlcInformationData> testPlcInformationData = new() {
new ExpectedTestData { new ExpectedPlcInformationData {
PLCName = "FPX-H C30T", PLCName = "FPX-H C30T",
PLCIP = "192.168.115.210", PLCIP = "192.168.115.210",
@@ -20,7 +24,7 @@ namespace MewtocolTests {
ProgCapacity = 32, ProgCapacity = 32,
}, },
new ExpectedTestData { new ExpectedPlcInformationData {
PLCName = "FPX-H C14R", PLCName = "FPX-H C14R",
PLCIP = "192.168.115.212", PLCIP = "192.168.115.212",
@@ -32,12 +36,29 @@ namespace MewtocolTests {
}; };
private List<RegisterReadWriteTest> testRegisterRW = new() {
new RegisterReadWriteTest {
TargetRegister = new BoolRegister(IOType.R, 0xA, 10),
RegisterPlcAddressName = "R10A",
IntialValue = false,
AfterWriteValue = true,
},
new RegisterReadWriteTest {
TargetRegister = new NumberRegister<int>(3000),
RegisterPlcAddressName = "DT3000",
IntialValue = (int)0,
AfterWriteValue = (int)-513,
},
};
public TestLivePLC(ITestOutputHelper output) { public TestLivePLC(ITestOutputHelper output) {
this.output = output; this.output = output;
Logger.LogLevel = LogLevel.Verbose; Logger.LogLevel = LogLevel.Verbose;
Logger.OnNewLogMessage((d, m) => { Logger.OnNewLogMessage((d, l, m) => {
output.WriteLine($"Mewtocol Logger: {d} {m}"); output.WriteLine($"Mewtocol Logger: {d} {m}");
@@ -48,7 +69,7 @@ namespace MewtocolTests {
[Fact(DisplayName = "Connection cycle client to PLC")] [Fact(DisplayName = "Connection cycle client to PLC")]
public async void TestClientConnection() { public async void TestClientConnection() {
foreach (var plc in testData) { foreach (var plc in testPlcInformationData) {
output.WriteLine($"Testing: {plc.PLCName}"); output.WriteLine($"Testing: {plc.PLCName}");
@@ -69,7 +90,7 @@ namespace MewtocolTests {
[Fact(DisplayName = "Reading basic information from PLC")] [Fact(DisplayName = "Reading basic information from PLC")]
public async void TestClientReadPLCStatus() { public async void TestClientReadPLCStatus() {
foreach (var plc in testData) { foreach (var plc in testPlcInformationData) {
output.WriteLine($"Testing: {plc.PLCName}\n"); output.WriteLine($"Testing: {plc.PLCName}\n");
@@ -90,19 +111,38 @@ namespace MewtocolTests {
} }
} //[Fact(DisplayName = "Reading basic information from PLC")]
//public async void TestRegisterReadWriteAsync () {
public class ExpectedTestData { // foreach (var plc in testPlcInformationData) {
public string PLCName { get; set; } // output.WriteLine($"Testing: {plc.PLCName}\n");
public string PLCIP { get; set; } // var client = new MewtocolInterface(plc.PLCIP, plc.PLCPort);
public int PLCPort { get; set; } // foreach (var testRW in testRegisterRW) {
public CpuType Type { get; set; } // client.AddRegister(testRW.TargetRegister);
public int ProgCapacity { get; set; } // }
// await client.ConnectAsync();
// Assert.True(client.IsConnected);
// foreach (var testRW in testRegisterRW) {
// client.AddRegister(testRW.TargetRegister);
// }
// Assert.Equal(client.PlcInfo.CpuInformation.Cputype, plc.Type);
// Assert.Equal(client.PlcInfo.CpuInformation.ProgramCapacity, plc.ProgCapacity);
// client.Disconnect();
// }
//}
} }

View File

@@ -122,20 +122,12 @@ public class TestRegisterBuilder {
foreach (var item in dict) { foreach (var item in dict) {
try { output.WriteLine($"Expected: {item.Key}");
output.WriteLine($"Expected: {item.Key}"); var built = RegBuilder.Factory.FromPlcRegName(item.Key).AsPlcType(PlcVarType.BOOL).Build();
var built = RegBuilder.Factory.FromPlcRegName(item.Key).AsPlcType(PlcVarType.BOOL).Build(); output.WriteLine($"{(built?.ToString(true) ?? "null")}\n");
Assert.Equivalent(item.Value, built);
output.WriteLine($"{(built?.ToString(true) ?? "null")}\n");
Assert.Equivalent(item.Value, built);
} catch (Exception ex) {
output.WriteLine(ex.Message.ToString());
}
} }
@@ -204,11 +196,9 @@ public class TestRegisterBuilder {
[Fact(DisplayName = "Parsing as Bytes Register (Casted)")] [Fact(DisplayName = "Parsing as Bytes Register (Casted)")]
public void TestRegisterBuildingByteRangeCasted() { public void TestRegisterBuildingByteRangeCasted() {
var expect = new BytesRegister<byte[]>(303, 5); var expect = new BytesRegister(303, 5);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsBytes(5).Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType<short>().Build());
} }

View File

@@ -16,13 +16,15 @@ namespace MewtocolTests {
[Fact(DisplayName = "Numeric mewtocol query building")] [Fact(DisplayName = "Numeric mewtocol query building")]
public void NumericRegisterMewtocolIdentifiers() { public void NumericRegisterMewtocolIdentifiers() {
List<IRegister> registers = new List<IRegister> { List<IRegisterInternal> registers = new List<IRegisterInternal> {
new NumberRegister<short>(50, _name: null), new NumberRegister<short>(50),
new NumberRegister<ushort>(50, _name: null), new NumberRegister<ushort>(50),
new NumberRegister<int>(50, _name : null), new NumberRegister<int>(50),
new NumberRegister<uint>(50, _name : null), new NumberRegister<uint>(50),
new NumberRegister<float>(50, _name : null), new NumberRegister<float>(50),
new NumberRegister<TimeSpan>(50, _name : null), new NumberRegister<TimeSpan>(50),
new BytesRegister(50, 30),
new BytesRegister(50, 31),
}; };
List<string> expectedIdents = new List<string> { List<string> expectedIdents = new List<string> {
@@ -32,12 +34,14 @@ namespace MewtocolTests {
"D0005000051", //double word register "D0005000051", //double word register
"D0005000051", //double word register "D0005000051", //double word register
"D0005000051", //double word register "D0005000051", //double word register
"D0005000065", //variable len register even bytes
"D0005000066", //variable len register odd bytes
}; };
//test mewtocol idents //test mewtocol idents
for (int i = 0; i < registers.Count; i++) { for (int i = 0; i < registers.Count; i++) {
IRegister? reg = registers[i]; IRegisterInternal? reg = registers[i];
string expect = expectedIdents[i]; string expect = expectedIdents[i];
Assert.Equal(expect, reg.BuildMewtocolQuery()); Assert.Equal(expect, reg.BuildMewtocolQuery());
@@ -49,7 +53,7 @@ namespace MewtocolTests {
[Fact(DisplayName = "PLC register naming convention test")] [Fact(DisplayName = "PLC register naming convention test")]
public void PLCRegisterIdentifiers() { public void PLCRegisterIdentifiers() {
List<IRegister> registers = new List<IRegister> { List<IRegisterInternal> registers = new List<IRegisterInternal> {
//numeric ones //numeric ones
new NumberRegister<short>(50, _name: null), new NumberRegister<short>(50, _name: null),
new NumberRegister<ushort>(60, _name : null), new NumberRegister<ushort>(60, _name : null),
@@ -67,7 +71,7 @@ namespace MewtocolTests {
new BoolRegister(IOType.Y, 0xC, 75), new BoolRegister(IOType.Y, 0xC, 75),
//string //string
new BytesRegister<string>(999, 5), new BytesRegister(999, 5),
}; };
List<string> expcectedIdents = new List<string> { List<string> expcectedIdents = new List<string> {
@@ -96,7 +100,7 @@ namespace MewtocolTests {
//test mewtocol idents //test mewtocol idents
for (int i = 0; i < registers.Count; i++) { for (int i = 0; i < registers.Count; i++) {
IRegister? reg = registers[i]; IRegisterInternal? reg = registers[i];
string expect = expcectedIdents[i]; string expect = expcectedIdents[i];
Assert.Equal(expect, reg.GetRegisterPLCName()); Assert.Equal(expect, reg.GetRegisterPLCName());
@@ -134,7 +138,7 @@ namespace MewtocolTests {
var ex3 = Assert.Throws<NotSupportedException>(() => { var ex3 = Assert.Throws<NotSupportedException>(() => {
new BytesRegister<string>(100000, 5); new BytesRegister(100000, 5);
}); });

Binary file not shown.

View File

@@ -2,17 +2,17 @@
<ProjectConfiguration CompactMode="1"> <ProjectConfiguration CompactMode="1">
<PaneContents> <PaneContents>
<Pane-1 PaneBaseID="410" PaneInstanceNumber="1"> <Pane-1 PaneBaseID="410" PaneInstanceNumber="1">
<Contents BinaryData="B31A0000789CE599D16EDA301486FB1CBB8AD28BDE8D38900252A94449DA4582A64A22BA699A509678D45A6247B133CA5E6AAF383B296D49CB80AA2196768362E03FE7E77C9CF818FE7C383A3A9B108C18C96E118EC8421991240D423621111CA84055BCF00E26C140EDB4F9F51D59D821C1530417035553CFCF4624CE134C57178ACD6062E308DE8B97151B3398E120BE0E121ECC8E2066E8078299AA0C6334C7095FF3C0A0DF696B1D1E9D050C85B72862773CB3C1F5C39C91E7EB4F308860F64CCBFD59F769C013462261EB351FA0EA631AC4395CB300B44ECFE8F62A1674D05FB350ACDF6441AF5AB8BC1946510629DDC1466FBD10BDB7D6A15D35C1490BF50E30746DDD43B17E9389CE0B137120AA3045147D8F61F1ADD9FEDDA8D8019BEC3871F410F829FD568B46D5A2BF4C61830E5B8F3D560628E296CF79E837C2F3C252D19AFC43501B5F65244FE983DA8521C922A528F34075AD91E39AAA3226E1CF328162458805C507E3766E32F8EBB1B745B895D0F6AD89AAF8F09EDBF621651784C4A0147B6C59A8F59EC8B74974391C7BD60B81E22541C68A86F4600C4386082EEF3AC54A481D2C34E2C94A74DE43A6E95A9EB74AE1F22A6B6B2974D03DADCA46CE64625DFBDBDF371E3E859E0EDDD9D5D8B9188EB7EAFC2F37D64A76E138AF0B5A2596DAF098791AA33060701F445ABD784CD3970E90696F48543720D13F364E73F6D994AC8B2A869A26D4580BF13D009CCAD43E7EAFDF970A4D53BDC3C9B47599C8987E57D380546C1ABBB15DC624D817CFC783006A4B05C8B53608EA06E4B18CCF8C7B013A39A9FBD6C6F11852E1F17CD7BEBEFA6A7C6B663A40CC85734499382AEFCE099C1E6BD509AB165840AE29EEB6A8FA81398953D88C835A200A677CB4D5C521503A562F6CFD97AC444FF928D9EB34E41F6BF400DB12D0A5C2E3DB13AB113C16CE13B9663A43AE9B5C33131D156CF61A160ED23740B2DF116A6C1BC544348D83A5F81552BCB6B9F2F3D962B40C6314F25DC9838C0F0FFB1D60F562479A950FE0E1AAEE0DCA906C2EAF717F526C2A86061B5398311895713621DD055BD5E53BA1795F1A3BD5FE5FF56EADFD3F75FE17D00A35F9"> <Contents BinaryData="9B1A0000789CE599D16EDA301486FB1CBB8A72D34B92405A904A2B4AD2CE12345512C1A669425EEC516B891D254E297B91BDEEECA4B42594C1AAB258DA1D26FCE7FC9C8FE31C875F1F8E8ECEC68C12CEB229A1882DB4214B5218F13143B8AF9BBA16447738817DBDD316AFEFD802448C4E085EF475433F3F1BB2B84868BE7AA1018E1340117E9097354039CE288C6F60228201842927DF09CE746D1093394DC45A04367B9DB6D111D139E4249A12C4EF44665BE80705672FD71F3144387BA115FEDC87148A8448266CBDE6C3ACFB98C0B8C06B164CA3D3B54FBB350B96D95BB350AEDF64C1AA5BB8BA1D2094E13CDFC34677BD10DDB7D6A15D3721484BF51E302C63DD43B97E9389CE868918CA2A4C484EBEC5B8FCD5ECFE6DD4EC98DBEC78317A0CFC9C7EA745BB6E315CA6B84187ADA71EAB029471ABF702F293D07969A96C4DF1257240AF3356A4F9A3DAC711CB905696B9AFFBEED0F31D5D1BB1E84795407311E1B0FC62C2CE6D86EF9F7A5B865B0941E88E752DC40FC27688737EC9586C56E2802F4BB5D595F9B6892E2E2E363EAE0509CC78D98E018E71C409A3D59E53AEA4D0A35223DFACC5161DE438BE1B04AB04BEA8B1B196C2324F4FEAB2A1371EBB37E1EECF8D06CFA127037F763DF22E07A39DBAF0F3ADBB925D7ADEEB825605E560709C228D490439560A90E384CA2172C09644874624FB07D0B4E09F1CA520D5EC34CDA7B116127700F34429324ED8EDF59482D354EF08366D4B2D364E786A18A652741ADBDAAE6206D504D4560A90EF6E111C1A50C0333135FE15A0E3E3436F6E028FAD149E20F4C1CDF517FB6B33F301E13E9E939CCBC3B24A8D2440996ACD70D3B2E2FF98913C83CD04A405C9F14C0CB6963C022AC569C3D27FC949F6524812E54E42A28B2CA5E88460EC3642C7A545A21A1D5BAD1DAE99512E9774941BE34CC59E201CB06F3487E4690C97F2F9A3BCB6BDF0F3D962B88C6212893B5280B9181A943BBCDA8A4DDF07BC1B692097E301A039CE3846559C6D00F7815477F94E68DE97C65EB5FF53BD5B6BFF439DFF0625CE315E">
<FilterHistory/> <FilterHistory/>
</Contents> </Contents>
</Pane-1> </Pane-1>
<Pane-2 PaneBaseID="410" PaneInstanceNumber="4"> <Pane-2 PaneBaseID="410" PaneInstanceNumber="4">
<Contents BinaryData="480F0000789CED97ED6EDA3014867B07BB052B3740BEF8A8542AA524ED224183928C6ABF901B7B602DB1516206F4EA6727D036860E8658C70FFEE504BFEF797D9E9880FBE5EAEA66C028E12C7F2214B105E8B16C06133E6008773543035132C519EC6AB625AEA76CE1278C8E085E74355DBBBDE9B1749ED16273017C8E339F22BC941F039F729C53983EC24C98F908534E7E109C6BC049C98466A216C6C6B56DE9B670E79093E489203E159D9B42EFCC397B5F7FC510E1FC9D56E4F39633281A22D9B0B12B87A1E618C1748E6B110CDDEE34DB1D2582695CD72294F551114C35C2FDD04128C74571408C4E7D109D63E760A9210469A93E0086A9D73394F55121ECAD102994531891823CA7B87C6AF63F1B4A1CE3A338418AD6C66FEDF7466CAA11E3D50CFFC7848DD733561994BED5BD88BC103A29239547536CA2F0E943CEE6B362AD0E71C27204CA3177B5D0EB05A1AB813E4B7E560D80870887E5C6449C618E7FBD9E6D69B711FAB137D0408C9722F664FC5C6D7633A610A770655456115F955E664776FFC8220EBF795BEB4194C19C97A733C2294E3861B4FA0A2A2BA90CA8D4C89B8AB93850AE1B7A51B4E910EA357FD368B7544D2F180CBCC778FFBABEF3E63B72C2F1433FB873FA7B75F1F7A1B791DD05C16E41A302F489A0CC3303655C40ED06659D1BA8FB0BA9DDA4EC7323A51BCE85D5644CD6AC945DEE01D4DE7E959D148F1B5BCDB3A2A3CEE773E0A0351DF72FF15856EB1FF311805A6705686B44A72304FC42FE9EF46981738E51E52317ED0270082435E589D09C96C641B3FFD3BC1BB53FCDB7BF01DF64C269"> <Contents BinaryData="56170000789CED985D4FE2401486FD177B3BE95E7827FD023111930A559B8035D32EC61863C6CE2C4CB69D21EDB0C8FEFA9DB6205250C0156C36DC904EE979CF3BE7E9994E0BBF1D1C9C7638A382C7B794613E024D1E0D50203A1C9386A229C00BFA24420DC534E4719F8F9C80B32E25A386A22A67A74D1E0E23964C0F802348E4304C9ED3BF81C30489190AAF5124C51C4C98A03F2989156085B4C7223996C2DA8969A8A6541748D0E09662D19799AB32DE1A0AFE7A7C451026F1AB58E9CF7E1E209910A7092BCB7C68451F5D140EC99C054D35EBD5E37AC182AE9DCC59C8C61FB2A0172D5CDC5818C72449D6B0519F2F44FDA375308A2624E9347A0D18BA3AEF211B7FC884B96022446915BA34A14F21C9EE9AD5F746C18EF6961D37C413E159FA9516AB458BFE7840BED061E5A5C772814C373FE7D13F94F5324B596BCA49240EBB8CF970904CA22109788C4156E68602EDA60B5B0A68F3E0579E00D8980A944D4CDAB989C9EF97DE4EE5A6818E6F7714E0936769BBF7F8944F765A26484234D672294F8C332DBD9E667F4BC2873FEC85EB8117A15864DDE99190048272962F41D9288D74591A939E2C88CB866AB5A0ED79D30C509DD3D7B5E35A31A6E9763AF6B5BFFABAB635D3ED5AF0F1B2ED9E5BED9571FEDD8D3D0D3B77DDE501951CD00E41E92503A5ED412D0765940DD4C59ED4725266D948A99AB567D57BA413568559AE0074BC88F353F1B47CA35A2A3AC5FAEC060E9ED0696D88C7306A5BE62301D54A0568A144BB212472403E8DC8460BDC77BDAFA99191C89F64FBA8EAA542E53B1DFB0B5025392A4FC4F2856013588757240CF9E1B6573CB35CDB71CF87CEF5E57DF5614BA8C0154A9A7D1ACA77FCAC6AEFA143A3F3B12010B1DE9A5D5674FC49A84A84C782D0BA03F7EAD191663E00F702DC66855FA2302DF2BFB072922E8DC510856F91FA6FA0AC85E0BD86A8CCEAFD094D32FBD0F15E87C09A6A6DF4F8D9FEFEBAE8685768B6BE9996BD907E5D725842624170AEB36F8B956D31F709FDEC2FBD3D3AC7">
<FilterHistory/> <FilterHistory/>
</Contents> </Contents>
</Pane-2> </Pane-2>
<Pane-3 PaneBaseID="410" PaneInstanceNumber="5"> <Pane-3 PaneBaseID="410" PaneInstanceNumber="5">
<Contents BinaryData="0A070000789CC595D16E9B3014867BB5BD86C50B2410B252A9A94413D6218512014BB5ABCAC5678935B023E32C499F7EB6296DA1E912659576C731FEFFF3F91C1FF8F4F9ECEC32E28C4A2EEE28237C83C6BC5CE15C469CC0C8B22D94E64B28F1C87207EA79C93761CED99CC26664F5ADABCB312FD625AB9A07144A28434660AB5FA39049100C17B7B85466210126E94F0AC2427E4117AC54B132B62FDC41DF55EE124B9ADF5122972AF350E9FDB5E4AFE36F800988575AC5176C575825243A616F1F87DDE598E3620D2D04BBEF7AC373AF83E0D8172D04139F84E07411BECE7C420454D511185EBB10DEA97518742154A7B5FA886638FD3683894F8270DF40145857614E2BFA5080B93587EF4607C77E0F272EC893F14BFA8388C32E62B65BC17F24EC3DCF586D607CEBB5943E52B630486634D521AA90DD08BE5E554FEA04722E0832651E5949308E938985A63CFF55274001A1129B83299C9980DFCFB3ADED1A619805918532D82AECC5FD437DD8A64C091478E7D456A9DC192FC7D3D9DFB3C892EFC19BFD282DB190663A5328209794B3FA136422AD8C99D6E8C58EB91AA8C92409D2B4C990D82D7FC73EFFD2D58CE3280A6EB3C3FBA6FE8BEFDC4FEE6FA6F1B53F3DA8CB7ECC8246761DC7FB05BDBA41FFD0281456FA4684AC022181D43E7AD3BEF21FD3A22EE507F5E563BB7154EDFF56EF5EEBB777F507221839D5"> <Contents BinaryData="C3150000789CED98DF4EE24014C67D89BD9F742FBC5BFA0FC5444C2A546D02D6B45D8C31C68C9D5998EC7486B4C322FB72FB6A3B9D824A414157B4D970437A4ACF77BE39BF9E49DB3F5F76760EBB9C11C1D34BC2101F83164F8630165D8E7053333410C6039CC0A6665BF278C0C75ECC598FE07153D3B5A3C316A7A38465B303E0099C780CE1FBFC6FE031815306E9394CA498873013E407C1A9061C4AFA2C91B114360E6C4BB7A5BA8082C497048981AC5C97F9CE48F0A7F1198608A74F72A53FF77E0865419417AC2DF361947DF4201DE1390B866E37EAFB8D9205D33898B3A0E2375930CB164E2E1C84529C656BD868CC37A2F1D63E58651392749EBD060C539FF7A0E23799B0174C509877A147327247B1BA6B56DF1B253BC673767C8AA6C28FE5575AAC972D469321FE4487B587192B04946E712E24BF09EB2B4B6A34E522328F9DA67C34CCA6D9018E798A806A73530BDC961FB435D0E1F1CFA200701111502D4CDAB948F1AF87D9CEE566895EE4763510E17B69BB7F7B572C76D6A6005338310AA9504C9496D9C8AB3F271105DFDD85EB4198C054A8E90C31C5B1209C155B908AF24C9FE539F9C992B81CA8763B70C3705621D0E7F44D637FAF9CD3F2BB5DF73C5A7D5DC779D4ED39C1ED69C73F763A2BF3A2AB0B779676ECFBCB136A05A00F0465560C94B105B51C94553550275B52CB49D95523A51BCE9655FF964C599556B902D0FE22CE77C5D38EAC7AA5E894FBF33170D0944EFB95782C6B6FC37C24A0BD4A015A68D1C7101205A08824F8551BDC577360E88995C99F6CF3A81A954215795DF713506505AA50A4F285E035B076CF30A57C77D33B9E5DADC7F1300ABCF3D3EBFACD8650813398B50684CA777CD5B597D0C1F1F144E000B2FE9A535676FC4EA82A84C70902E70A5CEBDFBE19F60DF04FC0A56AFC12855993FF859597F5482A46903E47EABF81B216829706A2F6D8EF7718122FCB3F65782CC3A9C0A8D0D93258C960EE7BEDD15FFED4C0BE">
<FilterHistory/> <FilterHistory/>
</Contents> </Contents>
</Pane-3> </Pane-3>