From eb70dac5a81f250f022c344409a65acbb6a1e01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Wei=C3=9F?= <72068105+Sandoun@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:44:20 +0200 Subject: [PATCH] Multiple fixes --- MewtocolNet/Events/PlcConnectionArgs.cs | 13 ++ MewtocolNet/Events/RegisterChanged.cs | 22 +++ MewtocolNet/Exceptions/MewtocolException.cs | 46 ------ MewtocolNet/Helpers/MewtocolHelpers.cs | 15 +- MewtocolNet/InternalEnums/DynamicSizeState.cs | 17 -- MewtocolNet/Mewtocol.cs | 33 +++- MewtocolNet/MewtocolInterface.cs | 84 ++++++---- .../MewtocolInterfaceRegisterHandling.cs | 25 ++- MewtocolNet/MewtocolInterfaceRequests.cs | 49 +++--- MewtocolNet/MewtocolInterfaceSerial.cs | 14 +- MewtocolNet/MewtocolInterfaceTcp.cs | 13 +- MewtocolNet/PLCInfo.cs | 90 ++++++++-- .../RegisterAttributes/StringHintAttribute.cs | 20 +++ MewtocolNet/RegisterBuilding/RBuildBase.cs | 154 ++++++++++-------- MewtocolNet/RegisterBuilding/RBuildMult.cs | 102 ++++++------ .../RegisterBuilding/RegBuilderExtensions.cs | 53 ------ .../RegisterBuilding/RegisterAssembler.cs | 42 ++--- MewtocolNet/Registers/ArrayRegister.cs | 87 ++++++++-- MewtocolNet/Registers/Base/IRegister.cs | 3 +- MewtocolNet/Registers/Base/Register.cs | 12 +- MewtocolNet/Registers/SingleRegister.cs | 104 +++++------- MewtocolNet/TypeConversion/Conversions.cs | 9 +- MewtocolNet/TypeConversion/PlcValueParser.cs | 13 +- .../TypeConversion/PlcVarTypeConversions.cs | 7 +- MewtocolNet/UnderlyingRegisters/DTArea.cs | 18 -- .../UnderlyingRegisters/MemoryAreaManager.cs | 29 +--- 26 files changed, 584 insertions(+), 490 deletions(-) create mode 100644 MewtocolNet/Events/PlcConnectionArgs.cs create mode 100644 MewtocolNet/Events/RegisterChanged.cs delete mode 100644 MewtocolNet/Exceptions/MewtocolException.cs delete mode 100644 MewtocolNet/InternalEnums/DynamicSizeState.cs create mode 100644 MewtocolNet/RegisterAttributes/StringHintAttribute.cs diff --git a/MewtocolNet/Events/PlcConnectionArgs.cs b/MewtocolNet/Events/PlcConnectionArgs.cs new file mode 100644 index 0000000..2409f3b --- /dev/null +++ b/MewtocolNet/Events/PlcConnectionArgs.cs @@ -0,0 +1,13 @@ +using System; + +namespace MewtocolNet.Events { + + public delegate void PlcConnectionEventHandler(object sender, PlcConnectionArgs e); + + public class PlcConnectionArgs : EventArgs { + + + + } + +} diff --git a/MewtocolNet/Events/RegisterChanged.cs b/MewtocolNet/Events/RegisterChanged.cs new file mode 100644 index 0000000..e5e14cf --- /dev/null +++ b/MewtocolNet/Events/RegisterChanged.cs @@ -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; } + + } + +} diff --git a/MewtocolNet/Exceptions/MewtocolException.cs b/MewtocolNet/Exceptions/MewtocolException.cs deleted file mode 100644 index 40b8c2c..0000000 --- a/MewtocolNet/Exceptions/MewtocolException.cs +++ /dev/null @@ -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()}"); - - } - - } - -} diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index 0cb7bf4..78d3844 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -19,14 +19,16 @@ 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); if (typeof(MewtocolExtTypeInit1Word).IsAssignableFrom(type)) return 2; @@ -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)}..."; + + } + /// /// Converts a hex string (AB01C1) to a byte array /// diff --git a/MewtocolNet/InternalEnums/DynamicSizeState.cs b/MewtocolNet/InternalEnums/DynamicSizeState.cs deleted file mode 100644 index bdba8e6..0000000 --- a/MewtocolNet/InternalEnums/DynamicSizeState.cs +++ /dev/null @@ -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, - - } - -} diff --git a/MewtocolNet/Mewtocol.cs b/MewtocolNet/Mewtocol.cs index 5e8e44f..3057d37 100644 --- a/MewtocolNet/Mewtocol.cs +++ b/MewtocolNet/Mewtocol.cs @@ -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 { } + /// + /// A builder for attaching register collections + /// + public PostInit WithRegisters(Action 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; + + } + + } + /// /// Builds and returns the final plc interface /// diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 4a15561..10dbc26 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -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 + + /// + public event PlcConnectionEventHandler Connected; + + /// + public event PlcConnectionEventHandler Disconnected; + + /// + public event RegisterChangedEventHandler RegisterChanged; + + /// + 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 - /// - public event Action Connected; - - /// - public event Action Disconnected; - - /// - public event Action RegisterChanged; - - /// - public event PropertyChangedEventHandler PropertyChanged; - /// 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)" : $" ({asInternal.Name})"); + } + sb.Append($" {asInternal.underlyingSystemType.Name}"); + sb.Append($" changed \"{args.PreviousValueString.Ellipsis(25)}\"" + + $" => \"{asInternal.GetValueString().Ellipsis(75)}\""); + + Logger.Log(sb.ToString(), LogLevel.Change, this); - var sb = new StringBuilder(); - sb.Append(asInternal.GetMewName()); - if (asInternal.Name != null) { - sb.Append(asInternal.autoGenerated ? $" (Auto)" : $" ({o.Name})"); } - sb.Append($" {asInternal.underlyingSystemType.Name}"); - sb.Append($" changed to \"{asInternal.GetValueString()}\""); - Logger.Log(sb.ToString(), LogLevel.Change, this); - - OnRegisterChangedUpdateProps((Register)o); + OnRegisterChangedUpdateProps(asInternal); } /// 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); + } /// @@ -220,6 +245,9 @@ namespace MewtocolNet { /// public async Task SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1, Action 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(); } diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index b79d981..17d962b 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -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, + }); } diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 37054c9..7b6c6f2 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -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 /// /// A PLCInfo class - public async Task GetPLCInfoAsync(int timeout = -1) { + public async Task 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; } - PlcInfo = plcInf; - - return plcInf; + return PlcInfo; } @@ -93,9 +108,6 @@ namespace MewtocolNet { /// public async Task 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 { /// A byte array of the requested DT area public async Task ReadByteRangeNonBlocking(int start, int byteCount, Action 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++; diff --git a/MewtocolNet/MewtocolInterfaceSerial.cs b/MewtocolNet/MewtocolInterfaceSerial.cs index 0068424..47a91c2 100644 --- a/MewtocolNet/MewtocolInterfaceSerial.cs +++ b/MewtocolNet/MewtocolInterfaceSerial.cs @@ -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 TryConnectAsyncMulti() { + private async Task TryConnectAsyncMulti() { var baudRates = Enum.GetValues(typeof(BaudRate)).Cast(); @@ -197,7 +203,7 @@ namespace MewtocolNet { } - private async Task TryConnectAsyncSingle(string port, int baud, int dbits, Parity par, StopBits sbits) { + private async Task TryConnectAsyncSingle(string port, int baud, int dbits, Parity par, StopBits sbits) { try { diff --git a/MewtocolNet/MewtocolInterfaceTcp.cs b/MewtocolNet/MewtocolInterfaceTcp.cs index 4a7269b..b3bb648 100644 --- a/MewtocolNet/MewtocolInterfaceTcp.cs +++ b/MewtocolNet/MewtocolInterfaceTcp.cs @@ -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,8 +127,9 @@ namespace MewtocolNet { } catch (SocketException) { OnMajorSocketExceptionWhileConnecting(); + isConnectingStage = false; - } + } } diff --git a/MewtocolNet/PLCInfo.cs b/MewtocolNet/PLCInfo.cs index 6b0e9fd..e32ea34 100644 --- a/MewtocolNet/PLCInfo.cs +++ b/MewtocolNet/PLCInfo.cs @@ -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 { /// /// Holds various informations about the PLC /// - public struct PLCInfo { + public class PLCInfo : INotifyPropertyChanged { + private PlcType typeCode; + private string typeName; + private OPMode operationMode; + private HWInformation hardwareInformation; + private string selfDiagnosticError; + /// /// The type of the PLC named by Panasonic /// - public PlcType TypeCode { get; private set; } + public PlcType TypeCode { + get => typeCode; + internal set { + typeCode = value; + OnPropChange(); + //update name + typeName = typeCode.ToName(); + OnPropChange(nameof(TypeName)); + } + } /// - /// Contains information about the PLCs operation modes as flags + /// The full qualified name of the PLC /// - public OPMode OperationMode { get; private set; } - - /// - /// Hardware information flags about the PLC - /// - public HWInformation HardwareInformation { get; private set; } + public string TypeName => typeName; /// /// Program capacity in 1K steps /// - public int ProgramCapacity { get; private set; } + public int ProgramCapacity { get; internal set; } /// /// Version of the cpu /// - public string CpuVersion { get; private set; } + public string CpuVersion { get; internal set; } + + /// + /// Contains information about the PLCs operation modes as flags + /// + public OPMode OperationMode { + get => operationMode; + internal set { + operationMode = value; + OnPropChange(); + OnPropChange(nameof(IsRunMode)); + } + } + + /// + /// Hardware information flags about the PLC + /// + public HWInformation HardwareInformation { + get => hardwareInformation; + internal set { + hardwareInformation = value; + OnPropChange(); + + } + } /// /// Current error code of the PLC /// - public string SelfDiagnosticError { get; internal set; } + public string SelfDiagnosticError { + get => selfDiagnosticError; + internal set { + selfDiagnosticError = value; + OnPropChange(); + } + } /// /// Quickcheck for the runmode flag /// public bool IsRunMode => OperationMode.HasFlag(OPMode.RunMode); + public event PropertyChangedEventHandler PropertyChanged; + internal bool TryExtendFromEXRT(string msg) { var regexEXRT = new Regex(@"\%EE\$EX00RT00(?..)(?..)..(?..)(?..)..(?..)(?....)(?..)(?..)(?.)(?....)(?....)(?....).*", 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)); } diff --git a/MewtocolNet/RegisterAttributes/StringHintAttribute.cs b/MewtocolNet/RegisterAttributes/StringHintAttribute.cs new file mode 100644 index 0000000..f7ab9b3 --- /dev/null +++ b/MewtocolNet/RegisterAttributes/StringHintAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace MewtocolNet.RegisterAttributes { + /// + /// Defines a string size hint + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public class StringHintAttribute : Attribute { + + internal int size; + + public StringHintAttribute(int size) { + + this.size = size; + + } + + } + +} diff --git a/MewtocolNet/RegisterBuilding/RBuildBase.cs b/MewtocolNet/RegisterBuilding/RBuildBase.cs index 9db4803..97d6ca2 100644 --- a/MewtocolNet/RegisterBuilding/RBuildBase.cs +++ b/MewtocolNet/RegisterBuilding/RBuildBase.cs @@ -305,7 +305,7 @@ namespace MewtocolNet.RegisterBuilding { /// /// /// - public TempRegister AsType(int? sizeHint = null) { + public TypedRegister AsType() { if (!typeof(T).IsAllowedPlcCastingType()) { @@ -313,10 +313,9 @@ namespace MewtocolNet.RegisterBuilding { } - Data.byteSizeHint = (uint?)sizeHint; Data.dotnetVarType = typeof(T); - return new TempRegister(Data, builder); + return new TypedRegister().Map(this); } @@ -327,7 +326,7 @@ namespace MewtocolNet.RegisterBuilding { /// /// /// - 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); } /// /// Sets the register type as a predefined /// - 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 { /// DWORD32 bit double word interpreted as /// /// - public TempRegister AsType(string type) { + public TypedRegister AsType(string type) { - var stringMatch = Regex.Match(type, @"STRING *\[(?[0-9]*)\]", RegexOptions.IgnoreCase); - var arrayMatch = Regex.Match(type, @"ARRAY *\[(?[0-9]*)..(?[0-9]*)(?:\,(?[0-9]*)..(?[0-9]*))?(?:\,(?[0-9]*)..(?[0-9]*))?\] *OF {1,}(?.*)", RegexOptions.IgnoreCase); + var regexString = new Regex(@"^STRING *\[(?[0-9]*)\]$", RegexOptions.IgnoreCase); + var regexArray = new Regex(@"^ARRAY *\[(?[0-9]*)..(?[0-9]*)(?:\,(?[0-9]*)..(?[0-9]*))?(?:\,(?[0-9]*)..(?[0-9]*))?\] *OF {1,}(?.*)$", RegexOptions.IgnoreCase); + + var stringMatch = regexString.Match(type); + var arrayMatch = regexArray.Match(type); if (Enum.TryParse(type, out var parsed)) { @@ -440,52 +442,63 @@ namespace MewtocolNet.RegisterBuilding { //invoke generic AsTypeArray string arrTypeString = arrayMatch.Groups["t"].Value; + Type dotnetArrType = null; - if (Enum.TryParse(arrTypeString, out var parsedArrType)) { + var stringMatchInArray = regexString.Match(arrTypeString); - var dotnetArrType = parsedArrType.GetDefaultDotnetType(); - var indices = new List(); + if (Enum.TryParse(arrTypeString, out var parsedArrType) && parsedArrType != PlcVarType.STRING) { - for (int i = 1; i < 4; i++) { + dotnetArrType = parsedArrType.GetDefaultDotnetType(); - var arrStart = arrayMatch.Groups[$"S{i}"]?.Value; - var arrEnd = arrayMatch.Groups[$"E{i}"]?.Value; - if (string.IsNullOrEmpty(arrStart) || string.IsNullOrEmpty(arrEnd)) break; - var arrStartInt = int.Parse(arrStart); - var arrEndInt = int.Parse(arrEnd); + } else if (stringMatchInArray.Success) { - indices.Add(arrEndInt - arrStartInt + 1); - - } - - var arr = Array.CreateInstance(dotnetArrType, indices.ToArray()); - var arrType = arr.GetType(); - - MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray)); - MethodInfo generic = method.MakeGenericMethod(arrType); - - var tmp = (TempRegister)generic.Invoke(this, new object[] { - indices.ToArray() - }); - - tmp.builder = builder; - tmp.Data = Data; - - return tmp; + dotnetArrType = typeof(string); + //Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value); } else { throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized"); + } + var indices = new List(); + + for (int i = 1; i < 4; i++) { + + var arrStart = arrayMatch.Groups[$"S{i}"]?.Value; + var arrEnd = arrayMatch.Groups[$"E{i}"]?.Value; + if (string.IsNullOrEmpty(arrStart) || string.IsNullOrEmpty(arrEnd)) break; + + var arrStartInt = int.Parse(arrStart); + var arrEndInt = int.Parse(arrEnd); + + indices.Add(arrEndInt - arrStartInt + 1); + + } + + var arr = Array.CreateInstance(dotnetArrType, indices.ToArray()); + var arrType = arr.GetType(); + + MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray)); + MethodInfo generic = method.MakeGenericMethod(arrType); + + var tmp = (TypedRegister)generic.Invoke(this, new object[] { + indices.ToArray() + }); + + tmp.builder = builder; + tmp.Data = Data; + + return tmp; + } 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 = AsTypeArray<short[,,]>(3,4,5)
/// ARRAY [5..6, 0..2] OF DWORD = AsTypeArray<DWord[,]>(2, 3)
/// - public TempRegister AsTypeArray(params int[] indicies) { + public TypedRegister AsTypeArray(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,37 +583,20 @@ namespace MewtocolNet.RegisterBuilding { #region Options stage - public class TempRegister : 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) { } /// /// Sets the poll level of the register /// - public TempRegister 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) { } - - /// - /// Sets the poll level of the register - /// - public TempRegister PollLevel(int level) { - - Data.pollLevel = level; - return this; + return this; } diff --git a/MewtocolNet/RegisterBuilding/RBuildMult.cs b/MewtocolNet/RegisterBuilding/RBuildMult.cs index c832669..84613ba 100644 --- a/MewtocolNet/RegisterBuilding/RBuildMult.cs +++ b/MewtocolNet/RegisterBuilding/RBuildMult.cs @@ -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 AsType(int? sizeHint = null) => new TempRegister().Map(base.AsType(sizeHint)); + public new TypedRegister AsType() => new TypedRegister().Map(base.AsType()); - 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(params int[] indicies) => new TempRegister().Map(base.AsTypeArray(indicies)); + public new TypedRegister AsTypeArray(params int[] indicies) => new TypedRegister().Map(base.AsTypeArray(indicies)); + + + } + + #endregion + + #region Typing size hint + + public new class TypedRegister : RBuildBase.TypedRegister { + + public new OptionsRegister SizeHint(int hint) => new OptionsRegister().Map(base.SizeHint(hint)); + + /// + public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level)); + + /// + /// Outputs the generated + /// + public void Out(Action registerOut) { + + Data.registerOut = registerOut; + + } } @@ -65,58 +93,17 @@ namespace MewtocolNet.RegisterBuilding { #region Options stage - public new class TempRegister : RBuildBase.TempRegister { + public new class OptionsRegister : RBuildBase.OptionsRegister { - internal TempRegister() { } - - internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { } - - /// - public new TempRegister PollLevel(int level) => new TempRegister().Map(base.PollLevel(level)); + /// + public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level)); /// /// Outputs the generated /// - public TempRegister Out(Action registerOut) { + public void Out(Action registerOut) { Data.registerOut = registerOut; - return this; - - } - - } - - public new class TempRegister : RBuildBase.TempRegister { - - internal TempRegister() { } - - internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { } - - /// - public new TempRegister PollLevel(int level) => new TempRegister().Map(base.PollLevel(level)); - - /// - /// Outputs the generated - /// - public TempRegister Out(Action 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 { + + /// + /// Outputs the generated + /// + public void Out(Action registerOut) { + + Data.registerOut = registerOut; + + } + + } + } } diff --git a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs index 136d3c0..3812246 100644 --- a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs +++ b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs @@ -25,32 +25,6 @@ namespace MewtocolNet.RegisterBuilding { interf.AddRegisters(registers.ToArray()); - Task.Run(interf.memoryManager.CheckAllDynamicallySizedAreas); - - return registers.First(); - - } - - /// - /// Adds a single register to the plc stack and returns the generated - /// Waits - /// - /// The generated - public static async Task AddRegisterAsync (this IPlc plc, Action 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; - - } - - /// - /// Adds multiple registers to the plc stack at once
- /// Using this over adding each register individually will result in better generation time performance - /// of the

