Restricted register generics

This commit is contained in:
Felix Weiß
2023-07-18 00:51:33 +02:00
parent eb70dac5a8
commit 8fdff367f6
17 changed files with 447 additions and 180 deletions

View File

@@ -393,7 +393,7 @@ namespace MewtocolNet {
Register = reg, Register = reg,
PreviousValue = preValue, PreviousValue = preValue,
PreviousValueString = preValueString, PreviousValueString = preValueString,
Value = reg.Value, Value = reg.ValueObj,
}); });
} }

View File

@@ -108,6 +108,9 @@ namespace MewtocolNet {
/// <returns></returns> /// <returns></returns>
public async Task<bool> WriteByteRange(int start, byte[] byteArr) { public async Task<bool> WriteByteRange(int start, byte[] byteArr) {
if (byteArr == null)
throw new ArgumentNullException(nameof(byteArr));
string byteString = byteArr.ToHexString(); string byteString = byteArr.ToHexString();
var wordLength = byteArr.Length / 2; var wordLength = byteArr.Length / 2;

View File

@@ -37,7 +37,7 @@ namespace MewtocolNet.RegisterAttributes {
if (value is IRegister reg) { if (value is IRegister reg) {
privateField = (T)reg.Value; privateField = (T)reg.ValueObj;
return; return;
} }

View File

@@ -1,4 +1,5 @@
using MewtocolNet.Registers; using System;
using MewtocolNet.Registers;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MewtocolNet.RegisterBuilding { namespace MewtocolNet.RegisterBuilding {
@@ -33,16 +34,18 @@ namespace MewtocolNet.RegisterBuilding {
/// <returns>True if success</returns> /// <returns>True if success</returns>
public async Task<bool> WriteToAsync<T>(T value) { public async Task<bool> WriteToAsync<T>(T value) {
try { throw new NotImplementedException();
var tempRegister = AssembleTemporaryRegister<T>(); //try {
return await tempRegister.WriteAsync(value);
} catch { // var tempRegister = AssembleTemporaryRegister<T>();
// return await tempRegister.WriteAsync(value);
throw; //} catch {
} // throw;
//}
} }
@@ -52,16 +55,18 @@ namespace MewtocolNet.RegisterBuilding {
/// <returns>The value read or null if failed</returns> /// <returns>The value read or null if failed</returns>
public async Task<T> ReadFromAsync<T>() { public async Task<T> ReadFromAsync<T>() {
try { throw new NotImplementedException();
var tempRegister = AssembleTemporaryRegister<T>(); //try {
return (T)await tempRegister.ReadAsync();
} catch { // var tempRegister = AssembleTemporaryRegister<T>();
// return (T)await tempRegister.ReadAsync();
throw; //} catch {
} // throw;
//}
} }

View File

@@ -16,7 +16,7 @@ namespace MewtocolNet.RegisterBuilding {
internal RBuildBase(MewtocolInterface plc) => attachedPLC = plc; internal RBuildBase(MewtocolInterface plc) => attachedPLC = plc;
internal List<StepData> unfinishedList = new List<StepData>(); internal List<BaseStepData> unfinishedList = new List<BaseStepData>();
#region Parser stage #region Parser stage
@@ -50,7 +50,7 @@ namespace MewtocolNet.RegisterBuilding {
internal string hardFailReason; internal string hardFailReason;
internal StepData stepData; internal BaseStepData stepData;
} }
@@ -277,9 +277,7 @@ namespace MewtocolNet.RegisterBuilding {
res.stepData.originalParseStr = plcAddrName; res.stepData.originalParseStr = plcAddrName;
res.stepData.buildSource = RegisterBuildSource.Manual; res.stepData.buildSource = RegisterBuildSource.Manual;
unfinishedList.Add(res.stepData); return new StepData().Map(res.stepData);
return res.stepData;
} else if (res.state == ParseResultState.FailedHard) { } else if (res.state == ParseResultState.FailedHard) {
@@ -305,7 +303,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <typeparam name="T"> /// <typeparam name="T">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' /> /// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </typeparam> /// </typeparam>
public TypedRegister AsType<T>() { internal TypedRegister AsType<T>() {
if (!typeof(T).IsAllowedPlcCastingType()) { if (!typeof(T).IsAllowedPlcCastingType()) {
@@ -326,7 +324,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <param name="type"> /// <param name="type">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' /> /// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </param> /// </param>
public TypedRegister AsType(Type type) { internal TypedRegister AsType(Type type) {
//was ranged syntax array build //was ranged syntax array build
if (Data.wasAddressStringRangeBased && type.IsArray && type.GetArrayRank() == 1) { if (Data.wasAddressStringRangeBased && type.IsArray && type.GetArrayRank() == 1) {
@@ -395,7 +393,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <summary> /// <summary>
/// Sets the register type as a predefined <see cref="PlcVarType"/> /// Sets the register type as a predefined <see cref="PlcVarType"/>
/// </summary> /// </summary>
public TypedRegister AsType(PlcVarType type) { internal TypedRegister AsType(PlcVarType type) {
Data.dotnetVarType = type.GetDefaultDotnetType(); Data.dotnetVarType = type.GetDefaultDotnetType();
@@ -420,7 +418,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <item><term>DWORD</term><description>32 bit double word interpreted as <see cref="uint"/></description></item> /// <item><term>DWORD</term><description>32 bit double word interpreted as <see cref="uint"/></description></item>
/// </list> /// </list>
/// </summary> /// </summary>
public TypedRegister AsType(string type) { internal TypedRegister AsType(string type) {
var regexString = new Regex(@"^STRING *\[(?<len>[0-9]*)\]$", RegexOptions.IgnoreCase); var regexString = new Regex(@"^STRING *\[(?<len>[0-9]*)\]$", RegexOptions.IgnoreCase);
var regexArray = new Regex(@"^ARRAY *\[(?<S1>[0-9]*)..(?<E1>[0-9]*)(?:\,(?<S2>[0-9]*)..(?<E2>[0-9]*))?(?:\,(?<S3>[0-9]*)..(?<E3>[0-9]*))?\] *OF {1,}(?<t>.*)$", RegexOptions.IgnoreCase); var regexArray = new Regex(@"^ARRAY *\[(?<S1>[0-9]*)..(?<E1>[0-9]*)(?:\,(?<S2>[0-9]*)..(?<E2>[0-9]*))?(?:\,(?<S3>[0-9]*)..(?<E3>[0-9]*))?\] *OF {1,}(?<t>.*)$", RegexOptions.IgnoreCase);
@@ -520,7 +518,7 @@ namespace MewtocolNet.RegisterBuilding {
/// ARRAY [0..2, 0..3, 0..4] OF INT = <c>AsTypeArray&lt;short[,,]&gt;(3,4,5)</c><br/> /// ARRAY [0..2, 0..3, 0..4] OF INT = <c>AsTypeArray&lt;short[,,]&gt;(3,4,5)</c><br/>
/// ARRAY [5..6, 0..2] OF DWORD = <c>AsTypeArray&lt;DWord[,]&gt;(2, 3)</c><br/> /// ARRAY [5..6, 0..2] OF DWORD = <c>AsTypeArray&lt;DWord[,]&gt;(2, 3)</c><br/>
/// </example> /// </example>
public TypedRegister AsTypeArray<T>(params int[] indicies) { internal TypedRegister AsTypeArray<T>(params int[] indicies) {
if (!typeof(T).IsArray) if (!typeof(T).IsArray)
throw new NotSupportedException($"The type {typeof(T)} was no array"); throw new NotSupportedException($"The type {typeof(T)} was no array");

View File

@@ -15,6 +15,8 @@ namespace MewtocolNet.RegisterBuilding {
#region String parse stage #region String parse stage
//at runtime constructor
/// <summary> /// <summary>
/// Starts the register builder for a new mewtocol address <br/> /// Starts the register builder for a new mewtocol address <br/>
/// Examples: /// Examples:
@@ -26,6 +28,8 @@ namespace MewtocolNet.RegisterBuilding {
var data = ParseAddress(plcAddrName, name); var data = ParseAddress(plcAddrName, name);
unfinishedList.Add(data);
return new SAddress { return new SAddress {
Data = data, Data = data,
builder = this, builder = this,
@@ -33,6 +37,68 @@ namespace MewtocolNet.RegisterBuilding {
} }
//struct constructor
public SAddressStruct<T> Address<T>(string plcAddrName, string name = null) where T : struct {
var data = ParseAddress(plcAddrName, name);
data.dotnetVarType = typeof(T);
unfinishedList.Add(data);
return new SAddressStruct<T>(data) {
builder = this,
};
}
public SAddressString<T> AddressString<T>(string plcAddrName, int sizeHint, string name = null) where T : class {
var data = ParseAddress(plcAddrName, name);
data.dotnetVarType = typeof(T);
unfinishedList.Add(data);
if (typeof(T).IsArray) {
return new SAddressString<T>(data, true) {
Data = data,
builder = this,
};
}
return new SAddressString<T>(data) {
builder = this,
};
}
public SAddressArray<T> AddressArray<T>(string plcAddrName, string name = null) where T : class {
var data = ParseAddress(plcAddrName, name);
data.dotnetVarType = typeof(T);
unfinishedList.Add(data);
if (typeof(T).IsArray) {
return new SAddressArray<T>(data, true) {
Data = data,
builder = this,
};
}
return new SAddressArray<T>(data) {
builder = this,
};
}
//internal use only, adds a type definition (for use when building from attibute) //internal use only, adds a type definition (for use when building from attibute)
internal SAddress AddressFromAttribute(string plcAddrName, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) { internal SAddress AddressFromAttribute(string plcAddrName, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) {
@@ -52,6 +118,7 @@ namespace MewtocolNet.RegisterBuilding {
#region Typing stage #region Typing stage
//non generic
public new class SAddress : RBuildBase.SAddress { public new class SAddress : RBuildBase.SAddress {
public new TypedRegister AsType<T>() => new TypedRegister().Map(base.AsType<T>()); public new TypedRegister AsType<T>() => new TypedRegister().Map(base.AsType<T>());
@@ -64,9 +131,95 @@ namespace MewtocolNet.RegisterBuilding {
public new TypedRegister AsTypeArray<T>(params int[] indicies) => new TypedRegister().Map(base.AsTypeArray<T>(indicies)); public new TypedRegister AsTypeArray<T>(params int[] indicies) => new TypedRegister().Map(base.AsTypeArray<T>(indicies));
}
//structs
public class SAddressStruct<T> : RBuildBase.SAddress where T : struct {
internal SAddressStruct(StepData data) {
this.Data = data;
this.Map(AsType(typeof(T)));
}
internal SAddressStruct(StepData data, bool arrayed) {
this.Data = data;
}
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IRegister<T>)o));
}
} }
//strings
public class SAddressString<T> : RBuildBase.SAddress where T : class {
internal SAddressString(StepData data) {
this.Data = data;
this.Map(AsType(typeof(T)));
}
internal SAddressString(StepData data, bool arrayed) {
this.Data = data;
}
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IStringRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IStringRegister<T>)o));
}
}
//arrays
public class SAddressArray<T> : RBuildBase.SAddress {
internal SAddressArray(StepData data) {
this.Data = data;
this.Map(AsType(typeof(T)));
}
internal SAddressArray(StepData data, bool arrayed) {
this.Data = data;
}
public TypedRegister Indicies(params int[] indicies) => new TypedRegister().Map(base.AsTypeArray<T>(indicies));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IArrayRegister<T>> registerOut) {
Data.registerOut = new Action<object>(o => registerOut((IArrayRegister<T>)o));
}
}
#endregion #endregion
#region Typing size hint #region Typing size hint
@@ -83,7 +236,7 @@ namespace MewtocolNet.RegisterBuilding {
/// </summary> /// </summary>
public void Out(Action<IRegister> registerOut) { public void Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut; Data.registerOut = (Action<object>)registerOut;
} }
@@ -103,7 +256,7 @@ namespace MewtocolNet.RegisterBuilding {
/// </summary> /// </summary>
public void Out(Action<IRegister> registerOut) { public void Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut; Data.registerOut = (Action<object>)registerOut;
} }
@@ -118,7 +271,7 @@ namespace MewtocolNet.RegisterBuilding {
/// </summary> /// </summary>
public void Out(Action<IRegister> registerOut) { public void Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut; Data.registerOut = (Action<object>)registerOut;
} }

View File

@@ -28,7 +28,8 @@ namespace MewtocolNet.RegisterBuilding {
var generatedInstance = Assemble(data); var generatedInstance = Assemble(data);
generatedInstance.autoGenerated = flagAutoGenerated; generatedInstance.autoGenerated = flagAutoGenerated;
data.registerOut?.Invoke(generatedInstance);
data?.InvokeBuilt(generatedInstance);
generatedInstances.Add(generatedInstance); generatedInstances.Add(generatedInstance);
@@ -38,7 +39,7 @@ namespace MewtocolNet.RegisterBuilding {
} }
internal Register Assemble(StepData data) { internal Register Assemble(BaseStepData data) {
Register generatedInstance = null; Register generatedInstance = null;
@@ -114,7 +115,7 @@ namespace MewtocolNet.RegisterBuilding {
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
Type paramedClass = typeof(SingleRegister<>).MakeGenericType(data.dotnetVarType); Type paramedClass = typeof(StructRegister<>).MakeGenericType(data.dotnetVarType);
ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] { ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] {
typeof(uint), typeof(uint) ,typeof(string) typeof(uint), typeof(uint) ,typeof(string)
}, null); }, null);

View File

@@ -6,10 +6,9 @@ using System.Reflection;
namespace MewtocolNet.RegisterBuilding { namespace MewtocolNet.RegisterBuilding {
internal class StepData { internal class BaseStepData {
//for referencing the output at builder level internal Action<object> registerOut;
internal Action<IRegister> registerOut;
internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous; internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous;
@@ -34,6 +33,40 @@ namespace MewtocolNet.RegisterBuilding {
internal string typeDef; internal string typeDef;
internal void InvokeBuilt(Register reg) {
registerOut.Invoke(reg);
//var selftype = this.GetType();
//if ((selftype.IsGenericType && selftype.GetGenericTypeDefinition() == typeof(StepData<>))) {
// var field = selftype.GetField("registerOut", BindingFlags.NonPublic | BindingFlags.Instance);
// var generic = typeof(IRegister<>).MakeGenericType()
// var action = Action.CreateDelegate(typeof(IRegister<T>));
// field.SetValue(this,);
//}
}
}
internal class StepData<T> : BaseStepData {
//for referencing the output at builder level
//internal Action<IRegister<T>> registerOut;
}
internal class StepData : BaseStepData {
//for referencing the output at builder level
//internal Action<IRegister> registerOut;
} }
} }

View File

@@ -10,7 +10,7 @@ namespace MewtocolNet.Registers {
/// <summary> /// <summary>
/// Defines a register containing a string /// Defines a register containing a string
/// </summary> /// </summary>
public class ArrayRegister<T> : Register { public class ArrayRegister<T> : Register, IArrayRegister<T> {
internal int[] indices; internal int[] indices;
@@ -21,6 +21,8 @@ namespace MewtocolNet.Registers {
/// </summary> /// </summary>
public uint AddressLength => addressLength; public uint AddressLength => addressLength;
public T[] Value => (T[])ValueObj;
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")] [Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public ArrayRegister() => public ArrayRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
@@ -47,16 +49,16 @@ namespace MewtocolNet.Registers {
public override string GetValueString() { public override string GetValueString() {
if (Value == null) return "null"; if (ValueObj == null) return "null";
if(typeof(T) == typeof(byte[])) { if(typeof(T) == typeof(byte[])) {
return ((byte[])Value).ToHexString("-"); return ((byte[])ValueObj).ToHexString("-");
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
var valueIenum = (IEnumerable)Value; var valueIenum = (IEnumerable)ValueObj;
foreach (var el in valueIenum) { foreach (var el in valueIenum) {
@@ -64,7 +66,7 @@ namespace MewtocolNet.Registers {
} }
return ArrayToString((Array)Value); return ArrayToString((Array)ValueObj);
} }
@@ -75,9 +77,9 @@ namespace MewtocolNet.Registers {
public override uint GetRegisterAddressLen() => AddressLength; public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<bool> WriteAsync(object value) { public async Task<bool> WriteAsync(T value) {
var encoded = PlcValueParser.Encode(this, (T)value); var encoded = PlcValueParser.EncodeArray(this, value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
if (res) { if (res) {
@@ -99,18 +101,18 @@ namespace MewtocolNet.Registers {
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<object> ReadAsync() { async Task<T[]> IArrayRegister<T>.ReadAsync() {
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return null; if (res == null) throw new Exception();
//var matchingReg = attachedInterface.memoryManager.GetAllRegisters() var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
//.FirstOrDefault(x => x.IsSameAddressAndType(this)); .FirstOrDefault(x => x.IsSameAddressAndType(this));
//if (matchingReg != null) if (matchingReg != null)
// matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
return SetValueFromBytes(res); return (T[])SetValueFromBytes(res);
} }

View File

@@ -0,0 +1,26 @@
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
public interface IArrayRegister<T> : IRegister {
/// <summary>
/// The current value of the register
/// </summary>
T[] Value { get; }
/// <summary>
/// Reads the register value async from the plc
/// </summary>
/// <returns>The register value</returns>
Task<T[]> ReadAsync();
/// <summary>
/// Writes the register content async to the plc
/// </summary>
/// <returns>True if successfully set</returns>
Task<bool> WriteAsync(T data);
}
}

View File

@@ -32,7 +32,7 @@ namespace MewtocolNet.Registers {
/// <summary> /// <summary>
/// The current value of the register /// The current value of the register
/// </summary> /// </summary>
object Value { get; } object ValueObj { get; }
/// <summary> /// <summary>
/// The plc memory address of the register /// The plc memory address of the register
@@ -55,18 +55,6 @@ namespace MewtocolNet.Registers {
/// </summary> /// </summary>
string ToString(bool detailed); string ToString(bool detailed);
/// <summary>
/// Reads the register value async from the plc
/// </summary>
/// <returns>The register value</returns>
Task<object> ReadAsync();
/// <summary>
/// Writes the register content async to the plc
/// </summary>
/// <returns>True if successfully set</returns>
Task<bool> WriteAsync(object data);
} }
} }

View File

@@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using MewtocolNet.Events;
namespace MewtocolNet.Registers {
/// <summary>
/// An interface for all register types
/// </summary>
public interface IRegister<T> : IRegister where T : struct {
/// <summary>
/// The current value of the register
/// </summary>
T? Value { get; }
/// <summary>
/// Reads the register value async from the plc
/// </summary>
/// <returns>The register value</returns>
Task<T?> ReadAsync();
/// <summary>
/// Writes the register content async to the plc
/// </summary>
/// <returns>True if successfully set</returns>
Task<bool> WriteAsync(T data);
}
}

View File

@@ -0,0 +1,25 @@
using System.Threading.Tasks;
namespace MewtocolNet.Registers {
public interface IStringRegister<T> : IRegister where T : class {
/// <summary>
/// The current value of the register
/// </summary>
T Value { get; }
/// <summary>
/// Reads the register value async from the plc
/// </summary>
/// <returns>The register value</returns>
Task<T> ReadAsync();
/// <summary>
/// Writes the register content async to the plc
/// </summary>
/// <returns>True if successfully set</returns>
Task<bool> WriteAsync(T data);
}
}

View File

@@ -44,7 +44,7 @@ namespace MewtocolNet.Registers {
public MewtocolInterface AttachedInterface => attachedInterface; public MewtocolInterface AttachedInterface => attachedInterface;
/// <inheritdoc/> /// <inheritdoc/>
public object Value => lastValue; public object ValueObj => lastValue;
/// <inheritdoc/> /// <inheritdoc/>
public RegisterType RegisterType { get; internal set; } public RegisterType RegisterType { get; internal set; }
@@ -62,7 +62,7 @@ namespace MewtocolNet.Registers {
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueObj)));
#endregion #endregion
@@ -97,23 +97,13 @@ namespace MewtocolNet.Registers {
} }
#region Read / Write
public virtual Task<object> ReadAsync() => throw new NotImplementedException();
public virtual Task<bool> WriteAsync(object data) => throw new NotImplementedException();
internal virtual Task UpdateDynamicSize () => throw new NotImplementedException();
#endregion
#region Default accessors #region Default accessors
public virtual byte? GetSpecialAddress() => null; public virtual byte? GetSpecialAddress() => null;
public virtual string GetValueString() => Value?.ToString() ?? "null"; public virtual string GetValueString() => ValueObj?.ToString() ?? "null";
public virtual string GetAsPLC() => Value?.ToString() ?? "null"; public virtual string GetAsPLC() => ValueObj?.ToString() ?? "null";
public virtual string GetRegisterString() => RegisterType == RegisterType.DT_BYTE_RANGE ? "DT" : RegisterType.ToString(); public virtual string GetRegisterString() => RegisterType == RegisterType.DT_BYTE_RANGE ? "DT" : RegisterType.ToString();
@@ -202,7 +192,7 @@ namespace MewtocolNet.Registers {
sb.Append(GetMewName()); sb.Append(GetMewName());
if (Name != null) sb.Append($" ({Name})"); if (Name != null) sb.Append($" ({Name})");
sb.Append($" [{this.GetType().Name}({underlyingSystemType.Name})]"); sb.Append($" [{this.GetType().Name}({underlyingSystemType.Name})]");
if (Value != null) sb.Append($" Val: {GetValueString()}"); if (ValueObj != null) sb.Append($" Val: {GetValueString()}");
return sb.ToString(); return sb.ToString();
@@ -235,7 +225,7 @@ namespace MewtocolNet.Registers {
sb.Append($"Name: {Name ?? "Not named"}\n"); sb.Append($"Name: {Name ?? "Not named"}\n");
if (Value != null) if (ValueObj != null)
sb.Append($"Value: {GetValueString()}\n"); sb.Append($"Value: {GetValueString()}\n");
sb.Append($"Reads: {successfulReads}, Writes: {successfulWrites}\n"); sb.Append($"Reads: {successfulReads}, Writes: {successfulWrites}\n");

View File

@@ -14,7 +14,7 @@ namespace MewtocolNet.Registers {
/// Defines a register containing a number /// Defines a register containing a number
/// </summary> /// </summary>
/// <typeparam name="T">The type of the numeric value</typeparam> /// <typeparam name="T">The type of the numeric value</typeparam>
public class SingleRegister<T> : Register { public class StructRegister<T> : Register, IRegister<T> where T : struct {
internal uint byteLength; internal uint byteLength;
@@ -25,11 +25,13 @@ namespace MewtocolNet.Registers {
/// </summary> /// </summary>
public uint AddressLength => addressLength; public uint AddressLength => addressLength;
public T? Value => (T?)ValueObj;
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")] [Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public SingleRegister() => public StructRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
internal SingleRegister(uint _address, uint _reservedByteSize, string _name = null) { internal StructRegister(uint _address, uint _reservedByteSize, string _name = null) {
memoryAddress = _address; memoryAddress = _address;
name = _name; name = _name;
@@ -56,33 +58,33 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/> /// <inheritdoc/>
public override string GetAsPLC() { public override string GetAsPLC() {
if (typeof(T) == typeof(TimeSpan)) return ((TimeSpan)Value).ToPlcTime(); if (typeof(T) == typeof(TimeSpan)) return ((TimeSpan)(object)ValueObj).ToPlcTime();
return Value.ToString(); return ValueObj.ToString();
} }
/// <inheritdoc/> /// <inheritdoc/>
public override string GetValueString() { public override string GetValueString() {
if (Value != null && typeof(T) == typeof(TimeSpan)) return $"{Value} [{((TimeSpan)Value).ToPlcTime()}]"; if (ValueObj != null && typeof(T) == typeof(TimeSpan)) return $"{ValueObj} [{((TimeSpan)(object)ValueObj).ToPlcTime()}]";
if (Value != null && typeof(T) == typeof(Word)) return $"{Value} [{((Word)Value).ToStringBitsPlc()}]"; if (ValueObj != null && typeof(T) == typeof(Word)) return $"{ValueObj} [{((Word)(object)ValueObj).ToStringBitsPlc()}]";
if (Value != null && typeof(T) == typeof(DWord)) return $"{Value} [{((DWord)Value).ToStringBitsPlc()}]"; if (ValueObj != null && typeof(T) == typeof(DWord)) return $"{ValueObj} [{((DWord)(object)ValueObj).ToStringBitsPlc()}]";
var hasFlags = typeof(T).GetCustomAttribute<FlagsAttribute>() != null; var hasFlags = typeof(T).GetCustomAttribute<FlagsAttribute>() != null;
if (Value != null && typeof(T).IsEnum && !hasFlags) { if (ValueObj != null && typeof(T).IsEnum && !hasFlags) {
var underlying = Enum.GetUnderlyingType(typeof(T)); var underlying = Enum.GetUnderlyingType(typeof(T));
object val = Convert.ChangeType(Value, underlying); object val = Convert.ChangeType(ValueObj, underlying);
return $"{Value} [{val}]"; return $"{ValueObj} [{val}]";
} }
if (Value != null && typeof(T).IsEnum && hasFlags) return $"{Value}"; if (ValueObj != null && typeof(T).IsEnum && hasFlags) return $"{ValueObj}";
return Value?.ToString() ?? "null"; return ValueObj?.ToString() ?? "null";
} }
@@ -90,7 +92,7 @@ namespace MewtocolNet.Registers {
public override uint GetRegisterAddressLen() => AddressLength; public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<bool> WriteAsync(object value) { public async Task<bool> WriteAsync(T value) {
var encoded = PlcValueParser.Encode(this, (T)value); var encoded = PlcValueParser.Encode(this, (T)value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
@@ -114,25 +116,27 @@ namespace MewtocolNet.Registers {
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<object> ReadAsync() { public async Task<T?> ReadAsync() {
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return null; if (res == null) return (T?)(object)null;
var matchingReg = attachedInterface.memoryManager.GetAllRegisters() var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this)); .FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null) { if (matchingReg != null) {
if (matchingReg is SingleRegister<string> sreg && this is SingleRegister<string> selfSreg) { //if (matchingReg is StructRegister<string> sreg && this.GetType() == typeof(StructRegister<string>)) {
sreg.addressLength = selfSreg.addressLength;
} // sreg.addressLength = selfSreg.addressLength;
//}
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
} }
return SetValueFromBytes(res); return (T)SetValueFromBytes(res);
} }

View File

@@ -54,7 +54,7 @@ namespace MewtocolNet.TypeConversion {
//default short DT conversion //default short DT conversion
new PlcTypeConversion<short>(RegisterType.DT) { new PlcTypeConversion<short>(RegisterType.DT) {
HoldingRegisterType = typeof(SingleRegister<short>), HoldingRegisterType = typeof(StructRegister<short>),
PlcVarType = PlcVarType.INT, PlcVarType = PlcVarType.INT,
FromRaw = (reg, bytes) => BitConverter.ToInt16(bytes, 0), FromRaw = (reg, bytes) => BitConverter.ToInt16(bytes, 0),
ToRaw = (reg, value) => BitConverter.GetBytes(value), ToRaw = (reg, value) => BitConverter.GetBytes(value),
@@ -62,7 +62,7 @@ namespace MewtocolNet.TypeConversion {
//default ushort DT conversion //default ushort DT conversion
new PlcTypeConversion<ushort>(RegisterType.DT) { new PlcTypeConversion<ushort>(RegisterType.DT) {
HoldingRegisterType = typeof(SingleRegister<ushort>), HoldingRegisterType = typeof(StructRegister<ushort>),
PlcVarType = PlcVarType.UINT, PlcVarType = PlcVarType.UINT,
FromRaw = (reg, bytes) => BitConverter.ToUInt16(bytes, 0), FromRaw = (reg, bytes) => BitConverter.ToUInt16(bytes, 0),
ToRaw = (reg, value) => BitConverter.GetBytes(value), ToRaw = (reg, value) => BitConverter.GetBytes(value),
@@ -70,7 +70,7 @@ namespace MewtocolNet.TypeConversion {
//default Word DT conversion //default Word DT conversion
new PlcTypeConversion<Word>(RegisterType.DT) { new PlcTypeConversion<Word>(RegisterType.DT) {
HoldingRegisterType = typeof(SingleRegister<Word>), HoldingRegisterType = typeof(StructRegister<Word>),
PlcVarType = PlcVarType.WORD, PlcVarType = PlcVarType.WORD,
FromRaw = (reg, bytes) => new Word(bytes), FromRaw = (reg, bytes) => new Word(bytes),
ToRaw = (reg, value) => value.ToByteArray(), ToRaw = (reg, value) => value.ToByteArray(),
@@ -78,7 +78,7 @@ namespace MewtocolNet.TypeConversion {
//default int DDT conversion //default int DDT conversion
new PlcTypeConversion<int>(RegisterType.DDT) { new PlcTypeConversion<int>(RegisterType.DDT) {
HoldingRegisterType = typeof(SingleRegister<int>), HoldingRegisterType = typeof(StructRegister<int>),
PlcVarType = PlcVarType.DINT, PlcVarType = PlcVarType.DINT,
FromRaw = (reg, bytes) => BitConverter.ToInt32(bytes, 0), FromRaw = (reg, bytes) => BitConverter.ToInt32(bytes, 0),
ToRaw = (reg, value) => BitConverter.GetBytes(value), ToRaw = (reg, value) => BitConverter.GetBytes(value),
@@ -86,7 +86,7 @@ namespace MewtocolNet.TypeConversion {
//default uint DDT conversion //default uint DDT conversion
new PlcTypeConversion<uint>(RegisterType.DDT) { new PlcTypeConversion<uint>(RegisterType.DDT) {
HoldingRegisterType = typeof(SingleRegister<uint>), HoldingRegisterType = typeof(StructRegister<uint>),
PlcVarType = PlcVarType.UDINT, PlcVarType = PlcVarType.UDINT,
FromRaw = (reg, bytes) => BitConverter.ToUInt32(bytes, 0), FromRaw = (reg, bytes) => BitConverter.ToUInt32(bytes, 0),
ToRaw = (reg, value) => BitConverter.GetBytes(value), ToRaw = (reg, value) => BitConverter.GetBytes(value),
@@ -94,7 +94,7 @@ namespace MewtocolNet.TypeConversion {
//default DWord DDT conversion //default DWord DDT conversion
new PlcTypeConversion<DWord>(RegisterType.DDT) { new PlcTypeConversion<DWord>(RegisterType.DDT) {
HoldingRegisterType = typeof(SingleRegister<DWord>), HoldingRegisterType = typeof(StructRegister<DWord>),
PlcVarType = PlcVarType.DWORD, PlcVarType = PlcVarType.DWORD,
FromRaw = (reg, bytes) => new DWord(bytes), FromRaw = (reg, bytes) => new DWord(bytes),
ToRaw = (reg, value) => value.ToByteArray(), ToRaw = (reg, value) => value.ToByteArray(),
@@ -102,7 +102,7 @@ namespace MewtocolNet.TypeConversion {
//default float DDT conversion //default float DDT conversion
new PlcTypeConversion<float>(RegisterType.DDT) { new PlcTypeConversion<float>(RegisterType.DDT) {
HoldingRegisterType = typeof(SingleRegister<float>), HoldingRegisterType = typeof(StructRegister<float>),
PlcVarType = PlcVarType.REAL, PlcVarType = PlcVarType.REAL,
FromRaw = (reg, bytes) => BitConverter.ToSingle(bytes, 0), FromRaw = (reg, bytes) => BitConverter.ToSingle(bytes, 0),
ToRaw = (reg, value) => BitConverter.GetBytes(value), ToRaw = (reg, value) => BitConverter.GetBytes(value),
@@ -110,7 +110,7 @@ namespace MewtocolNet.TypeConversion {
//default TimeSpan DDT conversion //default TimeSpan DDT conversion
new PlcTypeConversion<TimeSpan>(RegisterType.DDT) { new PlcTypeConversion<TimeSpan>(RegisterType.DDT) {
HoldingRegisterType = typeof(SingleRegister<TimeSpan>), HoldingRegisterType = typeof(StructRegister<TimeSpan>),
PlcVarType = PlcVarType.TIME, PlcVarType = PlcVarType.TIME,
FromRaw = (reg, bytes) => { FromRaw = (reg, bytes) => {
@@ -130,48 +130,48 @@ namespace MewtocolNet.TypeConversion {
//default string DT Range conversion Example bytes: (04 00 03 00 XX XX XX) //default string DT Range conversion Example bytes: (04 00 03 00 XX XX XX)
//first 4 bytes are reserved size (2 bytes) and used size (2 bytes) //first 4 bytes are reserved size (2 bytes) and used size (2 bytes)
//the remaining bytes are the ascii bytes for the string //the remaining bytes are the ascii bytes for the string
new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) { //new PlcTypeConversion<string>(RegisterType.DT_BYTE_RANGE) {
HoldingRegisterType = typeof(SingleRegister<string>), // HoldingRegisterType = typeof(StructRegister<string>),
PlcVarType = PlcVarType.STRING, // PlcVarType = PlcVarType.STRING,
FromRaw = (reg, bytes) => { // FromRaw = (reg, bytes) => {
if(bytes.Length == 4) return string.Empty; // if(bytes.Length == 4) return string.Empty;
if(bytes == null || bytes.Length < 4) { // if(bytes == null || bytes.Length < 4) {
throw new Exception("Failed to convert string bytes, response not long enough"); // throw new Exception("Failed to convert string bytes, response not long enough");
} // }
//get actual showed size // //get actual showed size
short actualLen = BitConverter.ToInt16(bytes, 2); // short actualLen = BitConverter.ToInt16(bytes, 2);
//skip 4 bytes because they only describe the length // //skip 4 bytes because they only describe the length
string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray()); // string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray());
return gotVal; // return gotVal;
}, // },
ToRaw = (reg, value) => { // ToRaw = (reg, value) => {
int padLen = value.Length; // int padLen = value.Length;
if(value.Length % 2 != 0) padLen++; // if(value.Length % 2 != 0) padLen++;
var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0')); // var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0'));
List<byte> finalBytes = new List<byte>(); // List<byte> finalBytes = new List<byte>();
short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4); // short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4);
short used = (short)value.Length; // short used = (short)value.Length;
finalBytes.AddRange(BitConverter.GetBytes(reserved)); // finalBytes.AddRange(BitConverter.GetBytes(reserved));
finalBytes.AddRange(BitConverter.GetBytes(used)); // finalBytes.AddRange(BitConverter.GetBytes(used));
finalBytes.AddRange(strBytes); // finalBytes.AddRange(strBytes);
return finalBytes.ToArray(); // return finalBytes.ToArray();
}, // },
}, //},
}; };

View File

@@ -1,6 +1,7 @@
using MewtocolNet.Registers; using MewtocolNet.Registers;
using MewtocolNet.TypeConversion; using MewtocolNet.TypeConversion;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -29,7 +30,7 @@ namespace MewtocolNet {
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingType); converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingType);
if (converter == null) if (converter == null)
throw new Exception($"A converter for the dotnet type {underlyingType} doesn't exist"); throw new Exception($"A converter for the type {underlyingType} doesn't exist");
return (T)converter.FromRawData(register, bytes); return (T)converter.FromRawData(register, bytes);
@@ -37,6 +38,9 @@ namespace MewtocolNet {
internal static T ParseArray <T>(Register register, int[] indices, byte[] bytes) { internal static T ParseArray <T>(Register register, int[] indices, byte[] bytes) {
//if byte array directly return the bytes
if (typeof(T) == typeof(byte[])) return (T)(object)bytes;
IPlcTypeConverter converter; IPlcTypeConverter converter;
Type underlyingElementType; Type underlyingElementType;
@@ -54,19 +58,18 @@ namespace MewtocolNet {
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingElementType); converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingElementType);
if (converter == null) if (converter == null)
throw new Exception($"A converter for the dotnet type {underlyingElementType} doesn't exist"); throw new Exception($"A converter for the type {underlyingElementType} doesn't exist");
//parse the array from one to n dimensions //parse the array from one to n dimensions
var outArray = Array.CreateInstance(underlyingElementType, indices); var outArray = Array.CreateInstance(underlyingElementType, indices);
if(outArray.GetType() == typeof(byte[])) { int sizePerItem = 0;
if(underlyingElementType == typeof(string)) {
Console.WriteLine(); throw new NotImplementedException();
} else {
sizePerItem = underlyingElementType.DetermineTypeByteIntialSize();
} }
int sizePerItem = underlyingElementType.DetermineTypeByteIntialSize();
var iterateItems = indices.Aggregate((a, x) => a * x); var iterateItems = indices.Aggregate((a, x) => a * x);
var indexer = new int[indices.Length]; var indexer = new int[indices.Length];
for (int i = 0; i < iterateItems; i++) { for (int i = 0; i < iterateItems; i++) {
@@ -92,44 +95,6 @@ namespace MewtocolNet {
} }
static void ConvertFlatArrayToDim (
IPlcTypeConverter converter,
Register register,
byte[] source,
Array target,
int sizePerVal,
int[] dims,
int currentIndex,
int currentArrayIndex
) {
if (currentIndex == dims.Length - 1) {
for (int i = 0; i < dims[currentIndex]; i++) {
byte[] rawDataItem = source.Skip(currentArrayIndex).Take(sizePerVal).ToArray();
var value = converter.FromRawData(register, rawDataItem);
target.SetValue(value, i);
currentArrayIndex += sizePerVal;
}
} else {
for (int i = 0; i < dims[currentIndex]; i++) {
Array innerArray = (Array)target.GetValue(i);
ConvertFlatArrayToDim(converter, register, source, innerArray, sizePerVal, dims, currentIndex + 1, currentArrayIndex);
currentArrayIndex += innerArray.Length * sizePerVal;
}
}
}
internal static byte[] Encode<T>(Register register, T value) { internal static byte[] Encode<T>(Register register, T value) {
IPlcTypeConverter converter; IPlcTypeConverter converter;
@@ -155,10 +120,53 @@ namespace MewtocolNet {
} }
//internal static byte[] EncodeArray (IRegister register, T value) { internal static byte[] EncodeArray<T>(Register register, T value) {
//if byte array directly return the bytes
if (typeof(T) == typeof(byte[])) return (byte[])(object)value;
//} IPlcTypeConverter converter;
Type underlyingElementType;
//special case for enums
if (typeof(T).IsEnum) {
underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType();
} else {
underlyingElementType = typeof(T).GetElementType();
}
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingElementType);
if (converter == null)
throw new Exception($"A converter for the type {underlyingElementType} doesn't exist");
int sizePerItem = 0;
if (underlyingElementType == typeof(string)) {
throw new NotImplementedException();
} else {
sizePerItem = underlyingElementType.DetermineTypeByteIntialSize();
}
byte[] encodedData = new byte[((ICollection)value).Count * sizePerItem];
int i = 0;
foreach (object item in (IEnumerable)value) {
var encoded = converter.ToRawData(register, item);
encoded.CopyTo(encodedData, i);
i += sizePerItem;
}
return encodedData;
}
public static List<Type> GetAllowDotnetTypes() => conversions.Select(x => x.GetDotnetType()).ToList(); public static List<Type> GetAllowDotnetTypes() => conversions.Select(x => x.GetDotnetType()).ToList();