Further implementation

- added new parsing method
This commit is contained in:
Felix Weiß
2023-06-26 19:13:04 +02:00
parent b48f86d23d
commit 7be52efb7e
36 changed files with 1181 additions and 1004 deletions

View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using System.Collections;
using MewtocolNet.RegisterBuilding;
using System.Collections.Generic;
using MewtocolNet.Registers;
namespace Examples;
@@ -46,7 +47,7 @@ public class ExampleScenarios {
while (interf.IsConnected) {
//flip the bool register each tick and wait for it to be registered
await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1);
//await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1);
Console.Title = $"Polling Paused: {interf.PollingPaused}, " +
$"Poller active: {interf.PollerActive}, " +
@@ -167,7 +168,7 @@ public class ExampleScenarios {
await interf.ConnectAsync();
//use the async method to make sure the cycling is stopped
await interf.SetRegisterAsync(nameof(registers.StartCyclePLC), false);
//await interf.SetRegisterAsync(nameof(registers.StartCyclePLC), false);
await Task.Delay(5000);
@@ -182,4 +183,49 @@ 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();
//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("R101A").Build();
var shortReg = builder.FromPlcRegName("DT35").AsPlcType(PlcVarType.INT).Build();
builder.FromPlcRegName("DDT36").AsPlcType(PlcVarType.DINT).Build();
//builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build();
//builder.FromPlcRegName("DT40").AsPlcType(PlcVarType.STRING).Build();
//connect
await interf.ConnectAsync();
//var res = await interf.SendCommandAsync("%01#RCSR000F");
while(true) {
await interf.SetRegisterAsync(r0reg, !(bool)r0reg.Value);
await interf.SetRegisterAsync(shortReg, (short)new Random().Next(0, 100));
foreach (var reg in interf.Registers) {
Console.WriteLine($"Register {reg.GetRegisterPLCName()} val: {reg.Value}");
}
Console.WriteLine();
await Task.Delay(1000);
}
}
}

View File

