Multiple fixes

This commit is contained in:
Felix Weiß
2023-07-17 17:44:20 +02:00
parent d6c00097bc
commit eb70dac5a8
26 changed files with 584 additions and 490 deletions

View File

@@ -0,0 +1,13 @@
using System;
namespace MewtocolNet.Events {
public delegate void PlcConnectionEventHandler(object sender, PlcConnectionArgs e);
public class PlcConnectionArgs : EventArgs {
}
}

View File

@@ -0,0 +1,22 @@
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.Events {
public delegate void RegisterChangedEventHandler(object sender, RegisterChangedArgs e);
public class RegisterChangedArgs : EventArgs {
public IRegister Register { get; internal set; }
public object Value { get; internal set; }
public object PreviousValue { get; internal set; }
public string PreviousValueString { get; internal set; }
}
}

View File

@@ -1,46 +0,0 @@
using MewtocolNet.Registers;
using System;
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) { }
internal static MewtocolException NotConnectedSend() {
return new MewtocolException($"Can not send a message to the PLC if it isn't connected");
}
internal static MewtocolException DupeRegister(Register register) {
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}");
}
internal static MewtocolException DupeNameRegister(Register register) {
return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetMewName()}");
}
internal static MewtocolException OverlappingRegister(Register registerA, Register registerB) {
return new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " +
$"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}");
}
}
}

View File

