Add Dword / Word

This commit is contained in:
Felix Weiß
2023-07-14 00:33:42 +02:00
parent daecd73a6d
commit 32c20e7360
20 changed files with 609 additions and 592 deletions

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
namespace MewtocolNet {
/// <summary>
/// A DWord is a 16 bit value of 2 bytes
/// </summary>
public struct DWord : MewtocolExtensionTypeDDT {
private int bitLength;
internal uint value;
public uint Value {
get => value;
set {
this.value = value;
}
}
public DWord(uint bytes) {
value = bytes;
bitLength = Marshal.SizeOf(value) * 8;
}
public DWord(byte[] bytes) {
bytes = bytes.Take(4).ToArray();
value = BitConverter.ToUInt32(bytes, 0);
bitLength = Marshal.SizeOf(value) * 8;
}
//operations
public static DWord operator -(DWord a, DWord b) => new DWord() {
value = (ushort)(a.value - b.value)
};
public static DWord operator +(DWord a, DWord b) => new DWord() {
value = (ushort)(a.value + b.value)
};
public static DWord operator *(DWord a, DWord b) => new DWord() {
value = (ushort)(a.value * b.value)
};
public static DWord operator /(DWord a, DWord b) => new DWord() {
value = (ushort)(a.value / b.value)
};
public static bool operator ==(DWord a, DWord b) => a.value == b.value;
public static bool operator !=(DWord a, DWord b) => a.value != b.value;
/// <summary>
/// Gets or sets the bit value at the given position
/// </summary>
public bool this[int bitIndex] {
get {
if (bitIndex > bitLength - 1)
throw new IndexOutOfRangeException($"The DWord bit index was out of range ({bitIndex}/{bitLength - 1})");
return (value & (1 << bitIndex)) != 0;
}
set {
if (bitIndex > bitLength - 1)
throw new IndexOutOfRangeException($"The DWord bit index was out of range ({bitIndex}/{bitLength - 1})");
int mask = 1 << bitIndex;
this.value = value ? this.value |= (uint)mask : this.value &= (uint)~mask;
}
}
public void ClearBits () => this.value = 0;
public override bool Equals(object obj) {
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
return false;
} else {
return (DWord)obj == this;
}
}
public override int GetHashCode() => (int)value;
public byte[] ToByteArray() => BitConverter.GetBytes(value);
//string ops
public override string ToString() => $"0x{value.ToString("X8")}";
public string ToStringBits () {
return Convert.ToString(value, 2).PadLeft(bitLength, '0');
}
public string ToStringBitsPlc () {
var parts = Convert.ToString(value, 2)
.PadLeft(Marshal.SizeOf(value) * 8, '0')
.SplitInParts(4);
return string.Join("_", parts);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace MewtocolNet {
internal interface MewtocolExtensionTypeDT { }
internal interface MewtocolExtensionTypeDDT { }
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
namespace MewtocolNet {
/// <summary>
/// A word is a 16 bit value of 2 bytes
/// </summary>
public struct Word : MewtocolExtensionTypeDT {
private int bitLength;
internal ushort value;
public ushort Value {
get => value;
set {
this.value = value;
}
}
public Word(ushort bytes) {
value = bytes;
bitLength = Marshal.SizeOf(value) * 8;
}
public Word(byte[] bytes) {
bytes = bytes.Take(2).ToArray();
value = BitConverter.ToUInt16(bytes, 0);
bitLength = Marshal.SizeOf(value) * 8;
}
//operations
public static Word operator -(Word a, Word b) => new Word() {
value = (ushort)(a.value - b.value)
};
public static Word operator +(Word a, Word b) => new Word() {
value = (ushort)(a.value + b.value)
};
public static Word operator *(Word a, Word b) => new Word() {
value = (ushort)(a.value * b.value)
};
public static Word operator /(Word a, Word b) => new Word() {
value = (ushort)(a.value / b.value)
};
public static bool operator ==(Word a, Word b) => a.value == b.value;
public static bool operator !=(Word a, Word b) => a.value != b.value;
/// <summary>
/// Gets or sets the bit value at the given position
/// </summary>
public bool this[int bitIndex] {
get {
if (bitIndex > bitLength - 1)
throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})");
return (value & (1 << bitIndex)) != 0;
}
set {
if (bitIndex > bitLength - 1)
throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})");
int mask = 1 << bitIndex;
this.value = value ? this.value |= (ushort)mask : this.value &= (ushort)~mask;
}
}
public void ClearBits () => this.value = 0;
public override bool Equals(object obj) {
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
return false;
} else {
return (Word)obj == this;
}
}
public override int GetHashCode() => (int)value;
public byte[] ToByteArray() => BitConverter.GetBytes(value);
//string ops
public override string ToString() => $"0x{value.ToString("X4")}";
public string ToStringBits () {
return Convert.ToString(value, 2).PadLeft(bitLength, '0');
}
public string ToStringBitsPlc () {
var parts = Convert.ToString(value, 2)
.PadLeft(Marshal.SizeOf(value) * 8, '0')
.SplitInParts(4);
return string.Join("_", parts);
}
}
}

View File