@@ -14,9 +14,6 @@ class Program {
static void Main(string[] args) {
RegBuilder.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build();
var res = RegBuilder.FromPlcRegName("DT100").AsPlcType(PlcVarType.INT).Build();
AppDomain.CurrentDomain.UnhandledException += (s,e) => {
Console.WriteLine(e.ExceptionObject.ToString());
};

View File

@@ -47,10 +47,10 @@ namespace Examples {
public BitArray TestBitRegister { get; private set; }
//corresponds to a DT1204 as a 16bit word/int takes the bit at index 9 and writes it back as a boolean
[Register(1204, 9, BitCount.B16)]
[Register(1204, BitCount.B16, 9)]
public bool BitValue { get; private set; }
[Register(1204, 5, BitCount.B16)]
[Register(1204, BitCount.B16, 5)]
public bool FillTest { get; private set; }
//corresponds to a DT7012 - DT7013 as a 32bit time value that gets parsed as a timespan (TIME)

View File

@@ -53,52 +53,52 @@ namespace Examples {
//you can also extract single bits from DT503
[Register(503, 0, BitCount.B16)]
[Register(503, BitCount.B16, 0)]
public bool BitValue0 { get; private set; }
[Register(503, 1, BitCount.B16)]
[Register(503, BitCount.B16, 1)]
public bool BitValue1 { get; private set; }
[Register(503, 2, BitCount.B16)]
[Register(503, BitCount.B16, 2)]
public bool BitValue2 { get; private set; }
[Register(503, 3, BitCount.B16)]
[Register(503, BitCount.B16, 3)]
public bool BitValue3 { get; private set; }
[Register(503, 4, BitCount.B16)]
[Register(503, BitCount.B16, 4)]
public bool BitValue4 { get; private set; }
[Register(503, 5, BitCount.B16)]
[Register(503, BitCount.B16, 5)]
public bool BitValue5 { get; private set; }
[Register(503, 6, BitCount.B16)]
[Register(503, BitCount.B16, 6)]
public bool BitValue6 { get; private set; }
[Register(503, 7, BitCount.B16)]
[Register(503, BitCount.B16, 7)]
public bool BitValue7 { get; private set; }
[Register(503, 8, BitCount.B16)]
[Register(503, BitCount.B16, 8)]
public bool BitValue8 { get; private set; }
[Register(503, 9, BitCount.B16)]
[Register(503, BitCount.B16, 9)]
public bool BitValue9 { get; private set; }
[Register(503, 10, BitCount.B16)]
[Register(503, BitCount.B16, 10)]
public bool BitValue10 { get; private set; }
[Register(503, 11, BitCount.B16)]
[Register(503, BitCount.B16, 11)]
public bool BitValue11 { get; private set; }
[Register(503, 12, BitCount.B16)]
[Register(503, BitCount.B16, 12)]
public bool BitValue12 { get; private set; }
[Register(503, 13, BitCount.B16)]
[Register(503, BitCount.B16, 13)]
public bool BitValue13 { get; private set; }
[Register(503, 14, BitCount.B16)]
[Register(503, BitCount.B16, 14)]
public bool BitValue14 { get; private set; }
[Register(503, 15, BitCount.B16)]
[Register(503, BitCount.B16, 15)]
public bool BitValue15 { get; private set; }
}

View File

@@ -1,12 +1,16 @@
using MewtocolNet.Logging;
using MewtocolNet.Exceptions;
using MewtocolNet.Logging;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace MewtocolNet {
namespace MewtocolNet
{
/// <summary>
/// The PLC com interface class
@@ -118,61 +122,21 @@ namespace MewtocolNet {
var reg = Registers[iteration];
if (reg is NRegister<short> shortReg) {
var lastVal = shortReg.Value;
var readout = (await ReadNumRegister(shortReg)).Register.Value;
if(reg.IsAllowedRegisterGenericType()) {
var lastVal = reg.Value;
var rwReg = (IRegisterInternal)reg;
var readout = await rwReg.ReadAsync(this);
if (lastVal != readout) {
InvokeRegisterChanged(shortReg);
}
}
if (reg is NRegister<ushort> ushortReg) {
var lastVal = ushortReg.Value;
var readout = (await ReadNumRegister(ushortReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(ushortReg);
}
}
if (reg is NRegister<int> intReg) {
var lastVal = intReg.Value;
var readout = (await ReadNumRegister(intReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(intReg);
}
}
if (reg is NRegister<uint> uintReg) {
var lastVal = uintReg.Value;
var readout = (await ReadNumRegister(uintReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(uintReg);
}
}
if (reg is NRegister<float> floatReg) {
var lastVal = floatReg.Value;
var readout = (await ReadNumRegister(floatReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(floatReg);
}
}
if (reg is NRegister<TimeSpan> tsReg) {
var lastVal = tsReg.Value;
var readout = (await ReadNumRegister(tsReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(tsReg);
}
}
if (reg is BRegister boolReg) {
var lastVal = boolReg.Value;
var readout = (await ReadBoolRegister(boolReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(boolReg);
}
}
if (reg is SRegister stringReg) {
var lastVal = stringReg.Value;
var readout = (await ReadStringRegister(stringReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(stringReg);
rwReg.SetValueFromPLC(readout);
InvokeRegisterChanged(reg);
}
}
iteration++;
@@ -194,67 +158,218 @@ namespace MewtocolNet {
internal void PropertyRegisterWasSet(string propName, object value) {
SetRegister(propName, value);
_ = SetRegisterAsync(GetRegister(propName), value);
}
#endregion
#region Register Colleciton adding
#region Register Collection
/// <summary>
/// Attaches a register collection object to
/// the interface that can be updated automatically.
/// <para/>
/// Just create a class inheriting from <see cref="RegisterCollectionBase"/>
/// and assert some propertys with the custom <see cref="RegisterAttribute"/>.
/// </summary>
/// <param name="collection">A collection inherting the <see cref="RegisterCollectionBase"/> class</param>
public MewtocolInterface WithRegisterCollection(RegisterCollectionBase collection) {
collection.PLCInterface = this;
var props = collection.GetType().GetProperties();
foreach (var prop in props) {
var attributes = prop.GetCustomAttributes(true);
string propName = prop.Name;
foreach (var attr in attributes) {
if (attr is RegisterAttribute cAttribute && prop.PropertyType.IsAllowedPlcCastingType()) {
var dotnetType = prop.PropertyType;
AddRegister(new RegisterBuildInfo {
memoryAddress = cAttribute.MemoryArea,
specialAddress = cAttribute.SpecialAddress,
memorySizeBytes = cAttribute.ByteLength,
registerType = cAttribute.RegisterType,
dotnetCastType = dotnetType,
collectionType = collection.GetType(),
name = prop.Name,
});
}
}
}
RegisterChanged += (reg) => {
//register is used bitwise
if (reg.IsUsedBitwise()) {
for (int i = 0; i < props.Length; i++) {
var prop = props[i];
var bitWiseFound = prop.GetCustomAttributes(true)
.FirstOrDefault(y => y.GetType() == typeof(RegisterAttribute) && ((RegisterAttribute)y).MemoryArea == reg.MemoryAddress);
if (bitWiseFound != null) {
var casted = (RegisterAttribute)bitWiseFound;
var bitIndex = casted.AssignedBitIndex;
BitArray bitAr = null;
if (reg is NumberRegister<short> reg16) {
var bytes = BitConverter.GetBytes((short)reg16.Value);
bitAr = new BitArray(bytes);
} else if (reg is NumberRegister<int> reg32) {
var bytes = BitConverter.GetBytes((int)reg32.Value);
bitAr = new BitArray(bytes);
}
if (bitAr != null && bitIndex < bitAr.Length && bitIndex >= 0) {
//set the specific bit index if needed
prop.SetValue(collection, bitAr[bitIndex]);
collection.TriggerPropertyChanged(prop.Name);
} else if (bitAr != null) {
//set the specific bit array if needed
prop.SetValue(collection, bitAr);
collection.TriggerPropertyChanged(prop.Name);
}
}
}
}
//updating normal properties
var foundToUpdate = props.FirstOrDefault(x => x.Name == reg.Name);
if (foundToUpdate != null) {
var foundAttributes = foundToUpdate.GetCustomAttributes(true);
var foundAttr = foundAttributes.FirstOrDefault(x => x.GetType() == typeof(RegisterAttribute));
if (foundAttr == null)
return;
var registerAttr = (RegisterAttribute)foundAttr;
//check if bit parse mode
if (registerAttr.AssignedBitIndex == -1) {
HashSet<Type> NumericTypes = new HashSet<Type> {
typeof(bool),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(float),
typeof(TimeSpan),
typeof(string)
};
var regValue = ((IRegister)reg).Value;
if (NumericTypes.Any(x => foundToUpdate.PropertyType == x)) {
foundToUpdate.SetValue(collection, regValue);
}
if (foundToUpdate.PropertyType.IsEnum) {
foundToUpdate.SetValue(collection, regValue);
}
}
collection.TriggerPropertyChanged(foundToUpdate.Name);
}
};
if (collection != null)
collection.OnInterfaceLinked(this);
Connected += (i) => {
if (collection != null)
collection.OnInterfaceLinkedAndOnline(this);
};
return this;
}
#endregion
#endregion
#region Register Adding
//Internal register adding for auto register collection building
internal void AddRegister<T>(Type _colType, int _address, PropertyInfo boundProp, int _length = 1, bool _isBitwise = false, Type _enumType = null) {
internal void AddRegister (RegisterBuildInfo buildInfo) {
Type regType = typeof(T);
var builtRegister = buildInfo.Build();
if (regType != typeof(string) && _length != 1) {
throw new NotSupportedException($"_lenght parameter only allowed for register of type string");
}
//is bitwise and the register list already contains that area register
if(builtRegister.IsUsedBitwise() && CheckDuplicateRegister(builtRegister, out var existing)) {
if (Registers.Any(x => x.MemoryAddress == _address) && _isBitwise) {
return;
}
IRegister reg = null;
if (CheckDuplicateRegister(builtRegister))
throw MewtocolException.DupeRegister(builtRegister);
string propName = boundProp.Name;
if(CheckDuplicateNameRegister(builtRegister))
throw MewtocolException.DupeNameRegister(builtRegister);
//rename the property name to prevent duplicate names in case of a bitwise prop
if (_isBitwise && regType == typeof(short))
propName = $"Auto_Bitwise_DT{_address}";
Registers.Add(builtRegister);
if (_isBitwise && regType == typeof(int))
propName = $"Auto_Bitwise_DDT{_address}";
if (regType == typeof(short)) {
reg = new NRegister<short>(_address, propName, _isBitwise, _enumType).WithCollectionType(_colType);
} else if (regType == typeof(ushort)) {
reg = new NRegister<ushort>(_address, propName).WithCollectionType(_colType);
} else if (regType == typeof(int)) {
reg = new NRegister<int>(_address, propName, _isBitwise, _enumType).WithCollectionType(_colType);
} else if (regType == typeof(uint)) {
reg = new NRegister<uint>(_address, propName).WithCollectionType(_colType);
} else if (regType == typeof(float)) {
reg = new NRegister<float>(_address, propName).WithCollectionType(_colType);
} else if (regType == typeof(string)) {
reg = new SRegister(_address, _length, propName).WithCollectionType(_colType);
} else if (regType == typeof(TimeSpan)) {
reg = new NRegister<TimeSpan>(_address, propName).WithCollectionType(_colType);
} else if (regType == typeof(bool)) {
reg = new BRegister(IOType.R, 0x0, _address, propName).WithCollectionType(_colType);
}
if (reg == null) {
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
$"Allowed are: short, ushort, int, uint, float and string");
public void AddRegister(IRegister register) {
if (CheckDuplicateRegister(register))
throw MewtocolException.DupeRegister(register);
if (CheckDuplicateNameRegister(register))
throw MewtocolException.DupeNameRegister(register);
Registers.Add(register);
}
if (Registers.Any(x => x.GetRegisterPLCName() == reg.GetRegisterPLCName()) && !_isBitwise) {
throw new NotSupportedException($"Cannot add a register multiple times, " +
$"make sure that all register attributes or AddRegister assignments have different adresses.");
private bool CheckDuplicateRegister (IRegister instance, out IRegister foundDupe) {
foundDupe = Registers.FirstOrDefault(x => x.CompareIsDuplicate(instance));
return Registers.Contains(instance) || foundDupe != null;
}
Registers.Add(reg);
private bool CheckDuplicateRegister(IRegister instance) {
var foundDupe = Registers.FirstOrDefault(x => x.CompareIsDuplicate(instance));
return Registers.Contains(instance) || foundDupe != null;
}
private bool CheckDuplicateNameRegister(IRegister instance) {
return Registers.Any(x => x.CompareIsNameDuplicate(instance));
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.Exceptions {
[Serializable]
public class MewtocolException : Exception {
public MewtocolException() { }
public MewtocolException(string message) : base(message) { }
public MewtocolException(string message, Exception inner) : base(message, inner) { }
protected MewtocolException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
public static MewtocolException DupeRegister (IRegister register) {
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetRegisterPLCName()}");
}
public static MewtocolException DupeNameRegister (IRegister register) {
return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.Name}");
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
namespace MewtocolNet {

View File

@@ -0,0 +1,18 @@
using MewtocolNet.Registers;
using System;
using System.Threading.Tasks;
namespace MewtocolNet {
internal interface IRegisterInternal {
void WithCollectionType(Type colType);
void SetValueFromPLC(object value);
Task<object> ReadAsync(MewtocolInterface interf);
Task<bool> WriteAsync(MewtocolInterface interf, object data);
}
}

View File

@@ -109,17 +109,17 @@ namespace MewtocolNet {
}
internal static string BuildDTString(this string _inString, short _stringReservedSize) {
internal static string BuildDTString (this byte[] inBytes, short reservedSize) {
StringBuilder sb = new StringBuilder();
//clamp string lenght
if (_inString.Length > _stringReservedSize) {
_inString = _inString.Substring(0, _stringReservedSize);
if (inBytes.Length > reservedSize) {
inBytes = inBytes.Take(reservedSize).ToArray();
}
//actual string content
var hexstring = _inString.GetAsciiHexFromString();
var hexstring = inBytes.ToHexString();
var sizeBytes = BitConverter.GetBytes((short)(hexstring.Length / 2)).ToHexString();
@@ -133,7 +133,7 @@ namespace MewtocolNet {
}
var reservedSizeBytes = BitConverter.GetBytes(_stringReservedSize).ToHexString();
var reservedSizeBytes = BitConverter.GetBytes(reservedSize).ToHexString();
//reserved string count bytes
sb.Append(reservedSizeBytes);
@@ -159,8 +159,10 @@ namespace MewtocolNet {
}
internal static string GetAsciiHexFromString(this string input) {
var bytes = new ASCIIEncoding().GetBytes(input);
return bytes.ToHexString();
}
internal static byte[] HexStringToByteArray(this string hex) {
@@ -265,6 +267,22 @@ namespace MewtocolNet {
}
internal static bool CompareIsDuplicate (this IRegister reg1, IRegister compare) {
bool valCompare = reg1.RegisterType == compare.RegisterType &&
reg1.MemoryAddress == compare.MemoryAddress &&
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
return valCompare;
}
internal static bool CompareIsNameDuplicate(this IRegister reg1, IRegister compare) {
return ( reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name;
}
}
}

View File

@@ -308,7 +308,6 @@ namespace MewtocolNet {
public MewtocolInterface WithPoller() {
usePoller = true;
return this;
}
@@ -407,301 +406,10 @@ namespace MewtocolNet {
#endregion
#region Register Collection
/// <summary>
/// Attaches a register collection object to
/// the interface that can be updated automatically.
/// <para/>
/// Just create a class inheriting from <see cref="RegisterCollectionBase"/>
/// and assert some propertys with the custom <see cref="RegisterAttribute"/>.
/// </summary>
/// <param name="collection">A collection inherting the <see cref="RegisterCollectionBase"/> class</param>
public MewtocolInterface WithRegisterCollection(RegisterCollectionBase collection) {
collection.PLCInterface = this;
var props = collection.GetType().GetProperties();
foreach (var prop in props) {
var attributes = prop.GetCustomAttributes(true);
string propName = prop.Name;
foreach (var attr in attributes) {
if (attr is RegisterAttribute cAttribute) {
if (prop.PropertyType == typeof(bool) && cAttribute.AssignedBitIndex == -1) {
//add bool register non bit assgined
Registers.Add(new BRegister((IOType)(int)cAttribute.RegisterType, cAttribute.SpecialAddress, cAttribute.MemoryArea, _name: propName).WithCollectionType(collection.GetType()));
}
if (prop.PropertyType == typeof(short)) {
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(ushort)) {
AddRegister<ushort>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(int)) {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(uint)) {
AddRegister<uint>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(float)) {
AddRegister<float>(collection.GetType(), cAttribute.MemoryArea, prop);
}
if (prop.PropertyType == typeof(string)) {
AddRegister<string>(collection.GetType(), cAttribute.MemoryArea, prop, cAttribute.StringLength);
}
if (prop.PropertyType.IsEnum) {
if (cAttribute.BitCount == BitCount.B16) {
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, prop, _enumType: prop.PropertyType);
} else {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, prop, _enumType: prop.PropertyType);
}
}
//read number as bit array
if (prop.PropertyType == typeof(BitArray)) {
if (cAttribute.BitCount == BitCount.B16) {
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, prop, _isBitwise: true);
} else {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, prop, _isBitwise: true);
}
}
//read number as bit array by invdividual properties
if (prop.PropertyType == typeof(bool) && cAttribute.AssignedBitIndex != -1) {
//var bitwiseCount = Registers.Count(x => x.Value.isUsedBitwise);
if (cAttribute.BitCount == BitCount.B16) {
AddRegister<short>(collection.GetType(), cAttribute.MemoryArea, prop, _isBitwise: true);
} else {
AddRegister<int>(collection.GetType(), cAttribute.MemoryArea, prop, _isBitwise: true);
}
}
if (prop.PropertyType == typeof(TimeSpan)) {
AddRegister<TimeSpan>(collection.GetType(), cAttribute.MemoryArea, prop);
}
}
}
}
RegisterChanged += (reg) => {
//register is used bitwise
if (reg.IsUsedBitwise()) {
for (int i = 0; i < props.Length; i++) {
var prop = props[i];
var bitWiseFound = prop.GetCustomAttributes(true)
.FirstOrDefault(y => y.GetType() == typeof(RegisterAttribute) && ((RegisterAttribute)y).MemoryArea == reg.MemoryAddress);
if (bitWiseFound != null) {
var casted = (RegisterAttribute)bitWiseFound;
var bitIndex = casted.AssignedBitIndex;
BitArray bitAr = null;
if (reg is NRegister<short> reg16) {
var bytes = BitConverter.GetBytes((short)reg16.Value);
bitAr = new BitArray(bytes);
} else if (reg is NRegister<int> reg32) {
var bytes = BitConverter.GetBytes((int)reg32.Value);
bitAr = new BitArray(bytes);
}
if (bitAr != null && bitIndex < bitAr.Length && bitIndex >= 0) {
//set the specific bit index if needed
prop.SetValue(collection, bitAr[bitIndex]);
collection.TriggerPropertyChanged(prop.Name);
} else if (bitAr != null) {
//set the specific bit array if needed
prop.SetValue(collection, bitAr);
collection.TriggerPropertyChanged(prop.Name);
}
}
}
}
//updating normal properties
var foundToUpdate = props.FirstOrDefault(x => x.Name == reg.Name);
if (foundToUpdate != null) {
var foundAttributes = foundToUpdate.GetCustomAttributes(true);
var foundAttr = foundAttributes.FirstOrDefault(x => x.GetType() == typeof(RegisterAttribute));
if (foundAttr == null)
return;
var registerAttr = (RegisterAttribute)foundAttr;
//check if bit parse mode
if (registerAttr.AssignedBitIndex == -1) {
HashSet<Type> NumericTypes = new HashSet<Type> {
typeof(bool),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(float),
typeof(TimeSpan),
typeof(string)
};
var regValue = ((IRegister)reg).Value;
if (NumericTypes.Any(x => foundToUpdate.PropertyType == x)) {
foundToUpdate.SetValue(collection, regValue);
}
if (foundToUpdate.PropertyType.IsEnum) {
foundToUpdate.SetValue(collection, regValue);
}
}
collection.TriggerPropertyChanged(foundToUpdate.Name);
}
};
if (collection != null)
collection.OnInterfaceLinked(this);
Connected += (i) => {
if (collection != null)
collection.OnInterfaceLinkedAndOnline(this);
};
return this;
}
#endregion
#region Register Writing
/// <summary>
/// Sets a register in the PLCs memory
/// </summary>
/// <param name="registerName">The name the register was given to or a property name from the RegisterCollection class</param>
/// <param name="value">The value to write to the register</param>
public void SetRegister(string registerName, object value) {
var foundRegister = GetAllRegisters().FirstOrDefault(x => x.Name == registerName);
if (foundRegister == null) {
throw new Exception($"Register with the name {registerName} was not found");
}
_ = SetRegisterAsync(registerName, value);
}
/// <summary>
/// Sets a register in the PLCs memory asynchronously, returns the result status from the PLC
/// </summary>
/// <param name="registerName">The name the register was given to or a property name from the RegisterCollection class</param>
/// <param name="value">The value to write to the register</param>
public async Task<bool> SetRegisterAsync(string registerName, object value) {
var foundRegister = GetAllRegisters().FirstOrDefault(x => x.Name == registerName);
if (foundRegister == null) {
throw new Exception($"Register with the name {registerName} was not found");
}
if (foundRegister.GetType() == typeof(BRegister)) {
return await WriteBoolRegister((BRegister)foundRegister, (bool)value);
}
if (foundRegister.GetType() == typeof(NRegister<short>)) {
return await WriteNumRegister((NRegister<short>)foundRegister, (short)value);
}
if (foundRegister.GetType() == typeof(NRegister<ushort>)) {
return await WriteNumRegister((NRegister<ushort>)foundRegister, (ushort)value);
}
if (foundRegister.GetType() == typeof(NRegister<int>)) {
return await WriteNumRegister((NRegister<int>)foundRegister, (int)value);
}
if (foundRegister.GetType() == typeof(NRegister<uint>)) {
return await WriteNumRegister((NRegister<uint>)foundRegister, (uint)value);
}
if (foundRegister.GetType() == typeof(NRegister<float>)) {
return await WriteNumRegister((NRegister<float>)foundRegister, (float)value);
}
if (foundRegister.GetType() == typeof(NRegister<TimeSpan>)) {
return await WriteNumRegister((NRegister<TimeSpan>)foundRegister, (TimeSpan)value);
}
if (foundRegister.GetType() == typeof(SRegister)) {
return await WriteStringRegister((SRegister)foundRegister, (string)value);
}
return false;
}
#endregion
#region Low level command handling
/// <summary>
/// Calculates checksum and sends a command to the PLC then awaits results
/// Calculates the checksum automatically and sends a command to the PLC then awaits results
/// </summary>
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
/// <returns>Returns the result</returns>

View File

@@ -4,7 +4,9 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace MewtocolNet {
@@ -155,244 +157,110 @@ namespace MewtocolNet {
#endregion
#region Bool register reading / writing
#region Raw register reading / writing
/// <summary>
/// Reads the given boolean register from the PLC
/// </summary>
/// <param name="_toRead">The register to read</param>
public async Task<BRegisterResult> ReadBoolRegister(BRegister _toRead) {
internal async Task<byte[]> ReadRawRegisterAsync (IRegister _toRead) {
//returns a byte array 1 long and with the byte beeing 0 or 1
if (_toRead.GetType() == typeof(BoolRegister)) {
string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring);
if (!result.Success) {
return new BRegisterResult {
Result = result,
Register = _toRead
};
}
var resultBool = result.Response.ParseRCSingleBit();
if (resultBool != null) {
_toRead.SetValueFromPLC(resultBool.Value);
}
var finalRes = new BRegisterResult {
Result = result,
Register = _toRead
};
return finalRes;
return resultBool.Value ? new byte[] { 1 } : new byte[] { 0 };
}
/// <summary>
/// Writes to the given bool register on the PLC
/// </summary>
/// <param name="_toWrite">The register to write to</param>
/// <param name="value">The value to write</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteBoolRegister(BRegister _toWrite, bool value) {
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(value ? "1" : "0")}";
var result = await SendCommandAsync(requeststring);
return result.Success && result.Response.StartsWith($"%{GetStationNumber()}$WC");
}
#endregion
#region Number register reading / writing
/// <summary>
/// Reads the given numeric register from the PLC
/// </summary>
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
/// <param name="_toRead">The register to read</param>
/// <returns>A result with the given NumberRegister containing the readback value and a result struct</returns>
public async Task<NRegisterResult<T>> ReadNumRegister<T>(NRegister<T> _toRead) {
Type numType = typeof(T);
//returns a byte array 2 bytes or 4 bytes long depending on the data size
if (_toRead.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring);
var failedResult = new NRegisterResult<T> {
Result = result,
Register = _toRead
};
if (!result.Success)
throw new Exception($"Failed to load the byte data for: {_toRead}");
if (!result.Success || string.IsNullOrEmpty(result.Response)) {
return failedResult;
}
if(_toRead.RegisterType == RegisterType.DT) {
if (numType == typeof(short)) {
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
if (resultBytes == null) return failedResult;
var val = short.Parse(resultBytes, NumberStyles.HexNumber);
_toRead.SetValueFromPLC(val);
} else if (numType == typeof(ushort)) {
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
if (resultBytes == null) return failedResult;
var val = ushort.Parse(resultBytes, NumberStyles.HexNumber);
_toRead.SetValueFromPLC(val);
} else if (numType == typeof(int)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
if (resultBytes == null) return failedResult;
var val = int.Parse(resultBytes, NumberStyles.HexNumber);
_toRead.SetValueFromPLC(val);
} else if (numType == typeof(uint)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
if (resultBytes == null) return failedResult;
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
_toRead.SetValueFromPLC(val);
} else if (numType == typeof(float)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
if (resultBytes == null) return failedResult;
//convert to unsigned int first
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
byte[] floatVals = BitConverter.GetBytes(val);
float finalFloat = BitConverter.ToSingle(floatVals, 0);
_toRead.SetValueFromPLC(finalFloat);
} else if (numType == typeof(TimeSpan)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
if (resultBytes == null) return failedResult;
//convert to unsigned int first
var vallong = long.Parse(resultBytes, NumberStyles.HexNumber);
var valMillis = vallong * 10;
var ts = TimeSpan.FromMilliseconds(valMillis);
//minmax writable / readable value is 10ms
_toRead.SetValueFromPLC(ts);
}
var finalRes = new NRegisterResult<T> {
Result = result,
Register = _toRead
};
return finalRes;
}
/// <summary>
/// Reads the given numeric register from the PLC
/// </summary>
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
/// <param name="_toWrite">The register to write</param>
/// <param name="_value">The value to write</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteNumRegister<T>(NRegister<T> _toWrite, T _value) {
byte[] toWriteVal;
Type numType = typeof(T);
if (numType == typeof(short)) {
toWriteVal = BitConverter.GetBytes(Convert.ToInt16(_value));
} else if (numType == typeof(ushort)) {
toWriteVal = BitConverter.GetBytes(Convert.ToUInt16(_value));
} else if (numType == typeof(int)) {
toWriteVal = BitConverter.GetBytes(Convert.ToInt32(_value));
} else if (numType == typeof(uint)) {
toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value));
} else if (numType == typeof(float)) {
var fl = _value as float?;
if (fl == null)
throw new NullReferenceException("Float cannot be null");
toWriteVal = BitConverter.GetBytes(fl.Value);
} else if (numType == typeof(TimeSpan)) {
var fl = _value as TimeSpan?;
if (fl == null)
throw new NullReferenceException("Timespan cannot be null");
var tLong = (uint)(fl.Value.TotalMilliseconds / 10);
toWriteVal = BitConverter.GetBytes(tLong);
return result.Response.ParseDTByteString(4).HexStringToByteArray();
} else {
toWriteVal = null;
return result.Response.ParseDTByteString(8).HexStringToByteArray();
}
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{toWriteVal.ToHexString()}";
}
//returns a byte array with variable size
if (_toRead.GetType() == typeof(BytesRegister<>)) {
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring);
return result.Success && result.Response.StartsWith($"%{GetStationNumber()}$WD");
if (!result.Success)
throw new Exception($"Failed to load the byte data for: {_toRead}");
return result.Response.ParseDTString().ReverseByteOrder().HexStringToByteArray();
}
throw new Exception($"Failed to load the byte data for: {_toRead}");
}
internal async Task<bool> WriteRawRegisterAsync (IRegister _toWrite, byte[] data) {
//returns a byte array 1 long and with the byte beeing 0 or 1
if (_toWrite.GetType() == typeof(BoolRegister)) {
string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}";
var result = await SendCommandAsync(requeststring);
return result.Success;
}
//returns a byte array 2 bytes or 4 bytes long depending on the data size
if (_toWrite.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) {
string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}";
var result = await SendCommandAsync(requeststring);
return result.Success;
}
//returns a byte array with variable size
if (_toWrite.GetType() == typeof(BytesRegister<>)) {
//string stationNum = GetStationNumber();
//string dataString = gotBytes.BuildDTString(_toWrite.ReservedSize);
//string dataArea = _toWrite.BuildCustomIdent(dataString.Length / 4);
//string requeststring = $"%{stationNum}#WD{dataArea}{dataString}";
//var result = await SendCommandAsync(requeststring);
}
return false;
}
#endregion
#region String register reading / writing
#region Register reading / writing
//string is build up like this
//04 00 04 00 53 50 33 35 13
//0, 1 = reserved size
//1, 2 = current size
//3,4,5,6 = ASCII encoded chars (SP35)
//7,8 = checksum
public async Task<bool> SetRegisterAsync (IRegister register, object value) {
/// <summary>
/// Reads back the value of a string register
/// </summary>
/// <param name="_toRead">The register to read</param>
/// <param name="_stationNumber">The station number of the PLC</param>
/// <returns></returns>
public async Task<SRegisterResult> ReadStringRegister(SRegister _toRead, int _stationNumber = 1) {
var internalReg = (IRegisterInternal)register;
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}";
var result = await SendCommandAsync(requeststring);
if (result.Success)
_toRead.SetValueFromPLC(result.Response.ParseDTString());
return new SRegisterResult {
Result = result,
Register = _toRead
};
}
return await internalReg.WriteAsync(this, value);
/// <summary>
/// Writes a string to a string register
/// </summary>
/// <param name="_toWrite">The register to write</param>
/// <param name="_value">The value to write, if the strings length is longer than the cap size it gets trimmed to the max char length</param>
/// <param name="_stationNumber">The station number of the PLC</param>
/// <returns>The success state of the write operation</returns>
public async Task<bool> WriteStringRegister(SRegister _toWrite, string _value, int _stationNumber = 1) {
if (_value == null) _value = "";
if (_value.Length > _toWrite.ReservedSize) {
throw new ArgumentException("Write string size cannot be longer than reserved string size");
}
string stationNum = GetStationNumber();
string dataString = _value.BuildDTString(_toWrite.ReservedSize);
string dataArea = _toWrite.BuildCustomIdent(dataString.Length / 4);
string requeststring = $"%{stationNum}#WD{dataArea}{dataString}";
var result = await SendCommandAsync(requeststring);
return result.Success && result.Response.StartsWith($"%{GetStationNumber()}$WD");
}
#endregion

View File

@@ -11,7 +11,9 @@ namespace MewtocolNet {
UDINT,
REAL,
TIME,
STRING
STRING,
WORD,
DWORD
}

View File

@@ -1,91 +0,0 @@
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MewtocolNet {
internal static class PlcVarTypeConversions {
static Dictionary<PlcVarType, Type> dictTypeConv = new Dictionary<PlcVarType, Type> {
{ PlcVarType.BOOL, typeof(bool) },
{ PlcVarType.INT, typeof(short) },
{ PlcVarType.UINT, typeof(ushort) },
{ PlcVarType.DINT, typeof(int) },
{ PlcVarType.UDINT, typeof(uint) },
{ PlcVarType.REAL, typeof(float) },
{ PlcVarType.TIME, typeof(TimeSpan) },
{ PlcVarType.STRING, typeof(string) },
};
static Dictionary<PlcVarType, Type> dictRegisterConv = new Dictionary<PlcVarType, Type> {
{ PlcVarType.BOOL, typeof(BRegister) },
{ PlcVarType.INT, typeof(NRegister<short>) },
{ PlcVarType.UINT, typeof(NRegister<ushort>) },
{ PlcVarType.DINT, typeof(NRegister<int>) },
{ PlcVarType.UDINT, typeof(NRegister<uint>) },
{ PlcVarType.REAL, typeof(NRegister<float>) },
{ PlcVarType.TIME, typeof(NRegister<TimeSpan>) },
{ PlcVarType.STRING, typeof(SRegister) },
};
internal static bool IsAllowedPlcCastingType <T> () {
var inversed = dictTypeConv.ToDictionary((i) => i.Value, (i) => i.Key);
return inversed.ContainsKey(typeof(T));
}
internal static bool IsAllowedPlcCastingType (this Type type) {
var inversed = dictTypeConv.ToDictionary((i) => i.Value, (i) => i.Key);
return inversed.ContainsKey(type);
}
internal static Type ToDotnetType (this PlcVarType type) {
if(dictTypeConv.ContainsKey(type)) {
return dictTypeConv[type];
}
throw new NotSupportedException($"The PlcVarType: '{type}' is not supported");
}
internal static PlcVarType ToPlcVarType (this Type type) {
var inversed = dictTypeConv.ToDictionary((i) => i.Value, (i) => i.Key);
if (inversed.ContainsKey(type)) {
return inversed[type];
}
throw new NotSupportedException($"The Dotnet Type: '{type}' is not supported");
}
internal static Type ToRegisterType (this PlcVarType type) {
if (dictRegisterConv.ContainsKey(type)) {
return dictRegisterConv[type];
}
throw new NotSupportedException($"The PlcVarType: '{type}' is not supported");
}
}
}

View File

@@ -8,10 +8,12 @@ namespace MewtocolNet.RegisterAttributes {
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class RegisterAttribute : Attribute {
internal int MemoryArea;
internal int StringLength;
internal RegisterType RegisterType;
internal RegisterType? RegisterType;
internal int MemoryArea = 0;
internal int ByteLength = 2;
internal byte SpecialAddress = 0x0;
internal BitCount BitCount;
internal int AssignedBitIndex = -1;
@@ -19,11 +21,36 @@ namespace MewtocolNet.RegisterAttributes {
/// Attribute for string type or numeric registers
/// </summary>
/// <param name="memoryArea">The area in the plcs memory</param>
/// <param name="stringLength">The max string length in the plc</param>
public RegisterAttribute(int memoryArea, int stringLength = 1) {
public RegisterAttribute(int memoryArea) {
MemoryArea = memoryArea;
StringLength = stringLength;
}
public RegisterAttribute(int memoryArea, int byteLength) {
MemoryArea = memoryArea;
ByteLength = byteLength;
}
public RegisterAttribute(int memoryArea, BitCount bitCount) {
MemoryArea = memoryArea;
BitCount = bitCount;
AssignedBitIndex = 0;
RegisterType = BitCount == BitCount.B16 ? MewtocolNet.RegisterType.DT : MewtocolNet.RegisterType.DDT;
}
public RegisterAttribute(int memoryArea, BitCount bitCount, int bitIndex) {
MemoryArea = memoryArea;
BitCount = bitCount;
AssignedBitIndex = bitIndex;
RegisterType = BitCount == BitCount.B16 ? MewtocolNet.RegisterType.DT : MewtocolNet.RegisterType.DDT;
}
@@ -49,42 +76,6 @@ namespace MewtocolNet.RegisterAttributes {
}
/// <summary>
/// Attribute to read numeric registers as bitwise
/// </summary>
/// <param name="memoryArea">The area in the plcs memory</param>
/// <param name="bitcount">The number of bits to parse</param>
public RegisterAttribute(int memoryArea, BitCount bitcount) {
MemoryArea = memoryArea;
StringLength = 0;
BitCount = bitcount;
}
/// <summary>
/// Attribute to read numeric registers as bitwise
/// </summary>
/// <param name="memoryArea">The area in the plcs memory</param>
/// <param name="bitcount">The number of bits to parse</param>
/// <param name="assignBit">The index of the bit that gets linked to the bool</param>
public RegisterAttribute(int memoryArea, uint assignBit, BitCount bitcount) {
if (assignBit > 15 && bitcount == BitCount.B16) {
throw new NotSupportedException("The assignBit parameter cannot be greater than 15 in a 16 bit var");
}
if (assignBit > 31 && bitcount == BitCount.B32) {
throw new NotSupportedException("The assignBit parameter cannot be greater than 31 in a 32 bit var");
}
MemoryArea = memoryArea;
StringLength = 0;
BitCount = bitcount;
AssignedBitIndex = (int)assignBit;
}
}
}

View File

@@ -0,0 +1,118 @@
using MewtocolNet.Registers;
using System;
using System.Collections;
using System.Reflection;
namespace MewtocolNet
{
internal struct RegisterBuildInfo {
internal string name;
internal int memoryAddress;
internal int memorySizeBytes;
internal byte? specialAddress;
internal RegisterType? registerType;
internal Type dotnetCastType;
internal Type collectionType;
internal IRegister Build () {
RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault();
PlcVarType plcType = dotnetCastType.ToPlcVarType();
Type registerClassType = plcType.GetDefaultPlcVarType();
if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool) || dotnetCastType == typeof(BitArray))) {
//-------------------------------------------
//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);
if (collectionType != null)
((IRegisterInternal)instance).WithCollectionType(collectionType);
return instance;
} else if (regType.IsNumericDTDDT()) {
//-------------------------------------------
//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);
if(collectionType != null)
((IRegisterInternal)instance).WithCollectionType(collectionType);
return instance;
}
if (regType.IsBoolean()) {
//-------------------------------------------
//as boolean register
var io = (IOType)(int)regType;
var spAddr = specialAddress;
var areaAddr = memoryAddress;
//create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.Instance;
var parameters = new object[] { io, spAddr.Value, areaAddr, name };
var instance = (BoolRegister)Activator.CreateInstance(typeof(BoolRegister), flags, null, parameters, null);
if (collectionType != null)
((IRegisterInternal)instance).WithCollectionType(collectionType);
return instance;
}
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");
}
}
}

View File

@@ -1,8 +1,10 @@
using MewtocolNet.Registers;
using System;
using System.Linq;
using System.Reflection;
namespace MewtocolNet.RegisterBuilding {
namespace MewtocolNet.RegisterBuilding
{
public static class FinalizerExtensions {
@@ -29,7 +31,7 @@ namespace MewtocolNet.RegisterBuilding {
step.dotnetVarType = typeof(bool);
} else if (isTypeNotDefined && step.RegType == RegisterType.DT_START) {
} else if (isTypeNotDefined && step.RegType == RegisterType.DT_RANGE) {
step.dotnetVarType = typeof(string);
@@ -37,58 +39,31 @@ namespace MewtocolNet.RegisterBuilding {
if(step.plcVarType != null) {
step.dotnetVarType = step.plcVarType.Value.ToDotnetType();
step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType();
}
//as numeric register
if (step.RegType.IsNumericDTDDT()) {
var builtReg = new RegisterBuildInfo {
if(step.plcVarType == null && step.dotnetVarType != null) {
name = step.Name,
specialAddress = step.SpecialAddress,
memoryAddress = step.MemAddress,
registerType = step.RegType,
dotnetCastType = step.dotnetVarType,
step.plcVarType = step.dotnetVarType.ToPlcVarType();
}.Build();
step.AddToRegisterList(builtReg);
return builtReg;
}
var type = step.plcVarType.Value.ToRegisterType();
private static void AddToRegisterList (this RegisterBuilderStep step, IRegister instance) {
var areaAddr = step.MemAddress;
var name = step.Name;
if (step.forInterface == null) return;
//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);
return instance;
}
if (step.RegType.IsBoolean()) {
var io = (IOType)(int)step.RegType;
var spAddr = step.SpecialAddress;
var areaAddr = step.MemAddress;
var name = step.Name;
//create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.Instance;
var parameters = new object[] { io, spAddr.Value, areaAddr, name };
var instance = (BRegister)Activator.CreateInstance(typeof(BRegister), flags, null, parameters, null);
return instance;
}
if (step.dotnetVarType != null) {
}
throw new Exception("Failed to build register");
step.forInterface.AddRegister(instance);
}

View File

@@ -9,7 +9,9 @@ namespace MewtocolNet.RegisterBuilding {
/// <summary>
/// Contains useful tools for register creation
/// </summary>
public static class RegBuilder {
public class RegBuilder {
internal MewtocolInterface forInterface = null;
//methods to test the input string on
private static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
@@ -19,7 +21,18 @@ namespace MewtocolNet.RegisterBuilding {
};
public static RegisterBuilderStep FromPlcRegName (string plcAddrName, string name = null) {
public static RegBuilder ForInterface (MewtocolInterface interf) {
var rb = new RegBuilder();
rb.forInterface = interf;
return rb;
}
public static RegBuilder Factory { get; private set; } = new RegBuilder();
public RegisterBuilderStep FromPlcRegName (string plcAddrName, string name = null) {
foreach (var method in parseMethods) {
@@ -31,7 +44,7 @@ namespace MewtocolNet.RegisterBuilding {
res.stepData.Name = name;
res.stepData.OriginalInput = plcAddrName;
res.stepData.forInterface = forInterface;
return res.stepData;
} else if(res.state == ParseResultState.FailedHard) {

View File

@@ -1,8 +1,11 @@
using System;
namespace MewtocolNet.RegisterBuilding {
public class RegisterBuilderStep {
internal MewtocolInterface forInterface;
internal bool wasCasted = false;
internal string OriginalInput;
@@ -74,7 +77,7 @@ namespace MewtocolNet.RegisterBuilding {
case RegisterType.DDT:
dotnetVarType = typeof(int);
break;
case RegisterType.DT_START:
case RegisterType.DT_RANGE:
dotnetVarType = typeof(string);
break;
}

View File

@@ -1,4 +1,6 @@
namespace MewtocolNet {
using System;
namespace MewtocolNet {
/// <summary>
/// The register prefixed type
@@ -26,9 +28,9 @@
/// </summary>
DDT = 4,
/// <summary>
/// Start area of a byte sequence longer than 2 words
/// Area of a byte sequence longer than 2 words
/// </summary>
DT_START = 5,
DT_RANGE = 5,
}

View File

@@ -1,13 +1,14 @@
using System;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a boolean
/// </summary>
public class BRegister : IRegister, INotifyPropertyChanged {
public class BoolRegister : IRegister, IRegisterInternal, INotifyPropertyChanged {
/// <summary>
/// Gets called whenever the value was changed
@@ -62,7 +63,7 @@ namespace MewtocolNet.Registers {
/// <param name="_name">The custom name</param>
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="Exception"></exception>
public BRegister(IOType _io, byte _spAddress = 0x0, int _areaAdress = 0, string _name = null) {
public BoolRegister(IOType _io, byte _spAddress = 0x0, int _areaAdress = 0, string _name = null) {
if (_areaAdress < 0)
throw new NotSupportedException("The area address cant be negative");
@@ -84,36 +85,33 @@ namespace MewtocolNet.Registers {
}
internal BRegister WithCollectionType(Type colType) {
collectionType = colType;
return this;
}
public void WithCollectionType (Type colType) => collectionType = colType;
public byte? GetSpecialAddress() => SpecialAddress;
/// <summary>
/// Builds the register area name
/// Builds the register area name for the mewtocol protocol
/// </summary>
public string BuildMewtocolQuery() {
//build area code from register type
StringBuilder asciistring = new StringBuilder(RegisterType.ToString());
//(R|X|Y)(area add [3] + special add [1])
StringBuilder asciistring = new StringBuilder();
string memPadded = MemoryAddress.ToString().PadLeft(4, '0');
string prefix = RegisterType.ToString();
string mem = MemoryAddress.ToString();
string sp = SpecialAddress.ToString("X1");
asciistring.Append(memPadded);
asciistring.Append(prefix);
asciistring.Append(mem.PadLeft(3, '0'));
asciistring.Append(sp);
return asciistring.ToString();
}
internal void SetValueFromPLC(bool val) {
public void SetValueFromPLC(object val) {
lastValue = val;
lastValue = (bool)val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
@@ -183,6 +181,19 @@ namespace MewtocolNet.Registers {
}
public async Task<object> ReadAsync (MewtocolInterface interf) {
var read = await interf.ReadRawRegisterAsync(this);
return PlcValueParser.Parse<bool>(read);
}
public async Task<bool> WriteAsync (MewtocolInterface interf, object data) {
return await interf.WriteRawRegisterAsync(this, PlcValueParser.Encode((bool)data));
}
}
}

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Result for a boolean register
/// </summary>
public class BRegisterResult {
public class BoolRegisterResult {
/// <summary>
/// The command result
@@ -13,7 +13,7 @@
/// <summary>
/// The used register
/// </summary>
public BRegister Register { get; set; }
public BoolRegister Register { get; set; }
}

View File

@@ -1,13 +1,15 @@
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 SRegister : IRegister {
public class BytesRegister<T> : IRegister, IRegisterInternal {
/// <summary>
/// Gets called whenever the value was changed
@@ -58,30 +60,25 @@ namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a string
/// </summary>
public SRegister(int _adress, int _reservedStringSize, string _name = null) {
public BytesRegister(int _adress, int _reservedSize, string _name = null) {
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
name = _name;
memoryAdress = _adress;
ReservedSize = (short)_reservedStringSize;
ReservedSize = (short)_reservedSize;
//calc mem length
var wordsize = (double)_reservedStringSize / 2;
var wordsize = (double)_reservedSize / 2;
if (wordsize % 2 != 0) {
wordsize++;
}
RegisterType = RegisterType.DT_START;
RegisterType = RegisterType.DT_RANGE;
memoryLength = (int)Math.Round(wordsize + 1);
}
internal SRegister WithCollectionType (Type colType) {
collectionType = colType;
return this;
}
public void WithCollectionType(Type colType) => collectionType = colType;
/// <summary>
/// Builds the register identifier for the mewotocol protocol
@@ -115,9 +112,9 @@ namespace MewtocolNet.Registers {
public bool IsUsedBitwise() => false;
internal void SetValueFromPLC(string val) {
public void SetValueFromPLC(object val) {
lastValue = val;
lastValue = (string)val;
TriggerChangedEvnt(this);
TriggerNotifyChange();
@@ -146,6 +143,19 @@ namespace MewtocolNet.Registers {
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, (byte[])data);
}
}
}

View File

@@ -3,7 +3,7 @@
/// <summary>
/// The results of a string register operation
/// </summary>
public class SRegisterResult {
public class BytesRegisterResult<T> {
/// <summary>
/// The command result
@@ -12,7 +12,7 @@
/// <summary>
/// The register definition used
/// </summary>
public SRegister Register { get; set; }
public BytesRegister<T> Register { get; set; }
}

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
@@ -10,7 +11,7 @@ namespace MewtocolNet.Registers {
/// Defines a register containing a number
/// </summary>
/// <typeparam name="T">The type of the numeric value</typeparam>
public class NRegister<T> : IRegister {
public class NumberRegister<T> : IRegister, IRegisterInternal {
/// <summary>
/// Gets called whenever the value was changed
@@ -65,7 +66,7 @@ namespace MewtocolNet.Registers {
/// </summary>
/// <param name="_adress">Memory start adress max 99999</param>
/// <param name="_name">Name of the register</param>
public NRegister (int _adress, string _name = null) {
public NumberRegister (int _adress, string _name = null) {
if (_adress > 99999)
throw new NotSupportedException("Memory adresses cant be greater than 99999");
@@ -98,7 +99,7 @@ namespace MewtocolNet.Registers {
}
public NRegister (int _adress, string _name = null, bool isBitwise = false, Type _enumType = null) {
public NumberRegister (int _adress, string _name = null, bool isBitwise = false, Type _enumType = null) {
if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999");
memoryAdress = _adress;
@@ -132,14 +133,9 @@ namespace MewtocolNet.Registers {
}
internal NRegister<T> WithCollectionType(Type colType) {
public void WithCollectionType(Type colType) => collectionType = colType;
collectionType = colType;
return this;
}
internal void SetValueFromPLC(object val) {
public void SetValueFromPLC(object val) {
lastValue = (T)val;
TriggerChangedEvnt(this);
@@ -214,7 +210,7 @@ namespace MewtocolNet.Registers {
/// <returns>A bitarray</returns>
public BitArray GetBitwise() {
if (this is NRegister<short> shortReg) {
if (this is NumberRegister<short> shortReg) {
var bytes = BitConverter.GetBytes((short)Value);
BitArray bitAr = new BitArray(bytes);
@@ -222,7 +218,7 @@ namespace MewtocolNet.Registers {
}
if (this is NRegister<int> intReg) {
if (this is NumberRegister<int> intReg) {
var bytes = BitConverter.GetBytes((int)Value);
BitArray bitAr = new BitArray(bytes);
@@ -291,6 +287,19 @@ namespace MewtocolNet.Registers {
public string ToString(bool additional) => $"{GetRegisterPLCName()} - Value: {GetValueString()}";
public async Task<object> ReadAsync(MewtocolInterface interf) {
var read = await interf.ReadRawRegisterAsync(this);
return PlcValueParser.Parse<T>(read);
}
public async Task<bool> WriteAsync(MewtocolInterface interf, object data) {
return await interf.WriteRawRegisterAsync(this, PlcValueParser.Encode((T)data));
}
}
}

View File

@@ -4,7 +4,7 @@
/// Result for a read/write operation
/// </summary>
/// <typeparam name="T">The type of the numeric value</typeparam>
public class NRegisterResult<T> {
public class NumberRegisterResult<T> {
/// <summary>
/// Command result
@@ -14,7 +14,7 @@
/// <summary>
/// The used register
/// </summary>
public NRegister<T> Register { get; set; }
public NumberRegister<T> Register { get; set; }
/// <summary>
/// Trys to get the value of there is one

View File

@@ -0,0 +1,124 @@
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace MewtocolNet.TypeConversion {
internal static class Conversions {
internal static Dictionary<PlcVarType, RegisterType> dictPlcTypeToRegisterType = new Dictionary<PlcVarType, RegisterType> {
{ PlcVarType.BOOL, RegisterType.R },
{ PlcVarType.INT, RegisterType.DT },
{ PlcVarType.UINT, RegisterType.DT },
{ PlcVarType.DINT, RegisterType.DDT },
{ PlcVarType.UDINT, RegisterType.DDT },
{ PlcVarType.REAL, RegisterType.DDT },
{ PlcVarType.TIME, RegisterType.DDT },
{ PlcVarType.WORD, RegisterType.DT },
{ PlcVarType.DWORD, RegisterType.DDT },
{ PlcVarType.STRING, RegisterType.DT_RANGE },
};
internal static List<IPlcTypeConverter> items = new List<IPlcTypeConverter> {
new PlcTypeConversion<bool>(RegisterType.R) {
HoldingRegisterType = typeof(BoolRegister),
PlcVarType = PlcVarType.BOOL,
FromRaw = bytes => {
return (bool)(bytes[0] == 1);
},
ToRaw = value => {
return new byte[] { (byte)(value ? 1 : 0) };
},
},
new PlcTypeConversion<bool>(RegisterType.X) {
HoldingRegisterType = typeof(BoolRegister),
PlcVarType = PlcVarType.BOOL,
FromRaw = bytes => {
return bytes[0] == 1;
},
ToRaw = value => {
return new byte[] { (byte)(value ? 1 : 0) };
},
},
new PlcTypeConversion<bool>(RegisterType.Y) {
HoldingRegisterType = typeof(BoolRegister),
PlcVarType = PlcVarType.BOOL,
FromRaw = bytes => {
return bytes[0] == 1;
},
ToRaw = value => {
return new byte[] { (byte)(value ? 1 : 0) };
},
},
new PlcTypeConversion<short>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<short>),
PlcVarType = PlcVarType.INT,
FromRaw = bytes => {
return BitConverter.ToInt16(bytes, 0);
},
ToRaw = value => {
return BitConverter.GetBytes(value);
},
},
new PlcTypeConversion<ushort>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<ushort>),
PlcVarType = PlcVarType.UINT,
FromRaw = bytes => {
return BitConverter.ToUInt16(bytes, 0);
},
ToRaw = value => {
return BitConverter.GetBytes(value);
},
},
new PlcTypeConversion<int>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<int>),
PlcVarType = PlcVarType.DINT,
FromRaw = bytes => {
return BitConverter.ToInt32(bytes, 0);
},
ToRaw = value => {
return BitConverter.GetBytes(value);
},
},
new PlcTypeConversion<uint>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<uint>),
PlcVarType = PlcVarType.UDINT,
FromRaw = bytes => {
return BitConverter.ToUInt32(bytes, 0);
},
ToRaw = value => {
return BitConverter.GetBytes(value);
},
},
};
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace MewtocolNet {
internal interface IPlcTypeConverter {
object FromRawData(byte[] data);
byte[] ToRawData(object value);
Type GetDotnetType();
Type GetHoldingRegisterType();
RegisterType GetPlcRegisterType();
PlcVarType GetPlcVarType();
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.ComponentModel;
namespace MewtocolNet {
internal class PlcTypeConversion<T> : IPlcTypeConverter {
public Type MainType { get; private set; }
public RegisterType PlcType { get; private set; }
public PlcVarType PlcVarType { get; set; }
public Type HoldingRegisterType { get; set; }
public Func<byte[], T> FromRaw { get; set; }
public Func<T, byte[]> ToRaw { get; set; }
public PlcTypeConversion(RegisterType plcType) {
MainType = typeof(T);
PlcType = plcType;
}
public Type GetDotnetType() => MainType;
public Type GetHoldingRegisterType() => HoldingRegisterType;
public RegisterType GetPlcRegisterType() => PlcType;
public PlcVarType GetPlcVarType() => PlcVarType;
public object FromRawData(byte[] data) => FromRaw.Invoke(data);
public byte[] ToRawData(object value) => ToRaw.Invoke((T)value);
}
}

View File

@@ -0,0 +1,55 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Registers;
using MewtocolNet.TypeConversion;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
namespace MewtocolNet {
internal static class PlcValueParser {
private static List<IPlcTypeConverter> conversions => Conversions.items;
public static T Parse<T>(byte[] bytes) {
var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
if (converter == null)
throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist");
return (T)converter.FromRawData(bytes);
}
public static byte[] Encode <T>(T value) {
var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T));
if (converter == null)
throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist");
return converter.ToRawData(value);
}
public static List<Type> GetAllowDotnetTypes () => conversions.Select(x => x.GetDotnetType()).ToList();
public static List<Type> GetAllowRegisterTypes () => conversions.Select(x => x.GetHoldingRegisterType()).ToList();
public static RegisterType? GetDefaultRegisterType (Type type) =>
conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetPlcRegisterType();
public static Type GetDefaultPlcVarType (this PlcVarType type) =>
conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetHoldingRegisterType();
public static Type GetDefaultDotnetType (this PlcVarType type) =>
conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetDotnetType();
public static PlcVarType? GetDefaultPlcVarType (this Type type) =>
conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetPlcVarType();
}
}

View File

@@ -0,0 +1,65 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Registers;
using MewtocolNet.TypeConversion;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace MewtocolNet {
internal static class PlcVarTypeConversions {
static List<Type> allowedCastingTypes = PlcValueParser.GetAllowDotnetTypes();
static List<Type> allowedGenericRegisters = PlcValueParser.GetAllowRegisterTypes();
internal static bool IsAllowedRegisterGenericType(this IRegister register) {
return allowedGenericRegisters.Contains(register.GetType());
}
internal static bool IsAllowedPlcCastingType<T>() {
return allowedCastingTypes.Contains(typeof(T));
}
internal static bool IsAllowedPlcCastingType(this Type type) {
return allowedCastingTypes.Contains(type);
}
internal static RegisterType ToRegisterTypeDefault(this Type type) {
var found = PlcValueParser.GetDefaultRegisterType(type);
if (found != null) {
return found.Value;
}
throw new MewtocolException("No default register type found");
}
internal static PlcVarType ToPlcVarType (this Type type) {
var found = type.GetDefaultPlcVarType().Value;
if (found != null) {
return found;
}
throw new MewtocolException("No default plcvar type found");
}
}
}

View File

@@ -19,7 +19,7 @@ namespace MewtocolTests {
//corresponds to a R100 boolean register in the PLC
//can also be written as R1000 because the last one is a special address
[Register(IOType.R, 100, spAdress: 0)]
[Register(IOType.R, memoryArea: 85, spAdress: 0)]
public bool TestBool1 { get; private set; }
//corresponds to a XD input of the PLC
@@ -60,10 +60,10 @@ namespace MewtocolTests {
public BitArray TestBitRegister32 { get; private set; }
//corresponds to a DT1204 as a 16bit word/int takes the bit at index 9 and writes it back as a boolean
[Register(1204, 9, BitCount.B16)]
[Register(1204, BitCount.B16, 9)]
public bool BitValue { get; private set; }
[Register(1204, 5, BitCount.B16)]
[Register(1204, BitCount.B32, 5)]
public bool FillTest { get; private set; }
//corresponds to a DT7012 - DT7013 as a 32bit time value that gets parsed as a timespan (TIME)
@@ -115,7 +115,7 @@ namespace MewtocolTests {
var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1));
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestBool1), false, 100, "R100");
TestBasicGeneration(register, nameof(TestRegisterCollection.TestBool1), false, 85, "R85");
}
@@ -208,8 +208,8 @@ namespace MewtocolTests {
//test generic properties
TestBasicGeneration(register, nameof(TestRegisterCollection.TestString2), null!, 7005, "DT7005");
Assert.Equal(5, ((SRegister)register).ReservedSize);
Assert.Equal(4, ((SRegister)register).MemoryLength);
Assert.Equal(5, ((BytesRegister<string>)register).ReservedSize);
Assert.Equal(4, ((BytesRegister<string>)register).MemoryLength);
}
@@ -224,8 +224,8 @@ namespace MewtocolTests {
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DT7010", (short)0, 7010, "DT7010");
Assert.True(((NRegister<short>)register).isUsedBitwise);
Assert.Equal(0, ((NRegister<short>)register).MemoryLength);
Assert.True(((NumberRegister<short>)register).isUsedBitwise);
Assert.Equal(0, ((NumberRegister<short>)register).MemoryLength);
}
@@ -240,8 +240,8 @@ namespace MewtocolTests {
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DDT8010", (int)0, 8010, "DDT8010");
Assert.True(((NRegister<int>)register).isUsedBitwise);
Assert.Equal(1, ((NRegister<int>)register).MemoryLength);
Assert.True(((NumberRegister<int>)register).isUsedBitwise);
Assert.Equal(1, ((NumberRegister<int>)register).MemoryLength);
}
@@ -256,7 +256,7 @@ namespace MewtocolTests {
//test generic properties
TestBasicGeneration(register, "Auto_Bitwise_DT1204", (short)0, 1204, "DT1204");
Assert.True(((NRegister<short>)register).isUsedBitwise);
Assert.True(((NumberRegister<short>)register).isUsedBitwise);
}

View File

@@ -16,32 +16,32 @@ public class TestRegisterBuilder {
this.output = output;
}
[Fact(DisplayName = "Parsing as BRegister List (Phyiscal Outputs)")]
[Fact(DisplayName = "Parsing as Bool Register List (Phyiscal Outputs)")]
public void TestParsingBRegisterY() {
var tests = new Dictionary<string, IRegister>() {
{"Y0", new BRegister(IOType.Y)},
{"Y1", new BRegister(IOType.Y, 0x1)},
{"Y2", new BRegister(IOType.Y, 0x2)},
{"Y3", new BRegister(IOType.Y, 0x3)},
{"Y4", new BRegister(IOType.Y, 0x4)},
{"Y5", new BRegister(IOType.Y, 0x5)},
{"Y6", new BRegister(IOType.Y, 0x6)},
{"Y7", new BRegister(IOType.Y, 0x7)},
{"Y8", new BRegister(IOType.Y, 0x8)},
{"Y9", new BRegister(IOType.Y, 0x9)},
{"Y0", new BoolRegister(IOType.Y)},
{"Y1", new BoolRegister(IOType.Y, 0x1)},
{"Y2", new BoolRegister(IOType.Y, 0x2)},
{"Y3", new BoolRegister(IOType.Y, 0x3)},
{"Y4", new BoolRegister(IOType.Y, 0x4)},
{"Y5", new BoolRegister(IOType.Y, 0x5)},
{"Y6", new BoolRegister(IOType.Y, 0x6)},
{"Y7", new BoolRegister(IOType.Y, 0x7)},
{"Y8", new BoolRegister(IOType.Y, 0x8)},
{"Y9", new BoolRegister(IOType.Y, 0x9)},
{"YA", new BRegister(IOType.Y, 0xA)},
{"YB", new BRegister(IOType.Y, 0xB)},
{"YC", new BRegister(IOType.Y, 0xC)},
{"YD", new BRegister(IOType.Y, 0xD)},
{"YE", new BRegister(IOType.Y, 0xE)},
{"YF", new BRegister(IOType.Y, 0xF)},
{"YA", new BoolRegister(IOType.Y, 0xA)},
{"YB", new BoolRegister(IOType.Y, 0xB)},
{"YC", new BoolRegister(IOType.Y, 0xC)},
{"YD", new BoolRegister(IOType.Y, 0xD)},
{"YE", new BoolRegister(IOType.Y, 0xE)},
{"YF", new BoolRegister(IOType.Y, 0xF)},
{"Y1A", new BRegister(IOType.Y, 0xA, 1)},
{"Y10B", new BRegister(IOType.Y, 0xB, 10)},
{"Y109C", new BRegister(IOType.Y, 0xC, 109)},
{"Y1A", new BoolRegister(IOType.Y, 0xA, 1)},
{"Y10B", new BoolRegister(IOType.Y, 0xB, 10)},
{"Y109C", new BoolRegister(IOType.Y, 0xC, 109)},
};
@@ -49,32 +49,32 @@ public class TestRegisterBuilder {
}
[Fact(DisplayName = "Parsing as BRegister List (Phyiscal Inputs)")]
[Fact(DisplayName = "Parsing as Bool Register List (Phyiscal Inputs)")]
public void TestParsingBRegisterX() {
var tests = new Dictionary<string, IRegister>() {
{"X0", new BRegister(IOType.X)},
{"X1", new BRegister(IOType.X, 0x1)},
{"X2", new BRegister(IOType.X, 0x2)},
{"X3", new BRegister(IOType.X, 0x3)},
{"X4", new BRegister(IOType.X, 0x4)},
{"X5", new BRegister(IOType.X, 0x5)},
{"X6", new BRegister(IOType.X, 0x6)},
{"X7", new BRegister(IOType.X, 0x7)},
{"X8", new BRegister(IOType.X, 0x8)},
{"X9", new BRegister(IOType.X, 0x9)},
{"X0", new BoolRegister(IOType.X)},
{"X1", new BoolRegister(IOType.X, 0x1)},
{"X2", new BoolRegister(IOType.X, 0x2)},
{"X3", new BoolRegister(IOType.X, 0x3)},
{"X4", new BoolRegister(IOType.X, 0x4)},
{"X5", new BoolRegister(IOType.X, 0x5)},
{"X6", new BoolRegister(IOType.X, 0x6)},
{"X7", new BoolRegister(IOType.X, 0x7)},
{"X8", new BoolRegister(IOType.X, 0x8)},
{"X9", new BoolRegister(IOType.X, 0x9)},
{"XA", new BRegister(IOType.X, 0xA)},
{"XB", new BRegister(IOType.X, 0xB)},
{"XC", new BRegister(IOType.X, 0xC)},
{"XD", new BRegister(IOType.X, 0xD)},
{"XE", new BRegister(IOType.X, 0xE)},
{"XF", new BRegister(IOType.X, 0xF)},
{"XA", new BoolRegister(IOType.X, 0xA)},
{"XB", new BoolRegister(IOType.X, 0xB)},
{"XC", new BoolRegister(IOType.X, 0xC)},
{"XD", new BoolRegister(IOType.X, 0xD)},
{"XE", new BoolRegister(IOType.X, 0xE)},
{"XF", new BoolRegister(IOType.X, 0xF)},
{"X1A", new BRegister(IOType.X, 0xA, 1)},
{"X10B", new BRegister(IOType.X, 0xB, 10)},
{"X109C", new BRegister(IOType.X, 0xC, 109)},
{"X1A", new BoolRegister(IOType.X, 0xA, 1)},
{"X10B", new BoolRegister(IOType.X, 0xB, 10)},
{"X109C", new BoolRegister(IOType.X, 0xC, 109)},
};
@@ -82,35 +82,35 @@ public class TestRegisterBuilder {
}
[Fact(DisplayName = "Parsing as BRegister List (Internal Relay)")]
[Fact(DisplayName = "Parsing as Bool Register List (Internal Relay)")]
public void TestParsingBRegisterR() {
var tests = new Dictionary<string, IRegister>() {
{"R0", new BRegister(IOType.R)},
{"R1", new BRegister(IOType.R, 0x1)},
{"R2", new BRegister(IOType.R, 0x2)},
{"R3", new BRegister(IOType.R, 0x3)},
{"R4", new BRegister(IOType.R, 0x4)},
{"R5", new BRegister(IOType.R, 0x5)},
{"R6", new BRegister(IOType.R, 0x6)},
{"R7", new BRegister(IOType.R, 0x7)},
{"R8", new BRegister(IOType.R, 0x8)},
{"R9", new BRegister(IOType.R, 0x9)},
{"R0", new BoolRegister(IOType.R)},
{"R1", new BoolRegister(IOType.R, 0x1)},
{"R2", new BoolRegister(IOType.R, 0x2)},
{"R3", new BoolRegister(IOType.R, 0x3)},
{"R4", new BoolRegister(IOType.R, 0x4)},
{"R5", new BoolRegister(IOType.R, 0x5)},
{"R6", new BoolRegister(IOType.R, 0x6)},
{"R7", new BoolRegister(IOType.R, 0x7)},
{"R8", new BoolRegister(IOType.R, 0x8)},
{"R9", new BoolRegister(IOType.R, 0x9)},
{"RA", new BRegister(IOType.R, 0xA)},
{"RB", new BRegister(IOType.R, 0xB)},
{"RC", new BRegister(IOType.R, 0xC)},
{"RD", new BRegister(IOType.R, 0xD)},
{"RE", new BRegister(IOType.R, 0xE)},
{"RF", new BRegister(IOType.R, 0xF)},
{"RA", new BoolRegister(IOType.R, 0xA)},
{"RB", new BoolRegister(IOType.R, 0xB)},
{"RC", new BoolRegister(IOType.R, 0xC)},
{"RD", new BoolRegister(IOType.R, 0xD)},
{"RE", new BoolRegister(IOType.R, 0xE)},
{"RF", new BoolRegister(IOType.R, 0xF)},
{"R1A", new BRegister(IOType.R, 0xA, 1)},
{"R10B", new BRegister(IOType.R, 0xB, 10)},
{"R109C", new BRegister(IOType.R, 0xC, 109)},
{"R1000", new BRegister(IOType.R, 0x0, 100)},
{"R511", new BRegister(IOType.R, 0x0, 511)},
{"R511A", new BRegister(IOType.R, 0xA, 511)},
{"R1A", new BoolRegister(IOType.R, 0xA, 1)},
{"R10B", new BoolRegister(IOType.R, 0xB, 10)},
{"R109C", new BoolRegister(IOType.R, 0xC, 109)},
{"R1000", new BoolRegister(IOType.R, 0x0, 100)},
{"R511", new BoolRegister(IOType.R, 0x0, 511)},
{"R511A", new BoolRegister(IOType.R, 0xA, 511)},
};
@@ -126,7 +126,7 @@ public class TestRegisterBuilder {
output.WriteLine($"Expected: {item.Key}");
var built = RegBuilder.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);
@@ -141,63 +141,75 @@ public class TestRegisterBuilder {
}
[Fact(DisplayName = "Parsing as BRegister (Casted)")]
[Fact(DisplayName = "Parsing as Bool Register (Casted)")]
public void TestRegisterBuildingBoolCasted () {
var expect = new BRegister(IOType.R, 0x1, 0);
var expect2 = new BRegister(IOType.Y, 0xA, 103);
var expect = new BoolRegister(IOType.R, 0x1, 0);
var expect2 = new BoolRegister(IOType.Y, 0xA, 103);
Assert.Equivalent(expect, RegBuilder.FromPlcRegName("R1").AsPlcType(PlcVarType.BOOL).Build());
Assert.Equivalent(expect, RegBuilder.FromPlcRegName("R1").AsType<bool>().Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").AsPlcType(PlcVarType.BOOL).Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").AsType<bool>().Build());
Assert.Equivalent(expect2, RegBuilder.FromPlcRegName("Y103A").AsPlcType(PlcVarType.BOOL).Build());
Assert.Equivalent(expect2, RegBuilder.FromPlcRegName("Y103A").AsType<bool>().Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").AsPlcType(PlcVarType.BOOL).Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").AsType<bool>().Build());
}
[Fact(DisplayName = "Parsing as BRegister (Auto)")]
[Fact(DisplayName = "Parsing as Bool Register (Auto)")]
public void TestRegisterBuildingBoolAuto () {
var expect = new BRegister(IOType.R, 0x1, 0);
var expect2 = new BRegister(IOType.Y, 0xA, 103);
var expect = new BoolRegister(IOType.R, 0x1, 0);
var expect2 = new BoolRegister(IOType.Y, 0xA, 103);
Assert.Equivalent(expect, RegBuilder.FromPlcRegName("R1").Build());
Assert.Equivalent(expect, RegBuilder.FromPlcRegName("R1").Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").Build());
Assert.Equivalent(expect2, RegBuilder.FromPlcRegName("Y103A").Build());
Assert.Equivalent(expect2, RegBuilder.FromPlcRegName("Y103A").Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").Build());
}
[Fact(DisplayName = "Parsing as NRegister (Casted)")]
[Fact(DisplayName = "Parsing as Number Register (Casted)")]
public void TestRegisterBuildingNumericCasted() {
var expect = new NRegister<short>(303, null);
var expect2 = new NRegister<int>(10002, null);
var expect3 = new NRegister<TimeSpan>(400, null);
var expect = new NumberRegister<short>(303, null);
var expect2 = new NumberRegister<int>(10002, null);
var expect3 = new NumberRegister<TimeSpan>(400, null);
//var expect4 = new NRegister<TimeSpan>(103, null, true);
Assert.Equivalent(expect, RegBuilder.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build());
Assert.Equivalent(expect, RegBuilder.FromPlcRegName("DT303").AsType<short>().Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build());
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType<short>().Build());
Assert.Equivalent(expect2, RegBuilder.FromPlcRegName("DDT10002").AsPlcType(PlcVarType.DINT).Build());
Assert.Equivalent(expect2, RegBuilder.FromPlcRegName("DDT10002").AsType<int>().Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsPlcType(PlcVarType.DINT).Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsType<int>().Build());
Assert.Equivalent(expect3, RegBuilder.FromPlcRegName("DDT400").AsPlcType(PlcVarType.TIME).Build());
Assert.Equivalent(expect3, RegBuilder.FromPlcRegName("DDT400").AsType<TimeSpan>().Build());
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT400").AsPlcType(PlcVarType.TIME).Build());
Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT400").AsType<TimeSpan>().Build());
//Assert.Equivalent(expect4, RegBuilder.FromPlcRegName("DT103").AsType<BitArray>().Build());
}
[Fact(DisplayName = "Parsing as NRegister (Auto)")]
[Fact(DisplayName = "Parsing as Number Register (Auto)")]
public void TestRegisterBuildingNumericAuto() {
var expect = new NRegister<short>(303, null);
var expect2 = new NRegister<int>(10002, null);
var expect = new NumberRegister<short>(303, null);
var expect2 = new NumberRegister<int>(10002, null);
Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").Build());
Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").Build());
}
[Fact(DisplayName = "Parsing as Bytes Register (Casted)")]
public void TestRegisterBuildingByteRangeCasted() {
var expect = new BytesRegister<byte[]>(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.FromPlcRegName("DT303").Build());
Assert.Equivalent(expect2, RegBuilder.FromPlcRegName("DDT10002").Build());
}

View File

@@ -17,12 +17,12 @@ namespace MewtocolTests {
public void NumericRegisterMewtocolIdentifiers() {
List<IRegister> registers = new List<IRegister> {
new NRegister<short>(50, _name: null),
new NRegister<ushort>(50, _name: null),
new NRegister<int>(50, _name : null),
new NRegister<uint>(50, _name : null),
new NRegister<float>(50, _name : null),
new NRegister<TimeSpan>(50, _name : null),
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<string> expectedIdents = new List<string> {
@@ -51,23 +51,23 @@ namespace MewtocolTests {
List<IRegister> registers = new List<IRegister> {
//numeric ones
new NRegister<short>(50, _name: null),
new NRegister<ushort>(60, _name : null),
new NRegister<int>(70, _name : null),
new NRegister<uint>(80, _name : null),
new NRegister<float>(90, _name : null),
new NRegister<TimeSpan>(100, _name : null),
new NumberRegister<short>(50, _name: null),
new NumberRegister<ushort>(60, _name : null),
new NumberRegister<int>(70, _name : null),
new NumberRegister<uint>(80, _name : null),
new NumberRegister<float>(90, _name : null),
new NumberRegister<TimeSpan>(100, _name : null),
//boolean
new BRegister(IOType.R, 0, 100),
new BRegister(IOType.R, 0, 0),
new BRegister(IOType.X, 5),
new BRegister(IOType.X, 0xA),
new BRegister(IOType.X, 0xF, 109),
new BRegister(IOType.Y, 0xC, 75),
new BoolRegister(IOType.R, 0, 100),
new BoolRegister(IOType.R, 0, 0),
new BoolRegister(IOType.X, 5),
new BoolRegister(IOType.X, 0xA),
new BoolRegister(IOType.X, 0xF, 109),
new BoolRegister(IOType.Y, 0xC, 75),
//string
new SRegister(999, 5),
new BytesRegister<string>(999, 5),
};
List<string> expcectedIdents = new List<string> {
@@ -110,7 +110,7 @@ namespace MewtocolTests {
var ex = Assert.Throws<NotSupportedException>(() => {
new NRegister<short>(100000, _name: null);
new NumberRegister<short>(100000, _name: null);
});
@@ -118,7 +118,7 @@ namespace MewtocolTests {
var ex1 = Assert.Throws<NotSupportedException>(() => {
new BRegister(IOType.R, _areaAdress: 512);
new BoolRegister(IOType.R, _areaAdress: 512);
});
@@ -126,7 +126,7 @@ namespace MewtocolTests {
var ex2 = Assert.Throws<NotSupportedException>(() => {
new BRegister(IOType.X, _areaAdress: 110);
new BoolRegister(IOType.X, _areaAdress: 110);
});
@@ -134,7 +134,7 @@ namespace MewtocolTests {
var ex3 = Assert.Throws<NotSupportedException>(() => {
new SRegister(100000, 5);
new BytesRegister<string>(100000, 5);
});
@@ -147,7 +147,7 @@ namespace MewtocolTests {
var ex = Assert.Throws<NotSupportedException>(() => {
new NRegister<double>(100, _name: null);
new NumberRegister<double>(100, _name: null);
});

Binary file not shown.

Binary file not shown.

View File

@@ -2,10 +2,20 @@
<ProjectConfiguration CompactMode="1">
<PaneContents>
<Pane-1 PaneBaseID="410" PaneInstanceNumber="1">
<Contents BinaryData="B21A0000789CE599516F9B3010C7FB39F684E843DF164C429B484D251A688794940A58BA699A22065E6A0D6C844DD3EC43ED33CE86A66D68B384AA04A4BD4418B8BB3FF7E3EC33F9F3E1E0E07442306224BD4138240B6944E2C40FD884847028035972835B18FB43B9D7E5C7B7646105044F115C0C65453E3B1D91288B315D1D481683B18543782F2E4B166630C57E74E5C7DC991542CCD04F045359D22334C7311F73C760D0EB2A3DEE9DF90C05372864B73CB2C6EDF58C91E7E34FD00F61FACC96EB33EF139F070C45C0CE6B3A4059C7D48F32B8260128BDBE76D22F4950C1604D423E7E9304B52CE1E25A0FC31452BA838CFE7A22FA6FCD43B72C829316D63BC05095750DF9F84D227A2F4444BEC8C21451F42382F95BB3FDDD28C9019BE4D851F8E0F829FC56895A59A2B74C60830A3B8F355638C8FD16E75CF41BE1792E292F4DFE10D4C29729C912FA60EDC080A4A194A779283BE6C8760C591A93E05711403243C4FCFCC1B89CEB14DE3DD6B670B732B43C73224B1EBCE7B23D48D9392111288C5DB6CCADD5BE88B7D1C8F96CBEB85F72633F65793DBA308201430417934E3E1296361636E264C9392F21C3704CD75D4570789295B5102A38392E9B8DECC9C4BCF2B6DF37D69F5C4F75677639B6CFF5F1563BEFEBB5B9323BB7EDD70D3A0595DAE8185912A1C067B00A21A55E3C86E1B50E90616D08543720513E164E32F6C5A882E8421FBB3557514950D3841A2B21BE0480E336958FD71F0C5A85A6A9DAE164BA6A9BC818DE89A28056B1696C62BB88885F15CFC7BD00EAB60A90636E30A81B90CB52DE325602747454F7D4C6F168ADC2E37A8E7575F94DFBDE4C77809803E78832B153DE9D13383E54CA1D562DB040BBBAB89B3CEB7BE6243661330E6A81289CF1D656157BC0D6B17A21EBBF64256ACA4371A5DD9077A8D03D2C4B406D151ECF9A988DE0317116B7ABA7D3DA35C935D3D151C1A652B3B097BA012DFB8E5063D94806A249E42FC54748716D73E6E7B3C568194428E0AB920B196F1EAA6D60D57C459A153FE0E1A8EE054A6B595F5EE3FA245954340D16A63065302CFC6C42BA0BB6B2CA7742F3BE3476CAFDBFF2DD59FB7BEAEC2F8D4435CD">
<Contents BinaryData="B31A0000789CE599D16EDA301486FB1CBB8AD28BDE8D38900252A94449DA4582A64A22BA699A509678D45A6247B133CA5E6AAF383B296D49CB80AA2196768362E03FE7E77C9CF818FE7C383A3A9B108C18C96E118EC8421991240D423621111CA84055BCF00E26C140EDB4F9F51D59D821C1530417035553CFCF4624CE134C57178ACD6062E308DE8B97151B3398E120BE0E121ECC8E2066E8078299AA0C6334C7095FF3C0A0DF696B1D1E9D050C85B72862773CB3C1F5C39C91E7EB4F308860F64CCBFD59F769C013462261EB351FA0EA631AC4395CB300B44ECFE8F62A1674D05FB350ACDF6441AF5AB8BC1946510629DDC1466FBD10BDB7D6A15D35C1490BF50E30746DDD43B17E9389CE0B137120AA3045147D8F61F1ADD9FEDDA8D8019BEC3871F410F829FD568B46D5A2BF4C61830E5B8F3D560628E296CF79E837C2F3C252D19AFC43501B5F65244FE983DA8521C922A528F34075AD91E39AAA3226E1CF328162458805C507E3766E32F8EBB1B745B895D0F6AD89AAF8F09EDBF621651784C4A0147B6C59A8F59EC8B74974391C7BD60B81E22541C68A86F4600C4386082EEF3AC54A481D2C34E2C94A74DE43A6E95A9EB74AE1F22A6B6B2974D03DADCA46CE64625DFBDBDF371E3E859E0EDDD9D5D8B9188EB7EAFC2F37D64A76E138AF0B5A2596DAF098791AA33060701F445ABD784CD3970E90696F48543720D13F364E73F6D994AC8B2A869A26D4580BF13D009CCAD43E7EAFDF970A4D53BDC3C9B47599C8987E57D380546C1ABBB15DC624D817CFC783006A4B05C8B53608EA06E4B18CCF8C7B013A39A9FBD6C6F11852E1F17CD7BEBEFA6A7C6B663A40CC85734499382AEFCE099C1E6BD509AB165840AE29EEB6A8FA81398953D88C835A200A677CB4D5C521503A562F6CFD97AC444FF928D9EB34E41F6BF400DB12D0A5C2E3DB13AB113C16CE13B9663A43AE9B5C33131D156CF61A160ED23740B2DF116A6C1BC544348D83A5F81552BCB6B9F2F3D962B40C6314F25DC9838C0F0FFB1D60F562479A950FE0E1AAEE0DCA906C2EAF717F526C2A86061B5398311895713621DD055BD5E53BA1795F1A3BD5FE5FF56EADFD3F75FE17D00A35F9">
<FilterHistory/>
</Contents>
</Pane-1>
<Summary PaneCount="1"/>
<Pane-2 PaneBaseID="410" PaneInstanceNumber="4">
<Contents BinaryData="480F0000789CED97ED6EDA3014867B07BB052B3740BEF8A8542AA524ED224183928C6ABF901B7B602DB1516206F4EA6727D036860E8658C70FFEE504BFEF797D9E9880FBE5EAEA66C028E12C7F2214B105E8B16C06133E6008773543035132C519EC6AB625AEA76CE1278C8E085E74355DBBBDE9B1749ED16273017C8E339F22BC941F039F729C53983EC24C98F908534E7E109C6BC049C98466A216C6C6B56DE9B670E79093E489203E159D9B42EFCC397B5F7FC510E1FC9D56E4F39633281A22D9B0B12B87A1E618C1748E6B110CDDEE34DB1D2582695CD72294F551114C35C2FDD04128C74571408C4E7D109D63E760A9210469A93E0086A9D73394F55121ECAD102994531891823CA7B87C6AF63F1B4A1CE3A338418AD6C66FEDF7466CAA11E3D50CFFC7848DD733561994BED5BD88BC103A29239547536CA2F0E943CEE6B362AD0E71C27204CA3177B5D0EB05A1AB813E4B7E560D80870887E5C6449C618E7FBD9E6D69B711FAB137D0408C9722F664FC5C6D7633A610A770655456115F955E664776FFC8220EBF795BEB4194C19C97A733C2294E3861B4FA0A2A2BA90CA8D4C89B8AB93850AE1B7A51B4E910EA357FD368B7544D2F180CBCC778FFBABEF3E63B72C2F1433FB873FA7B75F1F7A1B791DD05C16E41A302F489A0CC3303655C40ED06659D1BA8FB0BA9DDA4EC7323A51BCE85D5644CD6AC945DEE01D4DE7E959D148F1B5BCDB3A2A3CEE773E0A0351DF72FF15856EB1FF311805A6705686B44A72304FC42FE9EF46981738E51E52317ED0270082435E589D09C96C641B3FFD3BC1BB53FCDB7BF01DF64C269">
<FilterHistory/>
</Contents>
</Pane-2>
<Pane-3 PaneBaseID="410" PaneInstanceNumber="5">
<Contents BinaryData="0A070000789CC595D16E9B3014867BB5BD86C50B2410B252A9A94413D6218512014BB5ABCAC5678935B023E32C499F7EB6296DA1E912659576C731FEFFF3F91C1FF8F4F9ECEC32E28C4A2EEE28237C83C6BC5CE15C469CC0C8B22D94E64B28F1C87207EA79C93761CED99CC26664F5ADABCB312FD625AB9A07144A28434660AB5FA39049100C17B7B85466210126E94F0AC2427E4117AC54B132B62FDC41DF55EE124B9ADF5122972AF350E9FDB5E4AFE36F800988575AC5176C575825243A616F1F87DDE598E3620D2D04BBEF7AC373AF83E0D8172D04139F84E07411BECE7C420454D511185EBB10DEA97518742154A7B5FA886638FD3683894F8270DF40145857614E2BFA5080B93587EF4607C77E0F272EC893F14BFA8388C32E62B65BC17F24EC3DCF586D607CEBB5943E52B630486634D521AA90DD08BE5E554FEA04722E0832651E5949308E938985A63CFF55274001A1129B83299C9980DFCFB3ADED1A619805918532D82AECC5FD437DD8A64C091478E7D456A9DC192FC7D3D9DFB3C892EFC19BFD282DB190663A5328209794B3FA136422AD8C99D6E8C58EB91AA8C92409D2B4C990D82D7FC73EFFD2D58CE3280A6EB3C3FBA6FE8BEFDC4FEE6FA6F1B53F3DA8CB7ECC8246761DC7FB05BDBA41FFD0281456FA4684AC022181D43E7AD3BEF21FD3A22EE507F5E563BB7154EDFF56EF5EEBB777F507221839D5">
<FilterHistory/>
</Contents>
</Pane-3>
<Summary PaneCount="3"/>
</PaneContents>
</ProjectConfiguration>