mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Added performance improvements for cyclic polling by using single frame building of multiple registers
- cleaned and refactored codebase
This commit is contained in:
@@ -7,20 +7,25 @@ using System.Collections;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using System.Collections.Generic;
|
||||
using MewtocolNet.Registers;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace Examples;
|
||||
|
||||
public class ExampleScenarios {
|
||||
|
||||
public static bool MewtocolLoggerEnabled = false;
|
||||
|
||||
public void SetupLogger () {
|
||||
|
||||
//attaching the logger
|
||||
Logger.LogLevel = LogLevel.Verbose;
|
||||
Logger.OnNewLogMessage((date, msg) => {
|
||||
if(MewtocolLoggerEnabled)
|
||||
Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}");
|
||||
Logger.LogLevel = LogLevel.Info;
|
||||
Logger.OnNewLogMessage((date, level, 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) {
|
||||
|
||||
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")]
|
||||
public async Task RunReadTest () {
|
||||
|
||||
Console.WriteLine("Starting auto enums and bitwise");
|
||||
|
||||
//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
|
||||
var builder = RegBuilder.ForInterface(interf);
|
||||
@@ -200,8 +203,9 @@ public class ExampleScenarios {
|
||||
|
||||
var shortReg = builder.FromPlcRegName("DT35").AsPlcType(PlcVarType.INT).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();
|
||||
|
||||
//connect
|
||||
@@ -214,12 +218,20 @@ public class ExampleScenarios {
|
||||
await interf.SetRegisterAsync(r0reg, !(bool)r0reg.Value);
|
||||
await interf.SetRegisterAsync(shortReg, (short)new Random().Next(0, 100));
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using MewtocolNet.Logging;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Examples;
|
||||
|
||||
@@ -55,20 +57,23 @@ class Program {
|
||||
Console.WriteLine("\nEnter a number to excecute a example");
|
||||
Console.ResetColor();
|
||||
|
||||
Console.WriteLine("\nOther possible commands: \n\n" +
|
||||
"'toggle logger' - toggle the built in mewtocol logger on/off\n" +
|
||||
"'exit' - to close this program \n" +
|
||||
"'clear' - to clear the output \n");
|
||||
Console.WriteLine("\nOther possible commands: \n");
|
||||
Console.WriteLine($"'logger <level>' - set loglevel to one of: {string.Join(", ", Enum.GetNames(typeof(LogLevel)).ToList())}");
|
||||
Console.WriteLine("'exit' - to close this program");
|
||||
Console.WriteLine("'clear' - to clear the output");
|
||||
|
||||
|
||||
Console.Write("> ");
|
||||
|
||||
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") {
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MewtocolNet
|
||||
@@ -35,6 +37,7 @@ namespace MewtocolNet
|
||||
internal volatile bool pollerFirstCycle = false;
|
||||
|
||||
internal bool usePoller = false;
|
||||
internal bool pollerUseMultiFrame = false;
|
||||
|
||||
#region Register Polling
|
||||
|
||||
@@ -93,66 +96,140 @@ namespace MewtocolNet
|
||||
|
||||
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;
|
||||
pollerTaskRunning = true;
|
||||
pollerIsPaused = false;
|
||||
if (useMultiFrame) {
|
||||
await OnMultiFrameCycle();
|
||||
} else {
|
||||
await OnSingleFrameCycle();
|
||||
}
|
||||
|
||||
while (!pollerTaskStopped) {
|
||||
}
|
||||
|
||||
while (pollerTaskRunning) {
|
||||
//polls all registers one by one (slow)
|
||||
internal async Task Poll () {
|
||||
|
||||
if (iteration >= Registers.Count + 1) {
|
||||
iteration = 0;
|
||||
//invoke cycle polled event
|
||||
InvokePolledCycleDone();
|
||||
continue;
|
||||
}
|
||||
Logger.Log("Poller is attaching", LogLevel.Info, this);
|
||||
|
||||
if (iteration >= Registers.Count) {
|
||||
await GetPLCInfoAsync();
|
||||
iteration++;
|
||||
continue;
|
||||
}
|
||||
int iteration = 0;
|
||||
|
||||
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);
|
||||
InvokeRegisterChanged(reg);
|
||||
pollerIsPaused = !pollerTaskRunning;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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 Collection
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a register collection object to
|
||||
/// the interface that can be updated automatically.
|
||||
@@ -212,7 +287,7 @@ namespace MewtocolNet
|
||||
RegisterChanged += (reg) => {
|
||||
|
||||
//register is used bitwise
|
||||
if (reg.IsUsedBitwise()) {
|
||||
if (reg.GetType() == typeof(BytesRegister)) {
|
||||
|
||||
for (int i = 0; i < props.Length; i++) {
|
||||
|
||||
@@ -314,8 +389,6 @@ namespace MewtocolNet
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Register Adding
|
||||
|
||||
internal void AddRegister (RegisterBuildInfo buildInfo) {
|
||||
@@ -323,7 +396,7 @@ namespace MewtocolNet
|
||||
var builtRegister = buildInfo.Build();
|
||||
|
||||
//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;
|
||||
|
||||
@@ -335,11 +408,12 @@ namespace MewtocolNet
|
||||
if(CheckDuplicateNameRegister(builtRegister))
|
||||
throw MewtocolException.DupeNameRegister(builtRegister);
|
||||
|
||||
builtRegister.attachedInterface = this;
|
||||
Registers.Add(builtRegister);
|
||||
|
||||
}
|
||||
|
||||
public void AddRegister(IRegister register) {
|
||||
public void AddRegister (BaseRegister register) {
|
||||
|
||||
if (CheckDuplicateRegister(register))
|
||||
throw MewtocolException.DupeRegister(register);
|
||||
@@ -347,29 +421,30 @@ namespace MewtocolNet
|
||||
if (CheckDuplicateNameRegister(register))
|
||||
throw MewtocolException.DupeNameRegister(register);
|
||||
|
||||
register.attachedInterface = this;
|
||||
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
|
||||
|
||||
#region Register Reading
|
||||
@@ -413,9 +469,9 @@ namespace MewtocolNet
|
||||
/// <summary>
|
||||
/// Gets a list of all added registers
|
||||
/// </summary>
|
||||
public List<IRegister> GetAllRegisters() {
|
||||
public IEnumerable<IRegister> GetAllRegisters() {
|
||||
|
||||
return Registers;
|
||||
return Registers.Cast<IRegister>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@ namespace MewtocolNet.Exceptions {
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
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()}");
|
||||
|
||||
}
|
||||
|
||||
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()}");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the register address name as in the plc
|
||||
/// </summary>
|
||||
string PLCAddressName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current value of the register
|
||||
/// </summary>
|
||||
@@ -33,73 +38,6 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
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>
|
||||
/// Builds a readable string with all important register informations
|
||||
/// </summary>
|
||||
@@ -110,6 +48,18 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,13 +5,60 @@ using System.Threading.Tasks;
|
||||
namespace MewtocolNet {
|
||||
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 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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ namespace MewtocolNet.Logging {
|
||||
/// </summary>
|
||||
public static LogLevel LogLevel { get; set; }
|
||||
|
||||
internal static Action<DateTime, string> LogInvoked;
|
||||
internal static Action<DateTime, LogLevel, string> LogInvoked;
|
||||
|
||||
/// <summary>
|
||||
/// Gets invoked whenever a new log message is ready
|
||||
/// </summary>
|
||||
public static void OnNewLogMessage(Action<DateTime, string> onMsg) {
|
||||
public static void OnNewLogMessage(Action<DateTime, LogLevel, string> onMsg) {
|
||||
|
||||
LogInvoked += (t, m) => {
|
||||
onMsg(t, m);
|
||||
LogInvoked += (t, l, m) => {
|
||||
onMsg(t, l, m);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -29,9 +29,9 @@ namespace MewtocolNet.Logging {
|
||||
|
||||
if ((int)loglevel <= (int)LogLevel) {
|
||||
if (sender == null) {
|
||||
LogInvoked?.Invoke(DateTime.Now, message);
|
||||
LogInvoked?.Invoke(DateTime.Now, loglevel, message);
|
||||
} else {
|
||||
LogInvoked?.Invoke(DateTime.Now, $"[{sender.GetConnectionPortInfo()}] {message}");
|
||||
LogInvoked?.Invoke(DateTime.Now, loglevel, $"[{sender.GetConnectionPortInfo()}] {message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -26,7 +27,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Converts a string (after converting to upper case) to ascii bytes
|
||||
/// </summary>
|
||||
internal static byte[] ToHexASCIIBytes(this string _str) {
|
||||
internal static byte[] BytesFromHexASCIIString(this string _str) {
|
||||
|
||||
ASCIIEncoding ascii = new ASCIIEncoding();
|
||||
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) {
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
var stringBytes = _onString.SplitInParts(2).ToList();
|
||||
string val = res.Groups["data"].Value;
|
||||
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)
|
||||
throw new ArgumentNullException(nameof(s));
|
||||
@@ -109,63 +138,7 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
internal static string BuildDTString (this byte[] inBytes, short reservedSize) {
|
||||
|
||||
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) {
|
||||
internal static byte[] HexStringToByteArray (this string hex) {
|
||||
if (hex == null)
|
||||
return null;
|
||||
return Enumerable.Range(0, hex.Length)
|
||||
@@ -174,13 +147,39 @@ namespace MewtocolNet {
|
||||
.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();
|
||||
|
||||
for (int i = 0; i < arr.Length; i++) {
|
||||
byte b = arr[i];
|
||||
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();
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
/// Checks if the register type is boolean
|
||||
/// </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 &&
|
||||
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;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using MewtocolNet.Exceptions;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.Queue;
|
||||
using MewtocolNet.RegisterAttributes;
|
||||
@@ -113,7 +114,9 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// The registered data registers of the PLC
|
||||
/// </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 int port;
|
||||
@@ -208,11 +211,13 @@ namespace MewtocolNet {
|
||||
|
||||
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} " +
|
||||
$"{(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
|
||||
/// polls the registered data registers and writes the values to them
|
||||
/// </summary>
|
||||
public MewtocolInterface WithPoller() {
|
||||
public MewtocolInterface WithPoller(bool useMultiFrame = false) {
|
||||
|
||||
usePoller = true;
|
||||
pollerUseMultiFrame = useMultiFrame;
|
||||
return this;
|
||||
|
||||
}
|
||||
@@ -397,7 +403,7 @@ namespace MewtocolNet {
|
||||
|
||||
for (int i = 0; i < Registers.Count; i++) {
|
||||
|
||||
var reg = Registers[i];
|
||||
var reg = (IRegisterInternal)Registers[i];
|
||||
reg.ClearValue();
|
||||
|
||||
}
|
||||
@@ -423,22 +429,38 @@ namespace MewtocolNet {
|
||||
|
||||
queuedMessages++;
|
||||
|
||||
var response = await queue.Enqueue(() => SendSingleBlock(_msg));
|
||||
TCPMessageResult tcpResult = TCPMessageResult.Waiting;
|
||||
string response = "";
|
||||
|
||||
if (queuedMessages > 0)
|
||||
queuedMessages--;
|
||||
int lineFeedFails = 0;
|
||||
|
||||
//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
|
||||
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) {
|
||||
string eCode = m.Groups[1].Value;
|
||||
string eDes = CodeDescriptions.Error[Convert.ToInt32(eCode)];
|
||||
@@ -456,7 +478,7 @@ namespace MewtocolNet {
|
||||
return new CommandResult {
|
||||
Success = true,
|
||||
Error = "0000",
|
||||
Response = response.ToString()
|
||||
Response = response,
|
||||
};
|
||||
|
||||
} 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) {
|
||||
await ConnectTCP();
|
||||
}
|
||||
|
||||
if (client == null || !client.Connected)
|
||||
return null;
|
||||
return (TCPMessageResult.NotConnected, null);
|
||||
|
||||
var message = _blockString.ToHexASCIIBytes();
|
||||
var message = _blockString.BytesFromHexASCIIString();
|
||||
|
||||
//time measuring
|
||||
if (speedStopwatchUpstr == null) {
|
||||
@@ -542,13 +564,16 @@ namespace MewtocolNet {
|
||||
|
||||
} catch (IOException) {
|
||||
OnMajorSocketExceptionWhileConnected();
|
||||
return null;
|
||||
return (TCPMessageResult.FailedWithException, null);
|
||||
} catch (SocketException) {
|
||||
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);
|
||||
|
||||
@@ -559,10 +584,12 @@ namespace MewtocolNet {
|
||||
if (perSecUpstream <= 10000)
|
||||
BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero);
|
||||
|
||||
return response.ToString();
|
||||
return (TCPMessageResult.Success, resString);
|
||||
|
||||
} else {
|
||||
return null;
|
||||
|
||||
return (TCPMessageResult.FailedLineFeed, null);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -159,10 +159,12 @@ namespace MewtocolNet {
|
||||
|
||||
#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
|
||||
if (_toRead.GetType() == typeof(BoolRegister)) {
|
||||
if (toreadType == typeof(BoolRegister)) {
|
||||
|
||||
string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}";
|
||||
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
|
||||
if (_toRead.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
|
||||
if (toreadType.IsGenericType && _toRead.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
|
||||
|
||||
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
@@ -198,7 +200,7 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
//returns a byte array with variable size
|
||||
if (_toRead.GetType() == typeof(BytesRegister<>)) {
|
||||
if (toreadType == typeof(BytesRegister)) {
|
||||
|
||||
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
@@ -206,7 +208,9 @@ namespace MewtocolNet {
|
||||
if (!result.Success)
|
||||
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
|
||||
if (_toWrite.GetType() == typeof(BoolRegister)) {
|
||||
@@ -235,7 +239,7 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
//returns a byte array with variable size
|
||||
if (_toWrite.GetType() == typeof(BytesRegister<>)) {
|
||||
if (_toWrite.GetType() == typeof(BytesRegister)) {
|
||||
|
||||
//string stationNum = GetStationNumber();
|
||||
//string dataString = gotBytes.BuildDTString(_toWrite.ReservedSize);
|
||||
@@ -259,7 +263,7 @@ namespace MewtocolNet {
|
||||
|
||||
var internalReg = (IRegisterInternal)register;
|
||||
|
||||
return await internalReg.WriteAsync(this, value);
|
||||
return await internalReg.WriteAsync(value);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,52 +17,64 @@ namespace MewtocolNet
|
||||
internal Type dotnetCastType;
|
||||
internal Type collectionType;
|
||||
|
||||
internal IRegister Build () {
|
||||
internal BaseRegister Build () {
|
||||
|
||||
RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault();
|
||||
|
||||
PlcVarType plcType = dotnetCastType.ToPlcVarType();
|
||||
Type registerClassType = plcType.GetDefaultPlcVarType();
|
||||
Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType();
|
||||
|
||||
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
|
||||
|
||||
var type = typeof(NumberRegister<BitArray>);
|
||||
|
||||
var areaAddr = memoryAddress;
|
||||
|
||||
//create a new bregister instance
|
||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
||||
|
||||
//int _adress, string _name = null, bool isBitwise = false, Type _enumType = null
|
||||
var parameters = new object[] { areaAddr, name, true, null };
|
||||
var instance = (IRegister)Activator.CreateInstance(type, flags, null, parameters, null);
|
||||
//int _adress, int _reservedByteSize, string _name = null
|
||||
var parameters = new object[] { memoryAddress, memorySizeBytes, name };
|
||||
var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null);
|
||||
|
||||
if (collectionType != null)
|
||||
((IRegisterInternal)instance).WithCollectionType(collectionType);
|
||||
instance.WithCollectionType(collectionType);
|
||||
|
||||
return instance;
|
||||
|
||||
} else if (regType.IsNumericDTDDT()) {
|
||||
} else if (regType.IsNumericDTDDT() && !isBytesRegister) {
|
||||
|
||||
//-------------------------------------------
|
||||
//as numeric register
|
||||
|
||||
var type = plcType.GetDefaultPlcVarType();
|
||||
|
||||
var areaAddr = memoryAddress;
|
||||
|
||||
//create a new bregister instance
|
||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
||||
|
||||
//int _adress, string _name = null, bool isBitwise = false, Type _enumType = null
|
||||
var parameters = new object[] { areaAddr, name, false, null };
|
||||
var instance = (IRegister)Activator.CreateInstance(type, flags, null, parameters, null);
|
||||
//int _adress, Type _enumType = null, string _name = null
|
||||
var parameters = new object[] { areaAddr, null, name };
|
||||
var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, 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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
}
|
||||
|
||||
@@ -14,10 +14,31 @@ namespace MewtocolNet.RegisterBuilding
|
||||
if (!step.wasCasted)
|
||||
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 isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null;
|
||||
|
||||
//fallbacks if no casting builder was given
|
||||
if (isTypeNotDefined && step.RegType == RegisterType.DT) {
|
||||
|
||||
step.dotnetVarType = typeof(short);
|
||||
@@ -31,35 +52,21 @@ namespace MewtocolNet.RegisterBuilding
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
if(step.plcVarType != null) {
|
||||
if (step.plcVarType != null) {
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
internal string Name;
|
||||
internal RegisterType RegType;
|
||||
internal int MemAddress;
|
||||
internal int MemByteSize;
|
||||
internal byte? SpecialAddress;
|
||||
|
||||
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() {
|
||||
|
||||
switch (RegType) {
|
||||
@@ -77,7 +96,7 @@ namespace MewtocolNet.RegisterBuilding {
|
||||
case RegisterType.DDT:
|
||||
dotnetVarType = typeof(int);
|
||||
break;
|
||||
case RegisterType.DT_RANGE:
|
||||
case RegisterType.DT_BYTE_RANGE:
|
||||
dotnetVarType = typeof(string);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace MewtocolNet {
|
||||
/// <summary>
|
||||
/// Area of a byte sequence longer than 2 words
|
||||
/// </summary>
|
||||
DT_RANGE = 5,
|
||||
DT_BYTE_RANGE = 5,
|
||||
|
||||
}
|
||||
|
||||
|
||||
127
MewtocolNet/Registers/BaseRegister.cs
Normal file
127
MewtocolNet/Registers/BaseRegister.cs
Normal 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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,45 +8,7 @@ namespace MewtocolNet.Registers {
|
||||
/// <summary>
|
||||
/// Defines a register containing a boolean
|
||||
/// </summary>
|
||||
public class BoolRegister : IRegister, IRegisterInternal, INotifyPropertyChanged {
|
||||
|
||||
/// <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;
|
||||
public class BoolRegister : BaseRegister {
|
||||
|
||||
internal byte specialAddress;
|
||||
/// <summary>
|
||||
@@ -77,6 +39,8 @@ namespace MewtocolNet.Registers {
|
||||
if (_spAddress > 0xF)
|
||||
throw new NotSupportedException("Special address cant be greater 15 or 0xF");
|
||||
|
||||
lastValue = false;
|
||||
|
||||
memoryAddress = _areaAdress;
|
||||
specialAddress = _spAddress;
|
||||
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>
|
||||
/// Builds the register area name for the mewtocol protocol
|
||||
/// </summary>
|
||||
public string BuildMewtocolQuery() {
|
||||
lastValue = (bool)val;
|
||||
TriggerChangedEvnt(this);
|
||||
TriggerNotifyChange();
|
||||
|
||||
}
|
||||
|
||||
/// <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])
|
||||
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;
|
||||
TriggerChangedEvnt(this);
|
||||
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() {
|
||||
/// <inheritdoc/>
|
||||
public override string GetRegisterPLCName() {
|
||||
|
||||
var spAdressEnd = SpecialAddress.ToString("X1");
|
||||
|
||||
@@ -159,13 +124,8 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
/// <inheritdoc/>
|
||||
public override string ToString(bool additional) {
|
||||
|
||||
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));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,150 +9,80 @@ namespace MewtocolNet.Registers {
|
||||
/// <summary>
|
||||
/// Defines a register containing a string
|
||||
/// </summary>
|
||||
public class BytesRegister<T> : IRegister, IRegisterInternal {
|
||||
public class BytesRegister : BaseRegister {
|
||||
|
||||
internal int addressLength;
|
||||
/// <summary>
|
||||
/// Gets called whenever the value was changed
|
||||
/// The rgisters memory length
|
||||
/// </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 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;
|
||||
public int AddressLength => addressLength;
|
||||
|
||||
internal short ReservedSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines a register containing a string
|
||||
/// </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;
|
||||
memoryAdress = _adress;
|
||||
ReservedSize = (short)_reservedSize;
|
||||
memoryAddress = _address;
|
||||
ReservedSize = (short)_reservedByteSize;
|
||||
|
||||
//calc mem length
|
||||
var wordsize = (double)_reservedSize / 2;
|
||||
if (wordsize % 2 != 0) {
|
||||
wordsize++;
|
||||
}
|
||||
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
|
||||
var byteSize = _reservedByteSize;
|
||||
if (_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>
|
||||
/// Builds the register identifier for the mewotocol protocol
|
||||
/// </summary>
|
||||
public string BuildMewtocolQuery() {
|
||||
/// <inheritdoc/>
|
||||
public override string BuildMewtocolQuery() {
|
||||
|
||||
StringBuilder asciistring = new StringBuilder("D");
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
internal string BuildCustomIdent(int overwriteWordLength) {
|
||||
/// <inheritdoc/>
|
||||
public override void SetValueFromPLC (object val) {
|
||||
|
||||
if (overwriteWordLength <= 0)
|
||||
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;
|
||||
lastValue = (byte[])val;
|
||||
|
||||
TriggerChangedEvnt(this);
|
||||
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}" : "")}";
|
||||
|
||||
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);
|
||||
SetValueFromPLC(parsed);
|
||||
return parsed;
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -11,131 +12,64 @@ namespace MewtocolNet.Registers {
|
||||
/// Defines a register containing a number
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the numeric value</typeparam>
|
||||
public class NumberRegister<T> : IRegister, IRegisterInternal {
|
||||
|
||||
/// <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; }
|
||||
public class NumberRegister<T> : BaseRegister {
|
||||
|
||||
internal Type enumType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines a register containing a number
|
||||
/// </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>
|
||||
public NumberRegister (int _adress, string _name = null) {
|
||||
public NumberRegister (int _address, 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;
|
||||
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
|
||||
if(memoryLength == 1) {
|
||||
RegisterType = RegisterType.DDT;
|
||||
} else {
|
||||
RegisterType = RegisterType.DT;
|
||||
}
|
||||
Type numType = typeof(T);
|
||||
|
||||
var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
|
||||
if (!allowedTypes.Contains(numType))
|
||||
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");
|
||||
memoryAdress = _adress;
|
||||
if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
|
||||
|
||||
memoryAddress = _address;
|
||||
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 {
|
||||
|
||||
var allowedTypes = PlcValueParser.GetAllowDotnetTypes();
|
||||
if (!allowedTypes.Contains(numType))
|
||||
throw new NotSupportedException($"The type {numType} is not allowed for Number Registers");
|
||||
}
|
||||
|
||||
//set register type
|
||||
if (memoryLength == 1) {
|
||||
RegisterType = RegisterType.DDT;
|
||||
} else {
|
||||
RegisterType = RegisterType.DT;
|
||||
}
|
||||
var areaLen = (Marshal.SizeOf(numType) / 2) - 1;
|
||||
RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT;
|
||||
|
||||
isUsedBitwise = isBitwise;
|
||||
enumType = _enumType;
|
||||
lastValue = default(T);
|
||||
|
||||
}
|
||||
|
||||
public void WithCollectionType(Type colType) => collectionType = colType;
|
||||
|
||||
public void SetValueFromPLC(object val) {
|
||||
/// <inheritdoc/>
|
||||
public override void SetValueFromPLC(object val) {
|
||||
|
||||
lastValue = (T)val;
|
||||
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
|
||||
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>
|
||||
/// Gets the register bitwise if its a 16 or 32 bit int
|
||||
/// </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));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
87
MewtocolNet/Registers/StringRegister.cs
Normal file
87
MewtocolNet/Registers/StringRegister.cs
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
12
MewtocolNet/TCPMessageResult.cs
Normal file
12
MewtocolNet/TCPMessageResult.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace MewtocolNet {
|
||||
internal enum TCPMessageResult {
|
||||
|
||||
Waiting,
|
||||
Success,
|
||||
NotConnected,
|
||||
FailedWithException,
|
||||
FailedLineFeed,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
@@ -19,10 +20,13 @@ namespace MewtocolNet.TypeConversion {
|
||||
{ PlcVarType.TIME, RegisterType.DDT },
|
||||
{ PlcVarType.WORD, RegisterType.DT },
|
||||
{ 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> {
|
||||
|
||||
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;
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -41,9 +41,12 @@ namespace MewtocolNet {
|
||||
public static RegisterType? GetDefaultRegisterType (Type type) =>
|
||||
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();
|
||||
|
||||
public static Type GetDefaultRegisterHoldingType (this Type type) =>
|
||||
conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetHoldingRegisterType();
|
||||
|
||||
public static Type GetDefaultDotnetType (this PlcVarType type) =>
|
||||
conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetDotnetType();
|
||||
|
||||
|
||||
@@ -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.Equal(propName, reg.Name);
|
||||
@@ -115,7 +115,7 @@ namespace MewtocolTests {
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1));
|
||||
|
||||
//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));
|
||||
|
||||
//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));
|
||||
|
||||
//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));
|
||||
|
||||
//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));
|
||||
|
||||
//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));
|
||||
|
||||
//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));
|
||||
|
||||
//test generic properties
|
||||
TestBasicGeneration(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);
|
||||
TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestFloat32), (float)0, 7003, "DDT7003");
|
||||
|
||||
}
|
||||
|
||||
@@ -269,35 +206,25 @@ namespace MewtocolTests {
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime));
|
||||
|
||||
//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")]
|
||||
public void Enum16Gen() {
|
||||
//[Fact(DisplayName = "String generation")]
|
||||
//public void StringGen() {
|
||||
|
||||
var interf = new MewtocolInterface("192.168.0.1");
|
||||
interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
// var interf = new MewtocolInterface("192.168.0.1");
|
||||
// interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller();
|
||||
|
||||
var register = interf.GetRegister(nameof(TestRegisterCollection.TestEnum16));
|
||||
// var register = interf.GetRegister(nameof(TestRegisterCollection.TestString2));
|
||||
|
||||
//test generic properties
|
||||
TestBasicGeneration(register, nameof(TestRegisterCollection.TestEnum16), (short)TestRegisterCollection.CurrentState.Undefined, 50, "DT50");
|
||||
// //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 = "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");
|
||||
|
||||
}
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
20
MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs
Normal file
20
MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs
Normal 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; }
|
||||
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace MewtocolTests {
|
||||
|
||||
}
|
||||
|
||||
[Fact(DisplayName = nameof(MewtocolHelpers.ToHexASCIIBytes))]
|
||||
[Fact(DisplayName = nameof(MewtocolHelpers.BytesFromHexASCIIString))]
|
||||
public void ToHexASCIIBytesGeneration() {
|
||||
|
||||
string test = "Hello, world!";
|
||||
@@ -62,7 +62,7 @@ namespace MewtocolTests {
|
||||
0x4C,
|
||||
0x44,
|
||||
0x21
|
||||
}, test.ToHexASCIIBytes());
|
||||
}, test.BytesFromHexASCIIString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
using MewtocolNet;
|
||||
using MewtocolNet.Logging;
|
||||
using MewtocolNet.RegisterBuilding;
|
||||
using MewtocolNet.Registers;
|
||||
using MewtocolTests.EncapsulatedTests;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace MewtocolTests {
|
||||
namespace MewtocolTests
|
||||
{
|
||||
|
||||
public class TestLivePLC {
|
||||
|
||||
private readonly ITestOutputHelper output;
|
||||
|
||||
private List<ExpectedTestData> testData = new() {
|
||||
private List<ExpectedPlcInformationData> testPlcInformationData = new() {
|
||||
|
||||
new ExpectedTestData {
|
||||
new ExpectedPlcInformationData {
|
||||
|
||||
PLCName = "FPX-H C30T",
|
||||
PLCIP = "192.168.115.210",
|
||||
@@ -20,7 +24,7 @@ namespace MewtocolTests {
|
||||
ProgCapacity = 32,
|
||||
|
||||
},
|
||||
new ExpectedTestData {
|
||||
new ExpectedPlcInformationData {
|
||||
|
||||
PLCName = "FPX-H C14R",
|
||||
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) {
|
||||
|
||||
this.output = output;
|
||||
|
||||
Logger.LogLevel = LogLevel.Verbose;
|
||||
Logger.OnNewLogMessage((d, m) => {
|
||||
Logger.OnNewLogMessage((d, l, m) => {
|
||||
|
||||
output.WriteLine($"Mewtocol Logger: {d} {m}");
|
||||
|
||||
@@ -48,7 +69,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "Connection cycle client to PLC")]
|
||||
public async void TestClientConnection() {
|
||||
|
||||
foreach (var plc in testData) {
|
||||
foreach (var plc in testPlcInformationData) {
|
||||
|
||||
output.WriteLine($"Testing: {plc.PLCName}");
|
||||
|
||||
@@ -69,7 +90,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "Reading basic information from PLC")]
|
||||
public async void TestClientReadPLCStatus() {
|
||||
|
||||
foreach (var plc in testData) {
|
||||
foreach (var plc in testPlcInformationData) {
|
||||
|
||||
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();
|
||||
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -122,20 +122,12 @@ public class TestRegisterBuilder {
|
||||
|
||||
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);
|
||||
|
||||
} catch (Exception ex) {
|
||||
|
||||
output.WriteLine(ex.Message.ToString());
|
||||
|
||||
}
|
||||
output.WriteLine($"{(built?.ToString(true) ?? "null")}\n");
|
||||
Assert.Equivalent(item.Value, built);
|
||||
|
||||
}
|
||||
|
||||
@@ -204,11 +196,9 @@ public class TestRegisterBuilder {
|
||||
[Fact(DisplayName = "Parsing as Bytes Register (Casted)")]
|
||||
public void TestRegisterBuildingByteRangeCasted() {
|
||||
|
||||
var expect = new BytesRegister<byte[]>(303, 5);
|
||||
var expect = new BytesRegister(303, 5);
|
||||
|
||||
|
||||
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build());
|
||||
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType<short>().Build());
|
||||
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsBytes(5).Build());
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -16,13 +16,15 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "Numeric mewtocol query building")]
|
||||
public void NumericRegisterMewtocolIdentifiers() {
|
||||
|
||||
List<IRegister> registers = new List<IRegister> {
|
||||
new NumberRegister<short>(50, _name: null),
|
||||
new NumberRegister<ushort>(50, _name: null),
|
||||
new NumberRegister<int>(50, _name : null),
|
||||
new NumberRegister<uint>(50, _name : null),
|
||||
new NumberRegister<float>(50, _name : null),
|
||||
new NumberRegister<TimeSpan>(50, _name : null),
|
||||
List<IRegisterInternal> registers = new List<IRegisterInternal> {
|
||||
new NumberRegister<short>(50),
|
||||
new NumberRegister<ushort>(50),
|
||||
new NumberRegister<int>(50),
|
||||
new NumberRegister<uint>(50),
|
||||
new NumberRegister<float>(50),
|
||||
new NumberRegister<TimeSpan>(50),
|
||||
new BytesRegister(50, 30),
|
||||
new BytesRegister(50, 31),
|
||||
};
|
||||
|
||||
List<string> expectedIdents = new List<string> {
|
||||
@@ -32,12 +34,14 @@ namespace MewtocolTests {
|
||||
"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
|
||||
for (int i = 0; i < registers.Count; i++) {
|
||||
|
||||
IRegister? reg = registers[i];
|
||||
IRegisterInternal? reg = registers[i];
|
||||
string expect = expectedIdents[i];
|
||||
|
||||
Assert.Equal(expect, reg.BuildMewtocolQuery());
|
||||
@@ -49,7 +53,7 @@ namespace MewtocolTests {
|
||||
[Fact(DisplayName = "PLC register naming convention test")]
|
||||
public void PLCRegisterIdentifiers() {
|
||||
|
||||
List<IRegister> registers = new List<IRegister> {
|
||||
List<IRegisterInternal> registers = new List<IRegisterInternal> {
|
||||
//numeric ones
|
||||
new NumberRegister<short>(50, _name: null),
|
||||
new NumberRegister<ushort>(60, _name : null),
|
||||
@@ -67,7 +71,7 @@ namespace MewtocolTests {
|
||||
new BoolRegister(IOType.Y, 0xC, 75),
|
||||
|
||||
//string
|
||||
new BytesRegister<string>(999, 5),
|
||||
new BytesRegister(999, 5),
|
||||
};
|
||||
|
||||
List<string> expcectedIdents = new List<string> {
|
||||
@@ -96,7 +100,7 @@ namespace MewtocolTests {
|
||||
//test mewtocol idents
|
||||
for (int i = 0; i < registers.Count; i++) {
|
||||
|
||||
IRegister? reg = registers[i];
|
||||
IRegisterInternal? reg = registers[i];
|
||||
string expect = expcectedIdents[i];
|
||||
|
||||
Assert.Equal(expect, reg.GetRegisterPLCName());
|
||||
@@ -134,7 +138,7 @@ namespace MewtocolTests {
|
||||
|
||||
var ex3 = Assert.Throws<NotSupportedException>(() => {
|
||||
|
||||
new BytesRegister<string>(100000, 5);
|
||||
new BytesRegister(100000, 5);
|
||||
|
||||
});
|
||||
|
||||
|
||||
Binary file not shown.
@@ -2,17 +2,17 @@
|
||||
<ProjectConfiguration CompactMode="1">
|
||||
<PaneContents>
|
||||
<Pane-1 PaneBaseID="410" PaneInstanceNumber="1">
|
||||
<Contents BinaryData="B31A0000789CE599D16EDA301486FB1CBB8AD28BDE8D38900252A94449DA4582A64A22BA699A509678D45A6247B133CA5E6AAF383B296D49CB80AA2196768362E03FE7E77C9CF818FE7C383A3A9B108C18C96E118EC8421991240D423621111CA84055BCF00E26C140EDB4F9F51D59D821C1530417035553CFCF4624CE134C57178ACD6062E308DE8B97151B3398E120BE0E121ECC8E2066E8078299AA0C6334C7095FF3C0A0DF696B1D1E9D050C85B72862773CB3C1F5C39C91E7EB4F308860F64CCBFD59F769C013462261EB351FA0EA631AC4395CB300B44ECFE8F62A1674D05FB350ACDF6441AF5AB8BC1946510629DDC1466FBD10BDB7D6A15D35C1490BF50E30746DDD43B17E9389CE0B137120AA3045147D8F61F1ADD9FEDDA8D8019BEC3871F410F829FD568B46D5A2BF4C61830E5B8F3D560628E296CF79E837C2F3C252D19AFC43501B5F65244FE983DA8521C922A528F34075AD91E39AAA3226E1CF328162458805C507E3766E32F8EBB1B745B895D0F6AD89AAF8F09EDBF621651784C4A0147B6C59A8F59EC8B74974391C7BD60B81E22541C68A86F4600C4386082EEF3AC54A481D2C34E2C94A74DE43A6E95A9EB74AE1F22A6B6B2974D03DADCA46CE64625DFBDBDF371E3E859E0EDDD9D5D8B9188EB7EAFC2F37D64A76E138AF0B5A2596DAF098791AA33060701F445ABD784CD3970E90696F48543720D13F364E73F6D994AC8B2A869A26D4580BF13D009CCAD43E7EAFDF970A4D53BDC3C9B47599C8987E57D380546C1ABBB15DC624D817CFC783006A4B05C8B53608EA06E4B18CCF8C7B013A39A9FBD6C6F11852E1F17CD7BEBEFA6A7C6B663A40CC85734499382AEFCE099C1E6BD509AB165840AE29EEB6A8FA81398953D88C835A200A677CB4D5C521503A562F6CFD97AC444FF928D9EB34E41F6BF400DB12D0A5C2E3DB13AB113C16CE13B9663A43AE9B5C33131D156CF61A160ED23740B2DF116A6C1BC544348D83A5F81552BCB6B9F2F3D962B40C6314F25DC9838C0F0FFB1D60F562479A950FE0E1AAEE0DCA906C2EAF717F526C2A86061B5398311895713621DD055BD5E53BA1795F1A3BD5FE5FF56EADFD3F75FE17D00A35F9">
|
||||
<Contents BinaryData="9B1A0000789CE599D16EDA301486FB1CBB8A72D34B92405A904A2B4AD2CE12345512C1A669425EEC516B891D254E297B91BDEEECA4B42594C1AAB258DA1D26FCE7FC9C8FE31C875F1F8E8ECEC68C12CEB229A1882DB4214B5218F13143B8AF9BBA16447738817DBDD316AFEFD802448C4E085EF475433F3F1BB2B84868BE7AA1018E1340117E9097354039CE288C6F60228201842927DF09CE746D1093394DC45A04367B9DB6D111D139E4249A12C4EF44665BE80705672FD71F3144387BA115FEDC87148A8448266CBDE6C3ACFB98C0B8C06B164CA3D3B54FBB350B96D95BB350AEDF64C1AA5BB8BA1D2094E13CDFC34677BD10DDB7D6A15D3721484BF51E302C63DD43B97E9389CE868918CA2A4C484EBEC5B8FCD5ECFE6DD4EC98DBEC78317A0CFC9C7EA745BB6E315CA6B84187ADA71EAB029471ABF702F293D07969A96C4DF1257240AF3356A4F9A3DAC711CB905696B9AFFBEED0F31D5D1BB1E84795407311E1B0FC62C2CE6D86EF9F7A5B865B0941E88E752DC40FC27688737EC9586C56E2802F4BB5D595F9B6892E2E2E363EAE0509CC78D98E018E71C409A3D59E53AEA4D0A35223DFACC5161DE438BE1B04AB04BEA8B1B196C2324F4FEAB2A1371EBB37E1EECF8D06CFA127037F763DF22E07A39DBAF0F3ADBB925D7ADEEB825605E560709C228D490439560A90E384CA2172C09644874624FB07D0B4E09F1CA520D5EC34CDA7B116127700F34429324ED8EDF59482D354EF08366D4B2D364E786A18A652741ADBDAAE6206D504D4560A90EF6E111C1A50C0333135FE15A0E3E3436F6E028FAD149E20F4C1CDF517FB6B33F301E13E9E939CCBC3B24A8D2440996ACD70D3B2E2FF98913C83CD04A405C9F14C0CB6963C022AC569C3D27FC949F6524812E54E42A28B2CA5E88460EC3642C7A545A21A1D5BAD1DAE99512E9774941BE34CC59E201CB06F3487E4690C97F2F9A3BCB6BDF0F3D962B88C6212893B5280B9181A943BBCDA8A4DDF07BC1B692097E301A039CE3846559C6D00F7815477F94E68DE97C65EB5FF53BD5B6BFF439DFF0625CE315E">
|
||||
<FilterHistory/>
|
||||
</Contents>
|
||||
</Pane-1>
|
||||
<Pane-2 PaneBaseID="410" PaneInstanceNumber="4">
|
||||
<Contents BinaryData="480F0000789CED97ED6EDA3014867B07BB052B3740BEF8A8542AA524ED224183928C6ABF901B7B602DB1516206F4EA6727D036860E8658C70FFEE504BFEF797D9E9880FBE5EAEA66C028E12C7F2214B105E8B16C06133E6008773543035132C519EC6AB625AEA76CE1278C8E085E74355DBBBDE9B1749ED16273017C8E339F22BC941F039F729C53983EC24C98F908534E7E109C6BC049C98466A216C6C6B56DE9B670E79093E489203E159D9B42EFCC397B5F7FC510E1FC9D56E4F39633281A22D9B0B12B87A1E618C1748E6B110CDDEE34DB1D2582695CD72294F551114C35C2FDD04128C74571408C4E7D109D63E760A9210469A93E0086A9D73394F55121ECAD102994531891823CA7B87C6AF63F1B4A1CE3A338418AD6C66FEDF7466CAA11E3D50CFFC7848DD733561994BED5BD88BC103A29239547536CA2F0E943CEE6B362AD0E71C27204CA3177B5D0EB05A1AB813E4B7E560D80870887E5C6449C618E7FBD9E6D69B711FAB137D0408C9722F664FC5C6D7633A610A770655456115F955E664776FFC8220EBF795BEB4194C19C97A733C2294E3861B4FA0A2A2BA90CA8D4C89B8AB93850AE1B7A51B4E910EA357FD368B7544D2F180CBCC778FFBABEF3E63B72C2F1433FB873FA7B75F1F7A1B791DD05C16E41A302F489A0CC3303655C40ED06659D1BA8FB0BA9DDA4EC7323A51BCE85D5644CD6AC945DEE01D4DE7E959D148F1B5BCDB3A2A3CEE773E0A0351DF72FF15856EB1FF311805A6705686B44A72304FC42FE9EF46981738E51E52317ED0270082435E589D09C96C641B3FFD3BC1BB53FCDB7BF01DF64C269">
|
||||
<Contents BinaryData="56170000789CED985D4FE2401486FD177B3BE95E7827FD023111930A559B8035D32EC61863C6CE2C4CB69D21EDB0C8FEFA9DB6205250C0156C36DC904EE979CF3BE7E9994E0BBF1D1C9C7638A382C7B794613E024D1E0D50203A1C9386A229C00BFA24420DC534E4719F8F9C80B32E25A386A22A67A74D1E0E23964C0F802348E4304C9ED3BF81C30489190AAF5124C51C4C98A03F2989156085B4C7223996C2DA8969A8A6541748D0E09662D19799AB32DE1A0AFE7A7C451026F1AB58E9CF7E1E209910A7092BCB7C68451F5D140EC99C054D35EBD5E37AC182AE9DCC59C8C61FB2A0172D5CDC5818C72449D6B0519F2F44FDA375308A2624E9347A0D18BA3AEF211B7FC884B96022446915BA34A14F21C9EE9AD5F746C18EF6961D37C413E159FA9516AB458BFE7840BED061E5A5C772814C373FE7D13F94F5324B596BCA49240EBB8CF970904CA22109788C4156E68602EDA60B5B0A68F3E0579E00D8980A944D4CDAB989C9EF97DE4EE5A6818E6F7714E0936769BBF7F8944F765A26484234D672294F8C332DBD9E667F4BC2873FEC85EB8117A15864DDE99190048272962F41D9288D74591A939E2C88CB866AB5A0ED79D30C509DD3D7B5E35A31A6E9763AF6B5BFFABAB635D3ED5AF0F1B2ED9E5BED9571FEDD8D3D0D3B77DDE501951CD00E41E92503A5ED412D0765940DD4C59ED4725266D948A99AB567D57BA413568559AE0074BC88F353F1B47CA35A2A3AC5FAEC060E9ED0696D88C7306A5BE62301D54A0568A144BB212472403E8DC8460BDC77BDAFA99191C89F64FBA8EAA542E53B1DFB0B5025392A4FC4F2856013588757240CF9E1B6573CB35CDB71CF87CEF5E57DF5614BA8C0154A9A7D1ACA77FCAC6AEFA143A3F3B12010B1DE9A5D5674FC49A84A84C782D0BA03F7EAD191663E00F702DC66855FA2302DF2BFB072922E8DC510856F91FA6FA0AC85E0BD86A8CCEAFD094D32FBD0F15E87C09A6A6DF4F8D9FEFEBAE8685768B6BE9996BD907E5D725842624170AEB36F8B956D31F709FDEC2FBD3D3AC7">
|
||||
<FilterHistory/>
|
||||
</Contents>
|
||||
</Pane-2>
|
||||
<Pane-3 PaneBaseID="410" PaneInstanceNumber="5">
|
||||
<Contents BinaryData="0A070000789CC595D16E9B3014867BB5BD86C50B2410B252A9A94413D6218512014BB5ABCAC5678935B023E32C499F7EB6296DA1E912659576C731FEFFF3F91C1FF8F4F9ECEC32E28C4A2EEE28237C83C6BC5CE15C469CC0C8B22D94E64B28F1C87207EA79C93761CED99CC26664F5ADABCB312FD625AB9A07144A28434660AB5FA39049100C17B7B85466210126E94F0AC2427E4117AC54B132B62FDC41DF55EE124B9ADF5122972AF350E9FDB5E4AFE36F800988575AC5176C575825243A616F1F87DDE598E3620D2D04BBEF7AC373AF83E0D8172D04139F84E07411BECE7C420454D511185EBB10DEA97518742154A7B5FA886638FD3683894F8270DF40145857614E2BFA5080B93587EF4607C77E0F272EC893F14BFA8388C32E62B65BC17F24EC3DCF586D607CEBB5943E52B630486634D521AA90DD08BE5E554FEA04722E0832651E5949308E938985A63CFF55274001A1129B83299C9980DFCFB3ADED1A619805918532D82AECC5FD437DD8A64C091478E7D456A9DC192FC7D3D9DFB3C892EFC19BFD282DB190663A5328209794B3FA136422AD8C99D6E8C58EB91AA8C92409D2B4C990D82D7FC73EFFD2D58CE3280A6EB3C3FBA6FE8BEFDC4FEE6FA6F1B53F3DA8CB7ECC8246761DC7FB05BDBA41FFD0281456FA4684AC022181D43E7AD3BEF21FD3A22EE507F5E563BB7154EDFF56EF5EEBB777F507221839D5">
|
||||
<Contents BinaryData="C3150000789CED98DF4EE24014C67D89BD9F742FBC5BFA0FC5444C2A546D02D6B45D8C31C68C9D5998EC7486B4C322FB72FB6A3B9D824A414157B4D970437A4ACF77BE39BF9E49DB3F5F76760EBB9C11C1D34BC2101F83164F8630165D8E7053333410C6039CC0A6665BF278C0C75ECC598FE07153D3B5A3C316A7A38465B303E0099C780CE1FBFC6FE031815306E9394CA498873013E407C1A9061C4AFA2C91B114360E6C4BB7A5BA8082C497048981AC5C97F9CE48F0A7F1198608A74F72A53FF77E0865419417AC2DF361947DF4201DE1390B866E37EAFB8D9205D33898B3A0E2375930CB164E2E1C84529C656BD868CC37A2F1D63E58651392749EBD060C539FF7A0E23799B0174C509877A147327247B1BA6B56DF1B253BC673767C8AA6C28FE5575AAC972D469321FE4487B587192B04946E712E24BF09EB2B4B6A34E522328F9DA67C34CCA6D9018E798A806A73530BDC961FB435D0E1F1CFA200701111502D4CDAB948F1AF87D9CEE566895EE4763510E17B69BB7F7B572C76D6A6005338310AA9504C9496D9C8AB3F271105DFDD85EB4198C054A8E90C31C5B1209C155B908AF24C9FE539F9C992B81CA8763B70C3705621D0E7F44D637FAF9CD3F2BB5DF73C5A7D5DC779D4ED39C1ED69C73F763A2BF3A2AB0B779676ECFBCB136A05A00F0465560C94B105B51C94553550275B52CB49D95523A51BCE9655FF964C599556B902D0FE22CE77C5D38EAC7AA5E894FBF33170D0944EFB95782C6B6FC37C24A0BD4A015A68D1C7101205A08824F8551BDC577360E88995C99F6CF3A81A954215795DF713506505AA50A4F285E035B076CF30A57C77D33B9E5DADC7F1300ABCF3D3EBFACD8650813398B50684CA777CD5B597D0C1F1F144E000B2FE9A535676FC4EA82A84C70902E70A5CEBDFBE19F60DF04FC0A56AFC12855993FF859597F5482A46903E47EABF81B216829706A2F6D8EF7718122FCB3F65782CC3A9C0A8D0D93258C960EE7BEDD15FFED4C0BE">
|
||||
<FilterHistory/>
|
||||
</Contents>
|
||||
</Pane-3>
|
||||
|
||||
Reference in New Issue
Block a user