@@ -19,13 +19,15 @@ namespace MewtocolNet {
#region Byte and string operation helpers
public static int DetermineTypeByteSize(this Type type) {
public static int DetermineTypeByteIntialSize(this Type type) {
//enums can only be of numeric types
if (type.IsEnum) return Marshal.SizeOf(Enum.GetUnderlyingType(type));
//strings get always set with 4 bytes because the first 4 bytes contain the length
if (type == typeof(string)) return 4;
if (type == typeof(TimeSpan)) return 4;
if (type == typeof(DateTime)) return 4;
if (type.Namespace.StartsWith("System")) return Marshal.SizeOf(type);
@@ -157,6 +159,15 @@ namespace MewtocolNet {
}
public static string Ellipsis(this string str, int maxLength) {
if (string.IsNullOrEmpty(str) || str.Length <= maxLength)
return str;
return $"{str.Substring(0, maxLength - 3)}...";
}
/// <summary>
/// Converts a hex string (AB01C1) to a byte array
/// </summary>

View File

@@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet {
[Flags]
internal enum DynamicSizeState {
None = 0,
DynamicallySized = 1,
NeedsSizeUpdate = 2,
WasSizeUpdated = 4,
}
}

View File

@@ -1,11 +1,12 @@
using MewtocolNet.Exceptions;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.SetupClasses;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MewtocolNet {
@@ -96,7 +97,7 @@ namespace MewtocolNet {
var portnames = SerialPort.GetPortNames();
if (!portnames.Any(x => x == portName))
throw new MewtocolException($"The port {portName} is no valid port");
throw new NotSupportedException($"The port {portName} is no valid port");
}
@@ -265,6 +266,32 @@ namespace MewtocolNet {
}
/// <summary>
/// A builder for attaching register collections
/// </summary>
public PostInit<T> WithRegisters(Action<RBuildMult> builder) {
try {
var plc = (MewtocolInterface)(object)intf;
var assembler = new RegisterAssembler(plc);
var regBuilder = new RBuildMult(plc);
builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder);
plc.AddRegisters(registers.ToArray());
return this;
} catch {
throw;
}
}
/// <summary>
/// Builds and returns the final plc interface
/// </summary>

View File

@@ -1,4 +1,5 @@
using MewtocolNet.Helpers;
using MewtocolNet.Events;
using MewtocolNet.Helpers;
using MewtocolNet.Logging;
using MewtocolNet.Registers;
using MewtocolNet.UnderlyingRegisters;
@@ -17,6 +18,22 @@ namespace MewtocolNet {
public abstract partial class MewtocolInterface : IPlc {
#region Events
/// <inheritdoc/>
public event PlcConnectionEventHandler Connected;
/// <inheritdoc/>
public event PlcConnectionEventHandler Disconnected;
/// <inheritdoc/>
public event RegisterChangedEventHandler RegisterChanged;
/// <inheritdoc/>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Private fields
private protected Stream stream;
@@ -40,6 +57,7 @@ namespace MewtocolNet {
private protected Stopwatch speedStopwatchDownstr;
private protected Task firstPollTask = new Task(() => { });
private protected bool wasInitialStatusReceived;
private protected MewtocolVersion mewtocolVersion;
#endregion
@@ -56,18 +74,6 @@ namespace MewtocolNet {
#region Public Read Only Properties / Fields
/// <inheritdoc/>
public event Action<PLCInfo> Connected;
/// <inheritdoc/>
public event Action Disconnected;
/// <inheritdoc/>
public event Action<IRegister> RegisterChanged;
/// <inheritdoc/>
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc/>
public bool Disposed { get; private set; }
@@ -143,7 +149,7 @@ namespace MewtocolNet {
Connected += MewtocolInterface_Connected;
RegisterChanged += OnRegisterChanged;
void MewtocolInterface_Connected(PLCInfo obj) {
void MewtocolInterface_Connected(object sender, PlcConnectionArgs args) {
if (usePoller)
AttachPoller();
@@ -154,29 +160,48 @@ namespace MewtocolNet {
}
private void OnRegisterChanged(IRegister o) {
private void OnRegisterChanged(object sender, RegisterChangedArgs args) {
var asInternal = (Register)o;
var asInternal = (Register)args.Register;
//log
if(IsConnected) {
var sb = new StringBuilder();
sb.Append(asInternal.GetMewName());
if (asInternal.Name != null) {
sb.Append(asInternal.autoGenerated ? $" (Auto)" : $" ({o.Name})");
sb.Append(asInternal.autoGenerated ? $" (Auto)" : $" ({asInternal.Name})");
}
sb.Append($" {asInternal.underlyingSystemType.Name}");
sb.Append($" changed to \"{asInternal.GetValueString()}\"");
sb.Append($" changed \"{args.PreviousValueString.Ellipsis(25)}\"" +
$" => \"{asInternal.GetValueString().Ellipsis(75)}\"");
Logger.Log(sb.ToString(), LogLevel.Change, this);
OnRegisterChangedUpdateProps((Register)o);
}
OnRegisterChangedUpdateProps(asInternal);
}
/// <inheritdoc/>
public virtual async Task ConnectAsync() {
isConnectingStage = false;
await memoryManager.OnPlcConnected();
Logger.Log($"PLC: {PlcInfo.TypeName}", LogLevel.Verbose, this);
Logger.Log($"TYPE CODE: {PlcInfo.TypeCode.ToString("X")}", LogLevel.Verbose, this);
Logger.Log($"OP MODE: {PlcInfo.OperationMode}", LogLevel.Verbose, this);
Logger.Log($"PROG CAP: {PlcInfo.ProgramCapacity}k", LogLevel.Verbose, this);
Logger.Log($"HW INFO: {PlcInfo.HardwareInformation}", LogLevel.Verbose, this);
Logger.Log($"DIAG ERR: {PlcInfo.SelfDiagnosticError}", LogLevel.Verbose, this);
Logger.Log($"CPU VER: {PlcInfo.CpuVersion}", LogLevel.Verbose, this);
Logger.Log($">> Intial connection end <<", LogLevel.Verbose, this);
}
/// <inheritdoc/>
@@ -220,6 +245,9 @@ namespace MewtocolNet {
/// <inheritdoc/>
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1, Action<double> onReceiveProgress = null) {
if (!IsConnected && !isConnectingStage)
throw new NotSupportedException("The device must be connected to send a message");
//send request
queuedMessages++;
@@ -468,11 +496,10 @@ namespace MewtocolNet {
private protected virtual void OnConnected(PLCInfo plcinf) {
Logger.Log("Connected to PLC", LogLevel.Info, this);
Logger.Log($"{plcinf.ToString()}", LogLevel.Verbose, this);
IsConnected = true;
Connected?.Invoke(plcinf);
Connected?.Invoke(this, new PlcConnectionArgs());
if (!usePoller) {
firstPollTask.RunSynchronously();
@@ -493,11 +520,12 @@ namespace MewtocolNet {
BytesPerSecondDownstream = 0;
BytesPerSecondUpstream = 0;
PollerCycleDurationMs = 0;
PlcInfo = null;
IsConnected = false;
ClearRegisterVals();
Disconnected?.Invoke();
Disconnected?.Invoke(this, new PlcConnectionArgs());
KillPoller();
}

View File

@@ -1,4 +1,5 @@
using MewtocolNet.Logging;
using MewtocolNet.Events;
using MewtocolNet.Logging;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.Registers;
@@ -210,19 +211,19 @@ namespace MewtocolNet {
if (attr is RegisterAttribute cAttribute) {
var pollFreqAttr = (PollLevelAttribute)attributes.FirstOrDefault(x => x.GetType() == typeof(PollLevelAttribute));
var stringHintAttr = (StringHintAttribute)attributes.FirstOrDefault(x => x.GetType() == typeof(StringHintAttribute));
var dotnetType = prop.PropertyType;
int pollLevel = 1;
uint? byteHint = (uint?)stringHintAttr?.size;
if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel;
//add builder item
var stp1 = regBuild
.AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef)
.AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, byteHint)
.AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType)
.PollLevel(pollLevel)
.RegCollection(collection)
.BoundProp(prop);
.PollLevel(pollLevel);
}
@@ -235,7 +236,7 @@ namespace MewtocolNet {
collection.OnInterfaceLinked(this);
}
Connected += (i) => {
Connected += (s,e) => {
if (collection != null)
collection.OnInterfaceLinkedAndOnline(this);
};
@@ -280,6 +281,9 @@ namespace MewtocolNet {
memoryManager.LinkAndMergeRegisters(registers);
//run a second iteration
//memoryManager.LinkAndMergeRegisters();
}
private bool CheckDuplicateRegister(Register instance, out Register foundDupe) {
@@ -383,9 +387,14 @@ namespace MewtocolNet {
}
internal void InvokeRegisterChanged(Register reg) {
internal void InvokeRegisterChanged(Register reg, object preValue, string preValueString) {
RegisterChanged?.Invoke(reg);
RegisterChanged?.Invoke(this, new RegisterChangedArgs {
Register = reg,
PreviousValue = preValue,
PreviousValueString = preValueString,
Value = reg.Value,
});
}

View File

@@ -1,14 +1,16 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace MewtocolNet {
public abstract partial class MewtocolInterface {
internal bool isConnectingStage = false;
internal int maxDataBlocksPerWrite = 8;
#region PLC info getters
@@ -17,20 +19,26 @@ namespace MewtocolNet {
/// Gets generic information about the PLC
/// </summary>
/// <returns>A PLCInfo class</returns>
public async Task<PLCInfo?> GetPLCInfoAsync(int timeout = -1) {
public async Task<PLCInfo> GetPLCInfoAsync(int timeout = -1) {
var resRT = await SendCommandAsync("%EE#RT", timeoutMs: timeout);
MewtocolFrameResponse resRT = await SendCommandAsync("%EE#RT", timeoutMs: timeout);
if (!resRT.Success) {
//timeouts are ok and dont throw
//timeouts are ok and don't throw
if (resRT == MewtocolFrameResponse.Timeout) return null;
throw new MewtocolException(resRT.Error);
throw new Exception(resRT.Error);
}
var resEXRT = await SendCommandAsync("%EE#EX00RT00", timeoutMs: timeout);
MewtocolFrameResponse? resEXRT = null;
if(isConnectingStage) {
resEXRT = await SendCommandAsync("%EE#EX00RT00", timeoutMs: timeout);
}
//timeouts are ok and dont throw
if (!resRT.Success && resRT == MewtocolFrameResponse.Timeout) return null;
@@ -40,20 +48,27 @@ namespace MewtocolNet {
//dont overwrite, use first
if (!PLCInfo.TryFromRT(resRT.Response, out plcInf)) {
throw new MewtocolException("The RT message could not be parsed");
throw new Exception("The RT message could not be parsed");
}
//overwrite first with EXRT
if (resEXRT.Success && !plcInf.TryExtendFromEXRT(resEXRT.Response)) {
//overwrite first with EXRT only on connecting stage
if (isConnectingStage && resEXRT != null && resEXRT.Value.Success && !plcInf.TryExtendFromEXRT(resEXRT.Value.Response)) {
throw new MewtocolException("The EXRT message could not be parsed");
throw new Exception("The EXRT message could not be parsed");
}
if(isConnectingStage) {
//set the intial obj
PlcInfo = plcInf;
} else {
//update the obj with RT dynamic values only
PlcInfo.SelfDiagnosticError = plcInf.SelfDiagnosticError;
PlcInfo.OperationMode = plcInf.OperationMode;
}
return plcInf;
return PlcInfo;
}
@@ -93,9 +108,6 @@ namespace MewtocolNet {
/// <returns></returns>
public async Task<bool> WriteByteRange(int start, byte[] byteArr) {
if (!IsConnected)
throw MewtocolException.NotConnectedSend();
string byteString = byteArr.ToHexString();
var wordLength = byteArr.Length / 2;
@@ -121,11 +133,6 @@ namespace MewtocolNet {
/// <returns>A byte array of the requested DT area</returns>
public async Task<byte[]> ReadByteRangeNonBlocking(int start, int byteCount, Action<double> onProgress = null) {
if (!IsConnected)
throw MewtocolException.NotConnectedSend();
onProgress += (p) => Console.WriteLine($"{p * 100:N2}%");
//on odd bytes add one word
var wordLength = byteCount / 2;
if (byteCount % 2 != 0) wordLength++;

View File

@@ -113,7 +113,10 @@ namespace MewtocolNet {
try {
PLCInfo? gotInfo = null;
Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this);
isConnectingStage = true;
PLCInfo gotInfo = null;
if (autoSerial) {
@@ -129,8 +132,9 @@ namespace MewtocolNet {
if (gotInfo != null) {
IsConnected = true;
await base.ConnectAsync();
OnConnected(gotInfo.Value);
OnConnected(gotInfo);
} else {
@@ -145,13 +149,15 @@ namespace MewtocolNet {
OnMajorSocketExceptionWhileConnecting();
isConnectingStage = false;
}
tryingSerialConfig -= OnTryConfig;
}
private async Task<PLCInfo?> TryConnectAsyncMulti() {
private async Task<PLCInfo> TryConnectAsyncMulti() {
var baudRates = Enum.GetValues(typeof(BaudRate)).Cast<BaudRate>();
@@ -197,7 +203,7 @@ namespace MewtocolNet {
}
private async Task<PLCInfo?> TryConnectAsyncSingle(string port, int baud, int dbits, Parity par, StopBits sbits) {
private async Task<PLCInfo> TryConnectAsyncSingle(string port, int baud, int dbits, Parity par, StopBits sbits) {
try {

View File

@@ -1,4 +1,3 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Logging;
using System;
using System.Net;
@@ -34,7 +33,7 @@ namespace MewtocolNet {
public void ConfigureConnection(string ip, int port = 9094, int station = 0xEE) {
if (!IPAddress.TryParse(ip, out ipAddr))
throw new MewtocolException($"The ip: {ip} is no valid ip address");
throw new NotSupportedException($"The ip: {ip} is no valid ip address");
if (stationNumber != 0xEE && stationNumber > 99)
throw new NotSupportedException("Station number can't be greater than 99");
@@ -66,6 +65,9 @@ namespace MewtocolNet {
try {
Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this);
isConnectingStage = true;
if (HostEndpoint != null) {
client = new TcpClient(HostEndpoint) {
@@ -109,9 +111,9 @@ namespace MewtocolNet {
if (plcinf != null) {
IsConnected = true;
await base.ConnectAsync();
OnConnected(plcinf.Value);
OnConnected(plcinf);
} else {
@@ -125,6 +127,7 @@ namespace MewtocolNet {
} catch (SocketException) {
OnMajorSocketExceptionWhileConnecting();
isConnectingStage = false;
}

View File

@@ -1,4 +1,6 @@
using System.Globalization;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
namespace MewtocolNet {
@@ -6,43 +8,85 @@ namespace MewtocolNet {
/// <summary>
/// Holds various informations about the PLC
/// </summary>
public struct PLCInfo {
public class PLCInfo : INotifyPropertyChanged {
private PlcType typeCode;
private string typeName;
private OPMode operationMode;
private HWInformation hardwareInformation;
private string selfDiagnosticError;
/// <summary>
/// The type of the PLC named by Panasonic
/// </summary>
public PlcType TypeCode { get; private set; }
public PlcType TypeCode {
get => typeCode;
internal set {
typeCode = value;
OnPropChange();
//update name
typeName = typeCode.ToName();
OnPropChange(nameof(TypeName));
}
}
/// <summary>
/// Contains information about the PLCs operation modes as flags
/// The full qualified name of the PLC
/// </summary>
public OPMode OperationMode { get; private set; }
/// <summary>
/// Hardware information flags about the PLC
/// </summary>
public HWInformation HardwareInformation { get; private set; }
public string TypeName => typeName;
/// <summary>
/// Program capacity in 1K steps
/// </summary>
public int ProgramCapacity { get; private set; }
public int ProgramCapacity { get; internal set; }
/// <summary>
/// Version of the cpu
/// </summary>
public string CpuVersion { get; private set; }
public string CpuVersion { get; internal set; }
/// <summary>
/// Contains information about the PLCs operation modes as flags
/// </summary>
public OPMode OperationMode {
get => operationMode;
internal set {
operationMode = value;
OnPropChange();
OnPropChange(nameof(IsRunMode));
}
}
/// <summary>
/// Hardware information flags about the PLC
/// </summary>
public HWInformation HardwareInformation {
get => hardwareInformation;
internal set {
hardwareInformation = value;
OnPropChange();
}
}
/// <summary>
/// Current error code of the PLC
/// </summary>
public string SelfDiagnosticError { get; internal set; }
public string SelfDiagnosticError {
get => selfDiagnosticError;
internal set {
selfDiagnosticError = value;
OnPropChange();
}
}
/// <summary>
/// Quickcheck for the runmode flag
/// </summary>
public bool IsRunMode => OperationMode.HasFlag(OPMode.RunMode);
public event PropertyChangedEventHandler PropertyChanged;
internal bool TryExtendFromEXRT(string msg) {
var regexEXRT = new Regex(@"\%EE\$EX00RT00(?<icnt>..)(?<mc>..)..(?<cap>..)(?<op>..)..(?<flg>..)(?<sdiag>....)(?<ver>..)(?<hwif>..)(?<nprog>.)(?<progsz>....)(?<hdsz>....)(?<sysregsz>....).*", RegexOptions.IgnoreCase);
@@ -114,7 +158,25 @@ namespace MewtocolNet {
public override string ToString() {
return $"{TypeCode.ToName()}, OP: {OperationMode}";
return $"{TypeName}, OP: {OperationMode}";
}
public override bool Equals(object obj) {
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
return false;
} else {
return (PLCInfo)obj == this;
}
}
public override int GetHashCode() => GetHashCode();
private protected void OnPropChange([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

View File

@@ -0,0 +1,20 @@
using System;
namespace MewtocolNet.RegisterAttributes {
/// <summary>
/// Defines a string size hint
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class StringHintAttribute : Attribute {
internal int size;
public StringHintAttribute(int size) {
this.size = size;
}
}
}

View File

@@ -305,7 +305,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <typeparam name="T">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </typeparam>
public TempRegister<T> AsType<T>(int? sizeHint = null) {
public TypedRegister AsType<T>() {
if (!typeof(T).IsAllowedPlcCastingType()) {
@@ -313,10 +313,9 @@ namespace MewtocolNet.RegisterBuilding {
}
Data.byteSizeHint = (uint?)sizeHint;
Data.dotnetVarType = typeof(T);
return new TempRegister<T>(Data, builder);
return new TypedRegister().Map(this);
}
@@ -327,7 +326,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <param name="type">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </param>
public TempRegister AsType(Type type) {
public TypedRegister AsType(Type type) {
//was ranged syntax array build
if (Data.wasAddressStringRangeBased && type.IsArray && type.GetArrayRank() == 1) {
@@ -344,14 +343,14 @@ namespace MewtocolNet.RegisterBuilding {
}
int byteSizePerItem = elementType.DetermineTypeByteSize();
int byteSizePerItem = elementType.DetermineTypeByteIntialSize();
//check if it fits without remainder
if (Data.byteSizeHint % byteSizePerItem != 0) {
throw new NotSupportedException($"The array element type {elementType} doesn't fit into the adress range");
}
return (TempRegister)generic.Invoke(this, new object[] {
return (TypedRegister)generic.Invoke(this, new object[] {
//element count
new int[] { (int)((Data.byteSizeHint / byteSizePerItem)) }
});
@@ -369,9 +368,9 @@ namespace MewtocolNet.RegisterBuilding {
return AsType(Data.typeDef);
} else if ((type.IsArray || type == typeof(string)) && Data.typeDef == null) {
} else if (type.IsArray && Data.typeDef == null) {
throw new NotSupportedException("Typedef parameter is needed for array or string types");
throw new NotSupportedException("Typedef parameter is needed for array types");
} else if (Data.typeDef != null) {
@@ -389,18 +388,18 @@ namespace MewtocolNet.RegisterBuilding {
Data.dotnetVarType = type;
return new TempRegister(Data, builder);
return new TypedRegister().Map(this);
}
/// <summary>
/// Sets the register type as a predefined <see cref="PlcVarType"/>
/// </summary>
public TempRegister AsType(PlcVarType type) {
public TypedRegister AsType(PlcVarType type) {
Data.dotnetVarType = type.GetDefaultDotnetType();
return new TempRegister(Data, builder);
return new TypedRegister().Map(this);
}
@@ -421,10 +420,13 @@ namespace MewtocolNet.RegisterBuilding {
/// <item><term>DWORD</term><description>32 bit double word interpreted as <see cref="uint"/></description></item>
/// </list>
/// </summary>
public TempRegister AsType(string type) {
public TypedRegister AsType(string type) {
var stringMatch = Regex.Match(type, @"STRING *\[(?<len>[0-9]*)\]", RegexOptions.IgnoreCase);
var arrayMatch = Regex.Match(type, @"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 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 stringMatch = regexString.Match(type);
var arrayMatch = regexArray.Match(type);
if (Enum.TryParse<PlcVarType>(type, out var parsed)) {
@@ -440,10 +442,26 @@ namespace MewtocolNet.RegisterBuilding {
//invoke generic AsTypeArray
string arrTypeString = arrayMatch.Groups["t"].Value;
Type dotnetArrType = null;
if (Enum.TryParse<PlcVarType>(arrTypeString, out var parsedArrType)) {
var stringMatchInArray = regexString.Match(arrTypeString);
if (Enum.TryParse<PlcVarType>(arrTypeString, out var parsedArrType) && parsedArrType != PlcVarType.STRING) {
dotnetArrType = parsedArrType.GetDefaultDotnetType();
} else if (stringMatchInArray.Success) {
dotnetArrType = typeof(string);
//Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value);
} else {
throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized");
}
var dotnetArrType = parsedArrType.GetDefaultDotnetType();
var indices = new List<int>();
for (int i = 1; i < 4; i++) {
@@ -465,7 +483,7 @@ namespace MewtocolNet.RegisterBuilding {
MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray));
MethodInfo generic = method.MakeGenericMethod(arrType);
var tmp = (TempRegister)generic.Invoke(this, new object[] {
var tmp = (TypedRegister)generic.Invoke(this, new object[] {
indices.ToArray()
});
@@ -474,18 +492,13 @@ namespace MewtocolNet.RegisterBuilding {
return tmp;
} else {
throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized");
}
} else {
throw new NotSupportedException($"The FP type '{type}' was not recognized");
}
return new TempRegister(Data, builder);
return new TypedRegister().Map(this);
}
@@ -507,7 +520,7 @@ namespace MewtocolNet.RegisterBuilding {
/// 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/>
/// </example>
public TempRegister AsTypeArray<T>(params int[] indicies) {
public TypedRegister AsTypeArray<T>(params int[] indicies) {
if (!typeof(T).IsArray)
throw new NotSupportedException($"The type {typeof(T)} was no array");
@@ -526,7 +539,7 @@ namespace MewtocolNet.RegisterBuilding {
Data.dotnetVarType = typeof(T);
int byteSizePerItem = elBaseType.DetermineTypeByteSize();
int byteSizePerItem = elBaseType.DetermineTypeByteIntialSize();
int calcedTotalByteSize = indicies.Aggregate((a, x) => a * x) * byteSizePerItem;
Data.byteSizeHint = (uint)calcedTotalByteSize;
@@ -536,7 +549,31 @@ namespace MewtocolNet.RegisterBuilding {
throw new NotSupportedException($"The array element type {elBaseType} doesn't fit into the adress range");
}
return new TempRegister(Data, builder);
return new TypedRegister().Map(this);
}
}
#endregion
#region Typing size hint
public class TypedRegister : SBase {
public OptionsRegister SizeHint(int hint) {
Data.byteSizeHint = (uint)hint;
return new OptionsRegister().Map(this);
}
public OptionsRegister PollLevel(int level) {
Data.pollLevel = level;
return new OptionsRegister().Map(this);
}
@@ -546,36 +583,19 @@ namespace MewtocolNet.RegisterBuilding {
#region Options stage
public class TempRegister<T> : SBase {
public class OptionsRegister : SBase {
internal TempRegister() { }
internal OptionsRegister() { }
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
internal OptionsRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
/// <summary>
/// Sets the poll level of the register
/// </summary>
public TempRegister<T> PollLevel(int level) {
public OptionsRegister PollLevel(int level) {
Data.pollLevel = level;
return this;
}
}
public class TempRegister : SBase {
internal TempRegister() { }
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
/// <summary>
/// Sets the poll level of the register
/// </summary>
public TempRegister PollLevel(int level) {
Data.pollLevel = level;
return this;
}

View File

@@ -34,11 +34,16 @@ namespace MewtocolNet.RegisterBuilding {
}
//internal use only, adds a type definition (for use when building from attibute)
internal SAddress AddressFromAttribute(string plcAddrName, string typeDef) {
internal SAddress AddressFromAttribute(string plcAddrName, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) {
var built = Address(plcAddrName);
built.Data.typeDef = typeDef;
built.Data.buildSource = RegisterBuildSource.Attribute;
built.Data.regCollection = regCol;
built.Data.boundProperty = prop;
built.Data.byteSizeHint = bytesizeHint;
return built;
}
@@ -49,15 +54,38 @@ namespace MewtocolNet.RegisterBuilding {
public new class SAddress : RBuildBase.SAddress {
public new TempRegister<T> AsType<T>(int? sizeHint = null) => new TempRegister<T>().Map(base.AsType<T>(sizeHint));
public new TypedRegister AsType<T>() => new TypedRegister().Map(base.AsType<T>());
public new TempRegister AsType(Type type) => new TempRegister().Map(base.AsType(type));
public new TypedRegister AsType(Type type) => new TypedRegister().Map(base.AsType(type));
public new TempRegister AsType(PlcVarType type) => new TempRegister().Map(base.AsType(type));
public new TypedRegister AsType(PlcVarType type) => new TypedRegister().Map(base.AsType(type));
public new TempRegister AsType(string type) => new TempRegister().Map(base.AsType(type));
public new TypedRegister AsType(string type) => new TypedRegister().Map(base.AsType(type));
public new TempRegister AsTypeArray<T>(params int[] indicies) => new TempRegister().Map(base.AsTypeArray<T>(indicies));
public new TypedRegister AsTypeArray<T>(params int[] indicies) => new TypedRegister().Map(base.AsTypeArray<T>(indicies));
}
#endregion
#region Typing size hint
public new class TypedRegister : RBuildBase.TypedRegister {
public new OptionsRegister SizeHint(int hint) => new OptionsRegister().Map(base.SizeHint(hint));
///<inheritdoc cref="RBuildBase.OptionsRegister.PollLevel(int)"/>
public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut;
}
}
@@ -65,58 +93,17 @@ namespace MewtocolNet.RegisterBuilding {
#region Options stage
public new class TempRegister<T> : RBuildBase.TempRegister<T> {
public new class OptionsRegister : RBuildBase.OptionsRegister {
internal TempRegister() { }
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
///<inheritdoc cref="RBuildBase.TempRegister.PollLevel(int)"/>
public new TempRegister<T> PollLevel(int level) => new TempRegister<T>().Map(base.PollLevel(level));
///<inheritdoc cref="RBuildBase.OptionsRegister.PollLevel(int)"/>
public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public TempRegister<T> Out(Action<IRegister> registerOut) {
public void Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut;
return this;
}
}
public new class TempRegister : RBuildBase.TempRegister {
internal TempRegister() { }
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
///<inheritdoc cref="RBuildBase.TempRegister.PollLevel(int)"/>
public new TempRegister PollLevel(int level) => new TempRegister().Map(base.PollLevel(level));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public TempRegister Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut;
return this;
}
//internal use only
internal TempRegister RegCollection(RegisterCollection col) {
Data.regCollection = col;
return this;
}
internal TempRegister BoundProp(PropertyInfo prop) {
Data.boundProperty = prop;
return this;
}
@@ -124,6 +111,19 @@ namespace MewtocolNet.RegisterBuilding {
#endregion
public class OutRegister : SBase {
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public void Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut;
}
}
}
}

View File

@@ -25,32 +25,6 @@ namespace MewtocolNet.RegisterBuilding {
interf.AddRegisters(registers.ToArray());
Task.Run(interf.memoryManager.CheckAllDynamicallySizedAreas);
return registers.First();
}
/// <summary>
/// Adds a single register to the plc stack and returns the generated <see cref="IRegister"/>
/// Waits
/// </summary>
/// <returns>The generated <see cref="IRegister"/></returns>
public static async Task<IRegister> AddRegisterAsync (this IPlc plc, Action<RBuildSingle> builder) {
var assembler = new RegisterAssembler((MewtocolInterface)plc);
var regBuilder = new RBuildSingle((MewtocolInterface)plc);
builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder);
var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray());
await interf.memoryManager.CheckAllDynamicallySizedAreas();
return registers.First();
}
@@ -77,33 +51,6 @@ namespace MewtocolNet.RegisterBuilding {
interf.AddRegisters(registers.ToArray());
Task.Run(interf.memoryManager.CheckAllDynamicallySizedAreas);
return plc;
}
/// <summary>
/// Adds multiple registers to the plc stack at once <br/>
/// Using this over adding each register individually will result in better generation time performance
/// of the <see cref="UnderlyingRegisters.MemoryAreaManager"/><br/><br/>
/// This waits for the memory manager to size all dynamic registers correctly
/// </summary>
public static async Task<IPlc> AddRegistersAsync (this IPlc plc, Action<RBuildMult> builder) {
var assembler = new RegisterAssembler((MewtocolInterface)plc);
var regBuilder = new RBuildMult((MewtocolInterface)plc);
builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder);
var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray());
await interf.memoryManager.CheckAllDynamicallySizedAreas();
return plc;
}

View File

@@ -1,5 +1,4 @@
using MewtocolNet.Exceptions;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
@@ -50,7 +49,7 @@ namespace MewtocolNet.RegisterBuilding {
Type elementType = data.dotnetVarType.GetElementType();
uint numericSizePerElement = (uint)elementType.DetermineTypeByteSize();
uint numericSizePerElement = (uint)elementType.DetermineTypeByteIntialSize();
if (elementType.IsEnum && numericSizePerElement > 4) {
if (data.boundProperty != null) {
@@ -60,25 +59,10 @@ namespace MewtocolNet.RegisterBuilding {
}
}
var sizeStateFlags = DynamicSizeState.None;
//string with size hint
if (elementType == typeof(string) && data.perElementByteSizeHint != null) {
numericSizePerElement = (uint)data.byteSizeHint + 4;
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
} else if (elementType == typeof(string)) {
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate;
}
var parameters = new object[] {
data.memAddress,
data.byteSizeHint,
data.arrayIndicies,
sizeStateFlags,
data.name
};
@@ -89,7 +73,6 @@ namespace MewtocolNet.RegisterBuilding {
typeof(uint),
typeof(uint),
typeof(int[]),
typeof(DynamicSizeState),
typeof(string)
}, null);
@@ -107,7 +90,7 @@ namespace MewtocolNet.RegisterBuilding {
//-------------------------------------------
//as single register
uint numericSize = (uint)data.dotnetVarType.DetermineTypeByteSize();
uint numericSize = (uint)data.dotnetVarType.DetermineTypeByteIntialSize();
if (data.dotnetVarType.IsEnum && numericSize > 4) {
if (data.boundProperty != null) {
@@ -117,17 +100,15 @@ namespace MewtocolNet.RegisterBuilding {
}
}
var sizeStateFlags = DynamicSizeState.None;
if(data.dotnetVarType == typeof(string)) {
//string with size hint
if(data.dotnetVarType == typeof(string) && data.byteSizeHint != null) {
if(data.byteSizeHint == null)
throw new NotSupportedException($"Can't create a STRING register without a string size hint");
numericSize = (uint)data.byteSizeHint + 4;
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
if(data.byteSizeHint < 0)
throw new NotSupportedException($"Can't create a STRING register with a string size hint < 0");
} else if (data.dotnetVarType == typeof(string)) {
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate;
numericSize = 4 + data.byteSizeHint.Value;
}
@@ -135,13 +116,12 @@ namespace MewtocolNet.RegisterBuilding {
Type paramedClass = typeof(SingleRegister<>).MakeGenericType(data.dotnetVarType);
ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] {
typeof(uint), typeof(uint), typeof(DynamicSizeState) ,typeof(string)
typeof(uint), typeof(uint) ,typeof(string)
}, null);
var parameters = new object[] {
data.memAddress,
numericSize,
sizeStateFlags,
data.name
};
@@ -167,7 +147,7 @@ namespace MewtocolNet.RegisterBuilding {
//finalize set for every
if (generatedInstance == null)
throw new MewtocolException("Failed to build register");
throw new ArgumentException("Failed to build register");
if (collectionTarget != null)
generatedInstance.WithRegisterCollection(collectionTarget);

View File

@@ -1,6 +1,6 @@
using MewtocolNet.Exceptions;
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -12,7 +12,7 @@ namespace MewtocolNet.Registers {
/// </summary>
public class ArrayRegister<T> : Register {
internal int[] indicies;
internal int[] indices;
internal uint addressLength;
@@ -25,12 +25,11 @@ namespace MewtocolNet.Registers {
public ArrayRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indicies , DynamicSizeState dynamicSizeSt, string _name = null) {
internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indicies, string _name = null) {
name = _name;
memoryAddress = _address;
dynamicSizeState = dynamicSizeSt;
indicies = _indicies;
indices = _indicies;
//calc mem length
//because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too
@@ -50,10 +49,25 @@ namespace MewtocolNet.Registers {
if (Value == null) return "null";
if(typeof(T) == typeof(byte[])) {
return ((byte[])Value).ToHexString("-");
}
StringBuilder sb = new StringBuilder();
var valueIenum = (IEnumerable)Value;
foreach (var el in valueIenum) {
sb.Append($"{el}, ");
}
return ArrayToString((Array)Value);
}
/// <inheritdoc/>
public override string GetRegisterString() => "DT";
@@ -63,9 +77,6 @@ namespace MewtocolNet.Registers {
/// <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);
@@ -90,9 +101,6 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return null;
@@ -110,8 +118,9 @@ namespace MewtocolNet.Registers {
AddSuccessRead();
var parsed = PlcValueParser.ParseArray<T>(this, indicies, bytes);
var parsed = PlcValueParser.ParseArray<T>(this, indices, bytes);
UpdateHoldingValue(parsed);
return parsed;
}
@@ -127,15 +136,63 @@ namespace MewtocolNet.Registers {
if (changeTriggerBitArr || changeTriggerGeneral) {
var beforeVal = lastValue;
var beforeValStr = GetValueString();
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
}
}
private string ArrayToString(Array array) {
int rank = array.Rank;
int[] lengths = new int[rank];
int[] indices = new int[rank];
for (int i = 0; i < rank; i++) {
lengths[i] = array.GetLength(i);
}
string result = "[";
result += ArrayToStringRecursive(array, lengths, indices, 0);
result += "]";
return result;
}
private string ArrayToStringRecursive(Array array, int[] lengths, int[] indices, int dimension) {
if (dimension == array.Rank - 1) {
string result = "[";
for (indices[dimension] = 0; indices[dimension] < lengths[dimension]; indices[dimension]++) {
result += array.GetValue(indices).ToString();
if (indices[dimension] < lengths[dimension] - 1) {
result += ",";
}
}
result += "]";
return result;
} else {
string result = "[";
for (indices[dimension] = 0; indices[dimension] < lengths[dimension]; indices[dimension]++) {
result += ArrayToStringRecursive(array, lengths, indices, dimension + 1);
if (indices[dimension] < lengths[dimension] - 1) {
result += ",";
}
}
result += "]";
return result;
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using MewtocolNet.Events;
namespace MewtocolNet.Registers {
@@ -11,7 +12,7 @@ namespace MewtocolNet.Registers {
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
event Action<object> ValueChanged;
event RegisterChangedEventHandler ValueChanged;
/// <summary>
/// Type of the underlying register

View File

@@ -1,4 +1,5 @@
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Events;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.UnderlyingRegisters;
using System;
using System.Collections.Generic;
@@ -14,7 +15,7 @@ namespace MewtocolNet.Registers {
/// <summary>
/// Gets called whenever the value was changed
/// </summary>
public event Action<object> ValueChanged;
public event RegisterChangedEventHandler ValueChanged;
//links to
internal RegisterCollection containedCollection;
@@ -26,8 +27,6 @@ namespace MewtocolNet.Registers {
internal IMemoryArea underlyingMemory;
internal bool autoGenerated;
internal DynamicSizeState dynamicSizeState;
internal object lastValue = null;
internal string name;
internal uint memoryAddress;
@@ -73,10 +72,13 @@ namespace MewtocolNet.Registers {
if (lastValue?.ToString() != val?.ToString()) {
var beforeVal = lastValue;
var beforeValStr = GetValueString();
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
}

View File

@@ -1,7 +1,9 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Logging;
using MewtocolNet.Logging;
using System;
using System.Collections;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
@@ -14,6 +16,8 @@ namespace MewtocolNet.Registers {
/// <typeparam name="T">The type of the numeric value</typeparam>
public class SingleRegister<T> : Register {
internal uint byteLength;
internal uint addressLength;
/// <summary>
@@ -21,17 +25,15 @@ namespace MewtocolNet.Registers {
/// </summary>
public uint AddressLength => addressLength;
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public SingleRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
internal SingleRegister(uint _address, uint _reservedByteSize, DynamicSizeState dynamicSizeSt, string _name = null) {
internal SingleRegister(uint _address, uint _reservedByteSize, string _name = null) {
memoryAddress = _address;
name = _name;
dynamicSizeState = dynamicSizeSt;
addressLength = _reservedByteSize / 2;
Resize(_reservedByteSize);
if (_reservedByteSize == 2) RegisterType = RegisterType.DT;
if(_reservedByteSize == 4) RegisterType = RegisterType.DDT;
@@ -43,6 +45,14 @@ namespace MewtocolNet.Registers {
}
private void Resize (uint reservedByteSize) {
addressLength = reservedByteSize / 2;
if (reservedByteSize % 2 != 0) addressLength++;
byteLength = reservedByteSize;
}
/// <inheritdoc/>
public override string GetAsPLC() {
@@ -55,27 +65,23 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override string GetValueString() {
if (Value != null && typeof(T) == typeof(TimeSpan)) {
if (Value != null && typeof(T) == typeof(TimeSpan)) return $"{Value} [{((TimeSpan)Value).ToPlcTime()}]";
return $"{Value} [{((TimeSpan)Value).ToPlcTime()}]";
if (Value != null && typeof(T) == typeof(Word)) return $"{Value} [{((Word)Value).ToStringBitsPlc()}]";
}
if (Value != null && typeof(T) == typeof(DWord)) return $"{Value} [{((DWord)Value).ToStringBitsPlc()}]";
if (Value != null && typeof(T) == typeof(Word)) {
var hasFlags = typeof(T).GetCustomAttribute<FlagsAttribute>() != null;
return $"{Value} [{((Word)Value).ToStringBitsPlc()}]";
}
if (Value != null && typeof(T).IsEnum) {
if (Value != null && typeof(T).IsEnum && !hasFlags) {
var underlying = Enum.GetUnderlyingType(typeof(T));
object val = Convert.ChangeType(Value, underlying);
return $"{Value} [{val}]";
}
if (Value != null && typeof(T).IsEnum && hasFlags) return $"{Value}";
return Value?.ToString() ?? "null";
}
@@ -86,12 +92,6 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object value) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
if (dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate))
await UpdateDynamicSize();
var encoded = PlcValueParser.Encode(this, (T)value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
@@ -116,12 +116,6 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
if(dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate))
await UpdateDynamicSize();
var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return null;
@@ -132,54 +126,32 @@ namespace MewtocolNet.Registers {
if (matchingReg is SingleRegister<string> sreg && this is SingleRegister<string> selfSreg) {
sreg.addressLength = selfSreg.addressLength;
sreg.dynamicSizeState = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
}
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
}
return SetValueFromBytes(res);
}
internal override async Task UpdateDynamicSize() {
if (typeof(T) == typeof(string)) await UpdateDynamicSizeString();
dynamicSizeState = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
}
private async Task UpdateDynamicSizeString () {
Logger.Log($"Calibrating dynamic register ({GetRegisterWordRangeString()}) from PLC source", LogLevel.Verbose, attachedInterface);
//get the string describer bytes
var bytes = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, 4);
if (bytes == null || bytes.Length == 0 || bytes.All(x => x == 0x0)) {
throw new MewtocolException($"The string register ({GetMewName()}{MemoryAddress}) doesn't exist in the PLC program");
}
var reservedSize = BitConverter.ToInt16(bytes, 0);
var usedSize = BitConverter.ToInt16(bytes, 2);
var wordsSize = Math.Max(0, (uint)(2 + (reservedSize + 1) / 2));
addressLength = wordsSize;
CheckAddressOverflow(memoryAddress, wordsSize);
}
internal override object SetValueFromBytes (byte[] bytes) {
//if string correct the sizing of the byte hint was wrong
if (typeof(T) == typeof(string)) {
var reservedSize = BitConverter.ToInt16(bytes, 0);
if (reservedSize != byteLength - 4)
throw new NotSupportedException(
$"The STRING register at {GetMewName()} is not correctly sized, " +
$"the size should be STRING[{reservedSize}] instead of STRING[{byteLength - 4}]"
);
}
AddSuccessRead();
var parsed = PlcValueParser.Parse<T>(this, bytes);
UpdateHoldingValue(parsed);
return parsed;
@@ -189,11 +161,13 @@ namespace MewtocolNet.Registers {
if (lastValue?.ToString() != val?.ToString()) {
if (val != null) lastValue = (T)val;
else lastValue = null;
var beforeVal = lastValue;
var beforeValStr = GetValueString();
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
}

View File

@@ -1,5 +1,4 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Registers;
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -136,9 +135,11 @@ namespace MewtocolNet.TypeConversion {
PlcVarType = PlcVarType.STRING,
FromRaw = (reg, bytes) => {
if(bytes == null || bytes.Length <= 4) {
if(bytes.Length == 4) return string.Empty;
throw new MewtocolException("Failed to convert string bytes, response not long enough");
if(bytes == null || bytes.Length < 4) {
throw new Exception("Failed to convert string bytes, response not long enough");
}

View File

@@ -1,5 +1,4 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Registers;
using MewtocolNet.Registers;
using MewtocolNet.TypeConversion;
using System;
using System.Collections.Generic;
@@ -30,7 +29,7 @@ namespace MewtocolNet {
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingType);
if (converter == null)
throw new MewtocolException($"A converter for the dotnet type {underlyingType} doesn't exist");
throw new Exception($"A converter for the dotnet type {underlyingType} doesn't exist");
return (T)converter.FromRawData(register, bytes);
@@ -55,7 +54,7 @@ namespace MewtocolNet {
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingElementType);
if (converter == null)
throw new MewtocolException($"A converter for the dotnet type {underlyingElementType} doesn't exist");
throw new Exception($"A converter for the dotnet type {underlyingElementType} doesn't exist");
//parse the array from one to n dimensions
var outArray = Array.CreateInstance(underlyingElementType, indices);
@@ -66,10 +65,10 @@ namespace MewtocolNet {
}
int sizePerItem = underlyingElementType.DetermineTypeByteSize();
int sizePerItem = underlyingElementType.DetermineTypeByteIntialSize();
var iterateItems = indices.Aggregate((a, x) => a * x);
var indexer = new int[indices.Length];
for (int i = 0; i < iterateItems; i++) {
int j = i * sizePerItem;
@@ -150,7 +149,7 @@ namespace MewtocolNet {
converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingType);
if (converter == null)
throw new MewtocolException($"A converter for the type {underlyingType} doesn't exist");
throw new Exception($"A converter for the type {underlyingType} doesn't exist");
return converter.ToRawData(register, value);

View File

@@ -1,5 +1,4 @@
using MewtocolNet.Exceptions;
using MewtocolNet.Registers;
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
@@ -45,7 +44,7 @@ namespace MewtocolNet {
}
throw new MewtocolException("No default register type found");
throw new Exception("No default register type found");
}
@@ -59,7 +58,7 @@ namespace MewtocolNet {
}
throw new MewtocolException("No default plcvar type found");
throw new Exception("No default plcvar type found");
}

View File

@@ -64,8 +64,6 @@ namespace MewtocolNet.UnderlyingRegisters {
internal async Task<bool> RequestByteReadAsync(ulong addStart, ulong addEnd) {
await CheckDynamicallySizedRegistersAsync();
var station = mewInterface.GetStationNumber();
string requeststring = $"%{station}#RD{GetMewtocolIdent(addStart, addEnd)}";
@@ -116,22 +114,6 @@ namespace MewtocolNet.UnderlyingRegisters {
}
internal async Task CheckDynamicallySizedRegistersAsync() {
//calibrating at runtime sized registers
var uncalibratedStringRegisters = managedRegisters
.SelectMany(x => x.Linked)
.Where(x => x.dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate))
.ToList();
foreach (var register in uncalibratedStringRegisters)
await register.UpdateDynamicSize();
if (uncalibratedStringRegisters.Count > 0)
mewInterface.memoryManager.LinkAndMergeRegisters();
}
private string GetMewtocolIdent() {
StringBuilder asciistring = new StringBuilder("D");

View File

@@ -46,8 +46,7 @@ namespace MewtocolNet.UnderlyingRegisters {
internal async Task OnPlcConnected () {
//check all area for dynamic sized registers
await CheckAllDynamicallySizedAreas();
await Task.CompletedTask;
}
@@ -64,13 +63,14 @@ namespace MewtocolNet.UnderlyingRegisters {
}
//maxes the highest poll level for all registers that contain each other
registers
var ordered = registers
.OrderByDescending(x => x.GetRegisterAddressLen())
.ToList()
.ForEach(x => x.AveragePollLevel(registers, pollLevelOrMode));
.ToList();
ordered.ForEach(x => x.AveragePollLevel(registers, pollLevelOrMode));
//insert into area
foreach (var reg in registers) {
foreach (var reg in ordered) {
TestPollLevelExistence(reg);
@@ -278,7 +278,8 @@ namespace MewtocolNet.UnderlyingRegisters {
//check if the linked group has duplicate type registers
var dupedTypeReg = existinglinkedGroup.Linked.FirstOrDefault(x => x.IsSameAddressAndType(insertReg));
if (dupedTypeReg != null) {
if (dupedTypeReg != null && insertReg.autoGenerated) {
dupedTypeReg.WithBoundProperties(insertReg.boundProperties);
} else {
existinglinkedGroup.Linked.Add(insertReg);
@@ -287,20 +288,6 @@ namespace MewtocolNet.UnderlyingRegisters {
}
internal async Task CheckAllDynamicallySizedAreas () {
foreach (var pollLevel in pollLevels.ToArray()) {
foreach (var area in pollLevel.dataAreas.ToArray()) {
await area.CheckDynamicallySizedRegistersAsync();
}
}
}
internal async Task PollAllAreasAsync() {
foreach (var pollLevel in pollLevels) {