- /// This waits for the memory manager to size all dynamic registers correctly - ///
- public static async Task AddRegistersAsync (this IPlc plc, Action 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; } diff --git a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs index 9b4c305..0f101fb 100644 --- a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs +++ b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs @@ -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); diff --git a/MewtocolNet/Registers/ArrayRegister.cs b/MewtocolNet/Registers/ArrayRegister.cs index 894f0ec..085f0ec 100644 --- a/MewtocolNet/Registers/ArrayRegister.cs +++ b/MewtocolNet/Registers/ArrayRegister.cs @@ -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 { /// public class ArrayRegister : 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,7 +49,22 @@ namespace MewtocolNet.Registers { if (Value == null) return "null"; - return ((byte[])Value).ToHexString("-"); + 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); } @@ -63,9 +77,6 @@ namespace MewtocolNet.Registers { /// public override async Task 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 { /// public override async Task 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(this, indicies, bytes); + var parsed = PlcValueParser.ParseArray(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; + } + + } + } } diff --git a/MewtocolNet/Registers/Base/IRegister.cs b/MewtocolNet/Registers/Base/IRegister.cs index 5e8ae85..fbadb7c 100644 --- a/MewtocolNet/Registers/Base/IRegister.cs +++ b/MewtocolNet/Registers/Base/IRegister.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using MewtocolNet.Events; namespace MewtocolNet.Registers { @@ -11,7 +12,7 @@ namespace MewtocolNet.Registers { /// /// Gets called whenever the value was changed /// - event Action ValueChanged; + event RegisterChangedEventHandler ValueChanged; /// /// Type of the underlying register diff --git a/MewtocolNet/Registers/Base/Register.cs b/MewtocolNet/Registers/Base/Register.cs index e8e3c81..8d57d68 100644 --- a/MewtocolNet/Registers/Base/Register.cs +++ b/MewtocolNet/Registers/Base/Register.cs @@ -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 { /// /// Gets called whenever the value was changed /// - public event Action 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); } diff --git a/MewtocolNet/Registers/SingleRegister.cs b/MewtocolNet/Registers/SingleRegister.cs index 5361477..cca4ca4 100644 --- a/MewtocolNet/Registers/SingleRegister.cs +++ b/MewtocolNet/Registers/SingleRegister.cs @@ -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 { /// The type of the numeric value public class SingleRegister : Register { + internal uint byteLength; + internal uint addressLength; /// @@ -21,17 +25,15 @@ namespace MewtocolNet.Registers { /// 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; + + } + /// public override string GetAsPLC() { @@ -55,26 +65,22 @@ namespace MewtocolNet.Registers { /// 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() != 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 { /// public override async Task 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 { /// public override async Task 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 sreg && this is SingleRegister 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"); + 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}]" + ); } - 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) { - AddSuccessRead(); var parsed = PlcValueParser.Parse(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); } diff --git a/MewtocolNet/TypeConversion/Conversions.cs b/MewtocolNet/TypeConversion/Conversions.cs index 07f1938..788ef55 100644 --- a/MewtocolNet/TypeConversion/Conversions.cs +++ b/MewtocolNet/TypeConversion/Conversions.cs @@ -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"); } diff --git a/MewtocolNet/TypeConversion/PlcValueParser.cs b/MewtocolNet/TypeConversion/PlcValueParser.cs index 9731abc..f731f91 100644 --- a/MewtocolNet/TypeConversion/PlcValueParser.cs +++ b/MewtocolNet/TypeConversion/PlcValueParser.cs @@ -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); diff --git a/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs b/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs index c7e4874..86890f4 100644 --- a/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs +++ b/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs @@ -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"); } diff --git a/MewtocolNet/UnderlyingRegisters/DTArea.cs b/MewtocolNet/UnderlyingRegisters/DTArea.cs index 6ea8adb..961d02c 100644 --- a/MewtocolNet/UnderlyingRegisters/DTArea.cs +++ b/MewtocolNet/UnderlyingRegisters/DTArea.cs @@ -64,8 +64,6 @@ namespace MewtocolNet.UnderlyingRegisters { internal async Task 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"); diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index f9fdbc0..01b7b93 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -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) {