@@ -5,8 +5,10 @@ using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using static MewtocolNet.RegisterBuilding.RBuild;
namespace MewtocolNet {
@@ -252,14 +254,18 @@ namespace MewtocolNet {
}
internal static bool CompareIsDuplicateNonCast (this BaseRegister reg1, BaseRegister compare, bool ingnoreByteRegisters = true) {
internal static bool CompareIsDuplicateNonCast (this BaseRegister toInsert, BaseRegister compare, List<Type> allowOverlappingTypes) {
if (ingnoreByteRegisters && (compare.GetType() == typeof(BytesRegister) || reg1.GetType() == typeof(BytesRegister))) return false;
foreach (var type in allowOverlappingTypes) {
bool valCompare = reg1.GetType() != compare.GetType() &&
reg1.MemoryAddress == compare.MemoryAddress &&
reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
reg1.GetSpecialAddress() == compare.GetSpecialAddress();
if (toInsert.GetType() == type) return false;
}
bool valCompare = toInsert.GetType() != compare.GetType() &&
toInsert.MemoryAddress == compare.MemoryAddress &&
toInsert.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
toInsert.GetSpecialAddress() == compare.GetSpecialAddress();
return valCompare;

View File

@@ -156,10 +156,11 @@ namespace MewtocolNet {
private void OnRegisterChanged(IRegister o) {
var asInternal = (IRegisterInternal)o;
var asInternal = (BaseRegister)o;
Logger.Log($"{asInternal.GetMewName()} " +
$"{(o.Name != null ? $"({o.Name}) " : "")}" +
$"{asInternal.underlyingSystemType} " +
$"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this);
OnRegisterChangedUpdateProps((IRegisterInternal)o);

View File

@@ -290,20 +290,7 @@ namespace MewtocolNet {
internal void InsertRegistersToMemoryStack (List<BaseRegister> registers) {
//order by address
registers = registers.OrderBy(x => x.GetSpecialAddress()).ToList();
registers = registers.OrderBy(x => x.MemoryAddress).ToList();
//link to memory manager
for (int i = 0, j = 0; i < registers.Count; i++) {
BaseRegister reg = registers[i];
reg.name = $"auto_prop_register_{j + 1}";
//link the memory area to the register
if (memoryManager.LinkRegister(reg)) j++;
}
memoryManager.LinkRegisters(registers);
}
@@ -408,7 +395,7 @@ namespace MewtocolNet {
}
internal void InvokeRegisterChanged(IRegister reg) {
internal void InvokeRegisterChanged(BaseRegister reg) {
RegisterChanged?.Invoke(reg);

View File

@@ -362,13 +362,14 @@ namespace MewtocolNet.RegisterBuilding {
/// <item><term><see cref="bool"/></term><description>Boolean R/X/Y registers</description></item>
/// <item><term><see cref="short"/></term><description>16 bit signed integer</description></item>
/// <item><term><see cref="ushort"/></term><description>16 bit un-signed integer</description></item>
/// <item><term><see cref="Word"/></term><description>16 bit word (2 bytes)</description></item>
/// <item><term><see cref="int"/></term><description>32 bit signed integer</description></item>
/// <item><term><see cref="uint"/></term><description>32 bit un-signed integer</description></item>
/// <item><term><see cref="DWord"/></term><description>32 bit word (4 bytes)</description></item>
/// <item><term><see cref="float"/></term><description>32 bit floating point</description></item>
/// <item><term><see cref="TimeSpan"/></term><description>32 bit time from <see cref="PlcVarType.TIME"/> interpreted as <see cref="TimeSpan"/></description></item>
/// <item><term><see cref="Enum"/></term><description>16 or 32 bit enums</description></item>
/// <item><term><see cref="Enum"/></term><description>16 or 32 bit enums, also supports flags</description></item>
/// <item><term><see cref="string"/></term><description>String of chars, the interface will automatically get the length</description></item>
/// <item><term><see cref="BitArray"/></term><description>As an array of bits</description></item>
/// <item><term><see cref="byte[]"/></term><description>As an array of bytes</description></item>
/// </list>
/// </summary>
@@ -611,7 +612,7 @@ namespace MewtocolNet.RegisterBuilding {
var assembler = new RegisterAssembler(attachedPLC);
var tempRegister = assembler.Assemble(reg.Data);
return await tempRegister.WriteToAnonymousAsync(value);
return await tempRegister.WriteAsync(value);
}
@@ -619,7 +620,7 @@ namespace MewtocolNet.RegisterBuilding {
var assembler = new RegisterAssembler(attachedPLC);
var tempRegister = assembler.Assemble(reg.Data);
return await tempRegister.WriteToAnonymousAsync(value);
return await tempRegister.WriteAsync(value);
}
@@ -627,7 +628,7 @@ namespace MewtocolNet.RegisterBuilding {
var assembler = new RegisterAssembler(attachedPLC);
var tempRegister = assembler.Assemble(reg.Data);
return await tempRegister.ReadFromAnonymousAsync();
return await tempRegister.ReadAsync();
}
@@ -635,7 +636,7 @@ namespace MewtocolNet.RegisterBuilding {
var assembler = new RegisterAssembler(attachedPLC);
var tempRegister = assembler.Assemble(reg.Data);
return (T)await tempRegister.ReadFromAnonymousAsync();
return (T)await tempRegister.ReadAsync();
}

View File

@@ -3,6 +3,7 @@ using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Runtime.InteropServices;
using static MewtocolNet.RegisterBuilding.RBuild;
@@ -49,9 +50,9 @@ namespace MewtocolNet.RegisterBuilding {
//as numeric register with enum target
var underlying = Enum.GetUnderlyingType(data.dotnetVarType);
var enuSize = Marshal.SizeOf(underlying);
int numericSize = Marshal.SizeOf(underlying);
if (enuSize > 4)
if (numericSize > 4)
throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported");
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
@@ -61,6 +62,8 @@ namespace MewtocolNet.RegisterBuilding {
var parameters = new object[] { data.memAddress, data.name };
var instance = (BaseRegister)constr.Invoke(parameters);
instance.RegisterType = numericSize > 2 ? RegisterType.DDT : RegisterType.DT;
generatedInstance = instance;
} else if (registerClassType.IsGenericType) {
@@ -74,23 +77,31 @@ namespace MewtocolNet.RegisterBuilding {
//int _adress, Type _enumType = null, string _name = null
var parameters = new object[] { data.memAddress, data.name };
var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null);
instance.pollLevel = data.pollLevel;
int numericSize = 0;
bool isExtensionTypeDT = typeof(MewtocolExtensionTypeDT).IsAssignableFrom(data.dotnetVarType);
bool isExtensionTypeDDT = typeof(MewtocolExtensionTypeDDT).IsAssignableFrom(data.dotnetVarType);
if (data.dotnetVarType.Namespace == "System") {
numericSize = Marshal.SizeOf(data.dotnetVarType);
} else if(isExtensionTypeDT) {
numericSize = 2;
} else if(isExtensionTypeDDT) {
numericSize = 4;
} else {
throw new NotSupportedException($"The type {data.dotnetVarType} is not supported for NumberRegisters");
}
instance.RegisterType = numericSize > 2 ? RegisterType.DDT : RegisterType.DT;
generatedInstance = instance;
} else if (registerClassType == typeof(BytesRegister) && data.byteSize != null) {
} else if (registerClassType == typeof(ArrayRegister) && data.byteSize != null) {
//-------------------------------------------
//as byte range register
BytesRegister instance = new BytesRegister(data.memAddress, (uint)data.byteSize, data.name);
generatedInstance = instance;
} else if (registerClassType == typeof(BytesRegister) && data.bitSize != null) {
//-------------------------------------------
//as bit range register
BytesRegister instance = new BytesRegister(data.memAddress, (ushort)data.bitSize, data.name);
ArrayRegister instance = new ArrayRegister(data.memAddress, (uint)data.byteSize, data.name);
generatedInstance = instance;
} else if (registerClassType == typeof(StringRegister)) {
@@ -125,8 +136,13 @@ namespace MewtocolNet.RegisterBuilding {
if (collectionTarget != null)
generatedInstance.WithRegisterCollection(collectionTarget);
generatedInstance.attachedInterface = onInterface;
if (data.boundProperty != null)
generatedInstance.WithBoundProperty(new RegisterPropTarget {
BoundProperty = data.boundProperty,
});
generatedInstance.attachedInterface = onInterface;
generatedInstance.underlyingSystemType = data.dotnetVarType;
generatedInstance.pollLevel = data.pollLevel;
return generatedInstance;

View File

@@ -0,0 +1,107 @@
using MewtocolNet.Exceptions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a string
/// </summary>
public class ArrayRegister : BaseRegister {
internal uint addressLength;
/// <summary>
/// The rgisters memory length
/// </summary>
public uint AddressLength => addressLength;
internal uint ReservedBytesSize { get; set; }
internal ushort? ReservedBitSize { get; set; }
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public ArrayRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
internal ArrayRegister(uint _address, uint _reservedByteSize, string _name = null) {
name = _name;
memoryAddress = _address;
ReservedBytesSize = _reservedByteSize;
//calc mem length
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
var byteSize = ReservedBytesSize;
if (ReservedBytesSize % 2 != 0) byteSize++;
RegisterType = RegisterType.DT_BYTE_RANGE;
addressLength = Math.Max((byteSize / 2), 1);
CheckAddressOverflow(memoryAddress, addressLength);
lastValue = null;
}
public override string GetValueString() {
if (Value == null) return "null";
if(Value != null && Value is BitArray bitArr) {
return bitArr.ToBitString();
} else {
return ((byte[])Value).ToHexString("-");
}
}
/// <inheritdoc/>
public override string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + AddressLength - 1).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
/// <inheritdoc/>
public override string GetRegisterString() => "DT";
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/>
internal override void UpdateHoldingValue(object val) {
bool changeTriggerBitArr = val is BitArray bitArr &&
lastValue is BitArray bitArr2 &&
(bitArr.ToBitString() != bitArr2.ToBitString());
bool changeTriggerGeneral = (lastValue?.ToString() != val?.ToString());
if (changeTriggerBitArr || changeTriggerGeneral) {
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
}
}

View File

@@ -19,8 +19,10 @@ namespace MewtocolNet.Registers {
//links to
internal RegisterCollection containedCollection;
internal MewtocolInterface attachedInterface;
internal List<RegisterPropTarget> boundToProps = new List<RegisterPropTarget>();
internal List<RegisterPropTarget> boundProperties = new List<RegisterPropTarget>();
internal Type underlyingSystemType;
internal IMemoryArea underlyingMemory;
internal object lastValue = null;
internal string name;
@@ -40,7 +42,7 @@ namespace MewtocolNet.Registers {
public object Value => lastValue;
/// <inheritdoc/>
public RegisterType RegisterType { get; protected set; }
public RegisterType RegisterType { get; internal set; }
/// <inheritdoc/>
public string Name => name;
@@ -59,9 +61,9 @@ namespace MewtocolNet.Registers {
#endregion
public virtual void ClearValue() => SetValueFromPLC(null);
public virtual void ClearValue() => UpdateHoldingValue(null);
public virtual void SetValueFromPLC(object val) {
internal virtual void UpdateHoldingValue(object val) {
if(lastValue?.ToString() != val?.ToString()) {
@@ -78,7 +80,14 @@ namespace MewtocolNet.Registers {
internal void WithRegisterCollection (RegisterCollection collection) => containedCollection = collection;
internal void WithBoundProperty(RegisterPropTarget propInfo) => boundToProps.Add(propInfo);
internal void WithBoundProperty(RegisterPropTarget propInfo) => boundProperties.Add(propInfo);
internal void WithBoundProperties(IEnumerable<RegisterPropTarget> propInfos) {
foreach (var item in propInfos)
boundProperties.Add(item);
}
#region Read / Write
@@ -86,10 +95,6 @@ namespace MewtocolNet.Registers {
public virtual Task<bool> WriteAsync(object data) => throw new NotImplementedException();
internal virtual Task<bool> WriteToAnonymousAsync (object value) => throw new NotImplementedException();
internal virtual Task<object> ReadFromAnonymousAsync () => throw new NotImplementedException();
#endregion
#region Default accessors
@@ -147,18 +152,36 @@ namespace MewtocolNet.Registers {
else successfulWrites++;
}
internal virtual bool IsSameAddressAndType (BaseRegister toCompare) {
return this.MemoryAddress == toCompare.MemoryAddress &&
this.RegisterType == toCompare.RegisterType &&
this.GetRegisterAddressLen() == toCompare.GetRegisterAddressLen() &&
this.GetSpecialAddress() == toCompare.GetSpecialAddress();
}
internal virtual bool IsSameAddress (BaseRegister toCompare) {
return (this.MemoryAddress == toCompare.MemoryAddress) &&
(this.GetRegisterAddressLen() == toCompare.GetRegisterAddressLen()) &&
(this.GetSpecialAddress() == toCompare.GetSpecialAddress());
}
public override string ToString() {
var sb = new StringBuilder();
sb.Append(GetMewName());
if(Name != null) sb.Append($" ({Name})");
sb.Append($" [{this.GetType().Name}({underlyingSystemType.Name})]");
if (Value != null) sb.Append($" Val: {GetValueString()}");
return sb.ToString();
}
public virtual string ToString(bool additional) {
public virtual string ToString (bool additional) {
if (!additional) return this.ToString();
@@ -166,16 +189,30 @@ namespace MewtocolNet.Registers {
sb.AppendLine($"MewName: {GetMewName()}");
sb.AppendLine($"Name: {Name ?? "Not named"}");
sb.AppendLine($"Value: {GetValueString()}");
sb.AppendLine($"Perf. Reads: {successfulReads}, Writes: {successfulWrites}");
sb.AppendLine($"Register Type: {RegisterType}");
sb.AppendLine($"Address: {GetRegisterWordRangeString()}");
if(this is StringRegister sr) sb.AppendLine($"Reserved: {sr.ReservedSize}, Used: {sr.UsedSize}");
return sb.ToString();
}
public virtual string Explain () {
StringBuilder sb = new StringBuilder();
sb.AppendLine($"MewName: {GetMewName()}");
sb.AppendLine($"Name: {Name ?? "Not named"}");
sb.AppendLine($"Value: {GetValueString()}");
sb.AppendLine($"Perf. Reads: {successfulReads}, Writes: {successfulWrites}");
sb.AppendLine($"Register Type: {RegisterType}");
sb.AppendLine($"Underlying System Type: {underlyingSystemType}");
sb.AppendLine($"Address: {GetRegisterWordRangeString()}");
if (this is StringRegister sr) sb.AppendLine($"Reserved: {sr.ReservedSize}, Used: {sr.UsedSize}");
if (GetSpecialAddress() != null) sb.AppendLine($"SPAddress: {GetSpecialAddress():X1}");
if (GetType().IsGenericType) sb.AppendLine($"Type: NumberRegister<{GetType().GenericTypeArguments[0]}>");
else sb.AppendLine($"Type: {GetType()}");
if(containedCollection != null) sb.AppendLine($"In collection: {containedCollection.GetType()}");
if(boundToProps != null && boundToProps.Count != 0)
sb.AppendLine($"Bound props: {string.Join(",", boundToProps)}");
if (containedCollection != null) sb.AppendLine($"In collection: {containedCollection.GetType()}");
if (boundProperties != null && boundProperties.Count > 0) sb.AppendLine($"Bound props: {string.Join(", ", boundProperties)}");
else sb.AppendLine("No bound properties");
return sb.ToString();

View File

@@ -54,57 +54,6 @@ namespace MewtocolNet.Registers {
}
#region Read / Write
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
return null;
}
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object data) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
return false;
}
internal override async Task<bool> WriteToAnonymousAsync (object value) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var station = attachedInterface.GetStationNumber();
string reqStr = $"%{station}#WCS{BuildMewtocolQuery()}{((bool)value ? "1" : "0")}";
var res = await attachedInterface.SendCommandAsync(reqStr);
return res.Success;
}
internal override async Task<object> ReadFromAnonymousAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var station = attachedInterface.GetStationNumber();
string requeststring = $"%{station}#RCS{BuildMewtocolQuery()}";
var result = await attachedInterface.SendCommandAsync(requeststring);
if (!result.Success) return null;
return result.Response.ParseRCSingleBit();
}
#endregion
/// <inheritdoc/>
public override byte? GetSpecialAddress() => SpecialAddress;

View File

@@ -1,203 +0,0 @@
using MewtocolNet.Exceptions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
/// <summary>
/// Defines a register containing a string
/// </summary>
public class BytesRegister : BaseRegister {
internal uint addressLength;
/// <summary>
/// The rgisters memory length
/// </summary>
public uint AddressLength => addressLength;
internal uint ReservedBytesSize { get; set; }
internal ushort? ReservedBitSize { get; set; }
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public BytesRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
internal BytesRegister(uint _address, uint _reservedByteSize, string _name = null) {
name = _name;
memoryAddress = _address;
ReservedBytesSize = _reservedByteSize;
//calc mem length
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
var byteSize = ReservedBytesSize;
if (ReservedBytesSize % 2 != 0) byteSize++;
RegisterType = RegisterType.DT_BYTE_RANGE;
addressLength = Math.Max((byteSize / 2), 1);
CheckAddressOverflow(memoryAddress, addressLength);
lastValue = null;
}
public BytesRegister(uint _address, ushort _reservedBitSize, string _name = null) {
name = _name;
memoryAddress = _address;
ReservedBytesSize = (uint)Math.Max(1, _reservedBitSize / 8);
ReservedBitSize = _reservedBitSize;
//calc mem length
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
var byteSize = ReservedBytesSize;
if (ReservedBytesSize % 2 != 0) byteSize++;
RegisterType = RegisterType.DT_BYTE_RANGE;
addressLength = Math.Max((byteSize / 2), 1);
CheckAddressOverflow(memoryAddress, addressLength);
lastValue = null;
}
public override string GetValueString() {
if (Value == null) return "null";
if(Value != null && Value is BitArray bitArr) {
return bitArr.ToBitString();
} else {
return ((byte[])Value).ToHexString("-");
}
}
/// <inheritdoc/>
public override string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + AddressLength - 1).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
/// <inheritdoc/>
public override void SetValueFromPLC (object val) {
bool changeTriggerBitArr = val is BitArray bitArr &&
lastValue is BitArray bitArr2 &&
(bitArr.ToBitString() != bitArr2.ToBitString());
bool changeTriggerGeneral = (lastValue?.ToString() != val?.ToString());
if (changeTriggerBitArr || changeTriggerGeneral) {
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
/// <inheritdoc/>
public override string GetRegisterString() => "DT";
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var res = await underlyingMemory.ReadRegisterAsync(this);
if (!res) return null;
var bytes = underlyingMemory.GetUnderlyingBytes(this);
return SetValueFromBytes(bytes);
}
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object data) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
byte[] encoded;
if (ReservedBitSize != null) {
encoded = PlcValueParser.Encode(this, (BitArray)data);
} else {
encoded = PlcValueParser.Encode(this, (byte[])data);
}
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
if (res) {
AddSuccessWrite();
SetValueFromPLC(data);
}
return res;
}
internal override object SetValueFromBytes(byte[] bytes) {
AddSuccessRead();
object parsed;
if (ReservedBitSize != null) {
parsed = PlcValueParser.Parse<BitArray>(this, bytes);
} else {
parsed = PlcValueParser.Parse<byte[]>(this, bytes);
}
SetValueFromPLC(parsed);
return parsed;
}
internal override async Task<bool> WriteToAnonymousAsync(object value) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
return await attachedInterface.WriteByteRange((int)MemoryAddress, (byte[])value);
}
internal override async Task<object> ReadFromAnonymousAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2, false);
if (res == null) return null;
return res;
}
}
}

View File

@@ -24,8 +24,6 @@ namespace MewtocolNet {
// setters
void SetValueFromPLC(object value);
void ClearValue();
// Accessors

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
@@ -62,21 +63,6 @@ namespace MewtocolNet.Registers {
}
/// <inheritdoc/>
public override void SetValueFromPLC(object val) {
if (lastValue?.ToString() != val?.ToString()) {
if (val != null) lastValue = (T)val;
else lastValue = null;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
/// <inheritdoc/>
public override string BuildMewtocolQuery() {
@@ -110,6 +96,12 @@ namespace MewtocolNet.Registers {
}
if (Value != null && typeof(T) == typeof(Word)) {
return $"{Value} [{((Word)Value).ToStringBitsPlc()}]";
}
if (Value != null && typeof(T).IsEnum) {
var underlying = Enum.GetUnderlyingType(typeof(T));
@@ -126,36 +118,43 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
/// <inheritdoc/>
public override async Task<bool> WriteAsync (object value) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var encoded = PlcValueParser.Encode(this, (T)value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
if(res) {
//find the underlying memory
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null)
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
AddSuccessWrite();
UpdateHoldingValue(value);
}
return res;
}
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var res = await underlyingMemory.ReadRegisterAsync(this);
if (!res) return null;
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2, false);
if (res == null) return null;
var bytes = underlyingMemory.GetUnderlyingBytes(this);
return SetValueFromBytes(bytes);
}
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object data) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var encoded = PlcValueParser.Encode(this, (T)data);
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
if (res) {
AddSuccessWrite();
SetValueFromPLC(data);
}
return res;
return SetValueFromBytes(res);
}
@@ -164,31 +163,23 @@ namespace MewtocolNet.Registers {
AddSuccessRead();
var parsed = PlcValueParser.Parse<T>(this, bytes);
SetValueFromPLC(parsed);
UpdateHoldingValue(parsed);
return parsed;
}
internal override async Task<bool> WriteToAnonymousAsync (object value) {
internal override void UpdateHoldingValue(object val) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
if (lastValue?.ToString() != val?.ToString()) {
var encoded = PlcValueParser.Encode(this, (T)value);
return await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
if (val != null) lastValue = (T)val;
else lastValue = null;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
internal override async Task<object> ReadFromAnonymousAsync () {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2, false);
if (res == null) return null;
return PlcValueParser.Parse<T>(this, res);
}
}

View File

@@ -51,21 +51,7 @@ namespace MewtocolNet.Registers {
}
/// <inheritdoc/>
public override string GetValueString() => $"'{Value}'";
/// <inheritdoc/>
public override void SetValueFromPLC (object val) {
if (val == null || !val.Equals(lastValue)) {
lastValue = (string)val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
public override string GetValueString() => Value == null ? "null" : $"'{Value}'";
/// <inheritdoc/>
public override string GetRegisterString() => "DT";
@@ -97,76 +83,17 @@ namespace MewtocolNet.Registers {
}
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
internal override void UpdateHoldingValue(object val) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
if ((val == null && lastValue != null) || val != lastValue) {
if (!isCalibratedFromPlc) await CalibrateFromPLC();
lastValue = val;
var res = await underlyingMemory.ReadRegisterAsync(this);
if (!res) return null;
var bytes = underlyingMemory.GetUnderlyingBytes(this);
return SetValueFromBytes(bytes);
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object data) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
if (!isCalibratedFromPlc) await CalibrateFromPLC();
var encoded = PlcValueParser.Encode(this, (string)data);
var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
if (res) {
AddSuccessWrite();
SetValueFromPLC(data);
}
return res;
}
internal override object SetValueFromBytes(byte[] bytes) {
AddSuccessRead();
var parsed = PlcValueParser.Parse<string>(this, bytes);
SetValueFromPLC(parsed);
return parsed;
}
internal override async Task<bool> WriteToAnonymousAsync(object value) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
if (!isCalibratedFromPlc) await CalibrateFromPLC();
var encoded = PlcValueParser.Encode(this, (string)value);
return await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
}
internal override async Task<object> ReadFromAnonymousAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
if (!isCalibratedFromPlc) await CalibrateFromPLC();
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return null;
return PlcValueParser.Parse<string>(this, res);
}
}

View File

@@ -72,12 +72,12 @@ namespace MewtocolNet.TypeConversion {
ToRaw = (reg, value) => BitConverter.GetBytes(value),
},
//default ushort DT conversion
new PlcTypeConversion<ushort>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<ushort>),
//default Word DT conversion
new PlcTypeConversion<Word>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<Word>),
PlcVarType = PlcVarType.WORD,
FromRaw = (reg, bytes) => BitConverter.ToUInt16(bytes, 0),
ToRaw = (reg, value) => BitConverter.GetBytes(value),
FromRaw = (reg, bytes) => new Word(bytes),
ToRaw = (reg, value) => value.ToByteArray(),
},
//default int DDT conversion
@@ -96,12 +96,12 @@ namespace MewtocolNet.TypeConversion {
ToRaw = (reg, value) => BitConverter.GetBytes(value),
},
//default uint DDT conversion
new PlcTypeConversion<uint>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<uint>),
//default DWord DDT conversion
new PlcTypeConversion<DWord>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<DWord>),
PlcVarType = PlcVarType.DWORD,
FromRaw = (reg, bytes) => BitConverter.ToUInt32(bytes, 0),
ToRaw = (reg, value) => BitConverter.GetBytes(value),
FromRaw = (reg, bytes) => new DWord(bytes),
ToRaw = (reg, value) => value.ToByteArray(),
},
//default float DDT conversion
@@ -134,7 +134,7 @@ namespace MewtocolNet.TypeConversion {
//default byte array DT Range conversion, direct pass through
new PlcTypeConversion<byte[]>(RegisterType.DT_BYTE_RANGE) {
HoldingRegisterType = typeof(BytesRegister),
HoldingRegisterType = typeof(ArrayRegister),
FromRaw = (reg, bytes) => bytes,
ToRaw = (reg, value) => value,
},
@@ -182,37 +182,6 @@ namespace MewtocolNet.TypeConversion {
},
},
//default bit array <=> byte array conversion
new PlcTypeConversion<BitArray>(RegisterType.DT_BYTE_RANGE) {
HoldingRegisterType = typeof(BytesRegister),
FromRaw = (reg, bytes) => {
var byteReg = (BytesRegister)reg;
BitArray bitAr = new BitArray(bytes);
bitAr.Length = (int)byteReg.ReservedBitSize;
return bitAr;
},
ToRaw = (reg, value) => {
byte[] ret = new byte[(value.Length - 1) / 8 + 1];
value.CopyTo(ret, 0);
if(ret.Length % 2 != 0) {
var lst = ret.ToList();
lst.Add(0);
ret = lst.ToArray();
}
return ret;
},
},
};
}

View File

@@ -19,6 +19,8 @@ namespace MewtocolNet.UnderlyingRegisters {
internal List<BaseRegister> linkedRegisters = new List<BaseRegister>();
internal Dictionary<BaseRegister, List<BaseRegister>> crossRegisterBindings = new Dictionary<BaseRegister, List<BaseRegister>>();
public ulong AddressStart => addressStart;
public ulong AddressEnd => addressEnd;
@@ -40,7 +42,7 @@ namespace MewtocolNet.UnderlyingRegisters {
//copy old bytes to new array
var offset = (int)(oldFrom - addFrom) * 2;
oldUnderlying.CopyTo(oldUnderlying, offset);
oldUnderlying.CopyTo(underlyingBytes, offset);
addressStart = addFrom;
addressEnd = addTo;
@@ -61,18 +63,6 @@ namespace MewtocolNet.UnderlyingRegisters {
}
public async Task<bool> ReadRegisterAsync (BaseRegister reg) {
return await RequestByteReadAsync(reg.MemoryAddress, reg.MemoryAddress + reg.GetRegisterAddressLen() - 1);
}
public async Task<bool> WriteRegisterAsync (BaseRegister reg, byte[] bytes) {
return await RequestByteWriteAsync(reg.MemoryAddress, bytes);
}
internal async Task<bool> RequestByteReadAsync (ulong addStart, ulong addEnd) {
await CheckDynamicallySizedRegistersAsync();
@@ -93,24 +83,6 @@ namespace MewtocolNet.UnderlyingRegisters {
}
internal async Task<bool> RequestByteWriteAsync(ulong addStart, byte[] bytes) {
var station = mewInterface.GetStationNumber();
var addEnd = addStart + ((ulong)bytes.Length / 2) - 1;
string requeststring = $"%{station}#WD{GetMewtocolIdent(addStart, addEnd)}{bytes.ToHexString()}";
var result = await mewInterface.SendCommandAsync(requeststring);
if (result.Success) {
SetUnderlyingBytes(bytes, addStart);
}
return result.Success;
}
public byte[] GetUnderlyingBytes(BaseRegister reg) {
int byteLen = (int)(reg.GetRegisterAddressLen() * 2);

View File

@@ -7,9 +7,7 @@ namespace MewtocolNet.UnderlyingRegisters {
byte[] GetUnderlyingBytes(BaseRegister reg);
Task<bool> ReadRegisterAsync(BaseRegister reg);
Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes);
void SetUnderlyingBytes(BaseRegister reg, byte[] bytes);
void UpdateAreaRegisterValues();

View File

@@ -1,9 +1,12 @@
using MewtocolNet.Registers;
using MewtocolNet.Helpers;
using MewtocolNet.Registers;
using MewtocolNet.SetupClasses;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
@@ -44,7 +47,52 @@ namespace MewtocolNet.UnderlyingRegisters {
}
internal bool LinkRegister (BaseRegister reg) {
internal void LinkRegisters (List<BaseRegister> registers = null) {
//for self calling
if (registers == null) registers = GetAllRegisters().ToList();
//pre combine
var groupedByAdd = registers
.GroupBy(x => new {
x.MemoryAddress,
len = x.GetRegisterAddressLen(),
spadd = x.GetSpecialAddress(),
});
var filteredRegisters = new List<BaseRegister>();
var propertyLookupTable = new Dictionary<PropertyInfo, BaseRegister>();
foreach (var addressGroup in groupedByAdd) {
var ordered = addressGroup.OrderBy(x => x.pollLevel);
var highestPollLevel = ordered.Max(x => x.pollLevel);
var distinctByUnderlyingType =
ordered.GroupBy(x => x.underlyingSystemType).ToList();
foreach (var underlyingTypeGroup in distinctByUnderlyingType) {
foreach (var register in underlyingTypeGroup) {
register.pollLevel = highestPollLevel;
var alreadyAdded = filteredRegisters
.FirstOrDefault(x => x.underlyingSystemType == register.underlyingSystemType);
if(alreadyAdded == null) {
filteredRegisters.Add(register);
} else {
alreadyAdded.WithBoundProperties(register.boundProperties);
}
}
}
}
foreach (var reg in filteredRegisters) {
TestPollLevelExistence(reg);
@@ -52,14 +100,16 @@ namespace MewtocolNet.UnderlyingRegisters {
case RegisterType.X:
case RegisterType.Y:
case RegisterType.R:
return AddWRArea(reg);
AddToWRArea(reg);
break;
case RegisterType.DT:
case RegisterType.DDT:
case RegisterType.DT_BYTE_RANGE:
return AddDTArea(reg);
AddToDTArea(reg);
break;
}
return false;
}
}
@@ -88,7 +138,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
private bool AddWRArea (BaseRegister insertReg) {
private bool AddToWRArea (BaseRegister insertReg) {
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
@@ -115,9 +165,6 @@ namespace MewtocolNet.UnderlyingRegisters {
if(existingLinkedRegister != null) {
foreach (var prop in insertReg.boundToProps)
existingLinkedRegister.WithBoundProperty(prop);
return false;
} else {
@@ -147,7 +194,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
private bool AddDTArea (BaseRegister insertReg) {
private void AddToDTArea (BaseRegister insertReg) {
uint regInsAddStart = insertReg.MemoryAddress;
uint regInsAddEnd = insertReg.MemoryAddress + insertReg.GetRegisterAddressLen() - 1;
@@ -159,26 +206,13 @@ namespace MewtocolNet.UnderlyingRegisters {
foreach (var dtArea in dataAreas) {
bool matchingAddress = regInsAddStart >= dtArea.AddressStart &&
regInsAddEnd <= dtArea.addressEnd;
bool addressInsideArea = regInsAddStart >= dtArea.AddressStart &&
regInsAddEnd <= dtArea.AddressEnd;
//found matching
if (matchingAddress) {
//check if the area has registers linked that are overlapping (not matching)
var foundDupe = dtArea.linkedRegisters
.FirstOrDefault(x => x.CompareIsDuplicateNonCast(insertReg, allowByteRegDupes));
if (foundDupe != null) {
throw new NotSupportedException(
message: $"Can't have registers of different types at the same referenced plc address: " +
$"{insertReg.PLCAddressName} ({insertReg.GetType()}) <=> " +
$"{foundDupe.PLCAddressName} ({foundDupe.GetType()})"
);
}
if (addressInsideArea) {
//found an area that is already existing where the register can fit into
targetArea = dtArea;
break;
}
@@ -208,7 +242,6 @@ namespace MewtocolNet.UnderlyingRegisters {
//expand the boundaries for the area to include the new adjacent area
dtArea.BoundaryUdpdate(addrFrom: regInsAddStart);
targetArea = dtArea;
break;
@@ -218,6 +251,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
//create a new area
if (targetArea == null) {
targetArea = new DTArea(mewInterface) {
@@ -227,45 +261,27 @@ namespace MewtocolNet.UnderlyingRegisters {
};
targetArea.BoundaryUdpdate();
dataAreas.Add(targetArea);
}
insertReg.underlyingMemory = targetArea;
var existingLinkedRegister = targetArea.linkedRegisters
.FirstOrDefault(x => x.CompareIsDuplicate(insertReg));
if (existingLinkedRegister != null) {
foreach (var prop in insertReg.boundToProps)
existingLinkedRegister.WithBoundProperty(prop);
return false;
} else {
targetArea.linkedRegisters.Add(insertReg);
return true;
if (insertReg.name == null) {
insertReg.name = $"auto_{Guid.NewGuid().ToString("N")}";
}
Console.WriteLine($"Adding linked register: {insertReg}");
targetArea.linkedRegisters.Add(insertReg);
return;
}
internal void MergeAndSizeDataAreas () {
//merge gaps that the algorithm didn't catch be rerunning the register attachment
foreach (var pLevel in pollLevels) {
var allDataAreaRegisters = pLevel.dataAreas.SelectMany(x => x.linkedRegisters).ToList();
var dataAreas = new List<DTArea>(allDataAreaRegisters.Capacity);
foreach (var reg in allDataAreaRegisters)
AddDTArea(reg);
}
LinkRegisters();
}
@@ -309,7 +325,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
//update registers in poll level
foreach (var dtArea in pollLevel.dataAreas) {
foreach (var dtArea in pollLevel.dataAreas.ToArray()) {
//set the whole memory area at once
await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
@@ -346,7 +362,7 @@ namespace MewtocolNet.UnderlyingRegisters {
sb.AppendLine($"Optimization distance: {maxOptimizationDistance}");
sb.AppendLine();
sb.AppendLine($"---- DT Area ----");
sb.AppendLine($"---- DT Areas: ----");
foreach (var area in pollLevel.dataAreas) {
@@ -358,7 +374,7 @@ namespace MewtocolNet.UnderlyingRegisters {
foreach (var reg in area.linkedRegisters) {
sb.AppendLine($"{reg.ToString(true)}");
sb.AppendLine($"{reg.Explain()}");
}
@@ -372,7 +388,7 @@ namespace MewtocolNet.UnderlyingRegisters {
foreach (var reg in area.linkedRegisters) {
sb.AppendLine($"{reg.ToString(true)}");
sb.AppendLine($"{reg.Explain()}");
}
@@ -386,7 +402,7 @@ namespace MewtocolNet.UnderlyingRegisters {
foreach (var reg in area.linkedRegisters) {
sb.AppendLine($"{reg.ToString(true)}");
sb.AppendLine($"{reg.Explain()}");
}
@@ -400,7 +416,7 @@ namespace MewtocolNet.UnderlyingRegisters {
foreach (var reg in area.linkedRegisters) {
sb.AppendLine($"{reg.ToString(true)}");
sb.AppendLine($"{reg.Explain()}");
}

View File

@@ -29,6 +29,10 @@ namespace MewtocolNet.UnderlyingRegisters {
}
public void SetUnderlyingBytes(BaseRegister reg, byte[] bytes) {
}
public byte[] GetUnderlyingBytes(BaseRegister reg) {