diff --git a/MewtocolNet/ComCassette/CassetteFinder.cs b/MewtocolNet/ComCassette/CassetteFinder.cs index 30d4d3c..d779168 100644 --- a/MewtocolNet/ComCassette/CassetteFinder.cs +++ b/MewtocolNet/ComCassette/CassetteFinder.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -16,7 +14,7 @@ namespace MewtocolNet.ComCassette { /// public class CassetteFinder { - public static async Task> FindClientsAsync (string ipSource = null, int timeoutMs = 100) { + public static async Task> FindClientsAsync(string ipSource = null, int timeoutMs = 100) { var from = new IPEndPoint(IPAddress.Any, 0); @@ -55,7 +53,7 @@ namespace MewtocolNet.ComCassette { //run the interface querys var grouped = await Task.WhenAll(interfacesTasks); - var decomposed = new List(); + var decomposed = new List(); foreach (var grp in grouped) { @@ -73,7 +71,7 @@ namespace MewtocolNet.ComCassette { } - private static IEnumerable GetUseableNetInterfaces () { + private static IEnumerable GetUseableNetInterfaces() { foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) { @@ -103,9 +101,9 @@ namespace MewtocolNet.ComCassette { } - private static async Task> FindClientsForEndpoint (IPEndPoint from, int timeoutMs, string ipEndpointName) { + private static async Task> FindClientsForEndpoint(IPEndPoint from, int timeoutMs, string ipEndpointName) { - var cassettesFound = new List(); + var cassettesFound = new List(); int plcPort = 9090; @@ -115,7 +113,7 @@ namespace MewtocolNet.ComCassette { // The start code of the status transmission response byte[] startCode = new byte[] { 0x88, 0xC0, 0x00 }; - using(var udpClient = new UdpClient()) { + using (var udpClient = new UdpClient()) { udpClient.EnableBroadcast = true; diff --git a/MewtocolNet/ComCassette/CassetteInformation.cs b/MewtocolNet/ComCassette/CassetteInformation.cs index fd5845f..008aeee 100644 --- a/MewtocolNet/ComCassette/CassetteInformation.cs +++ b/MewtocolNet/ComCassette/CassetteInformation.cs @@ -18,7 +18,7 @@ namespace MewtocolNet.ComCassette { /// /// Indicates if the cassette is currently configurating /// - public bool IsConfigurating { get; private set; } + public bool IsConfigurating { get; private set; } /// /// Name of the COM cassette @@ -38,7 +38,7 @@ namespace MewtocolNet.ComCassette { /// /// Subnet mask of the cassette /// - public IPAddress SubnetMask { get; set; } + public IPAddress SubnetMask { get; set; } /// /// Default gateway of the cassette @@ -101,7 +101,7 @@ namespace MewtocolNet.ComCassette { return new CassetteInformation { Name = name, - UsesDHCP = dhcpOn, + UsesDHCP = dhcpOn, IPAddress = ipAdd, SubnetMask = subnetMask, GatewayAddress = gateWaysAdd, @@ -109,14 +109,14 @@ namespace MewtocolNet.ComCassette { Endpoint = endpoint, EndpointName = endpointName, FirmwareVersion = firmwareV, - Port = port, + Port = port, Status = status, }; } - public async Task SendNewConfigAsync () { + public async Task SendNewConfigAsync() { if (IsConfigurating) return; @@ -166,7 +166,7 @@ namespace MewtocolNet.ComCassette { var sendBytesArr = sendBytes.ToArray(); - using(var udpClient = new UdpClient()) { + using (var udpClient = new UdpClient()) { udpClient.Client.Bind(Endpoint); diff --git a/MewtocolNet/ComCassette/CassetteStatus.cs b/MewtocolNet/ComCassette/CassetteStatus.cs index 4f84666..100d1d5 100644 --- a/MewtocolNet/ComCassette/CassetteStatus.cs +++ b/MewtocolNet/ComCassette/CassetteStatus.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Text; +namespace MewtocolNet.ComCassette { -namespace MewtocolNet.ComCassette { - /// /// Needs a list of all status codes.. hard to reverse engineer /// public enum CassetteStatus { - + /// /// Cassette is running as intended /// diff --git a/MewtocolNet/CustomTypes/DWord.cs b/MewtocolNet/CustomTypes/DWord.cs index 2bef2bd..fd31914 100644 --- a/MewtocolNet/CustomTypes/DWord.cs +++ b/MewtocolNet/CustomTypes/DWord.cs @@ -1,18 +1,13 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Drawing; using System.Linq; -using System.Numerics; using System.Runtime.InteropServices; -using System.Text; namespace MewtocolNet { /// /// A DWord is a 16 bit value of 2 bytes /// - public struct DWord : MewtocolExtensionTypeDDT { + public struct DWord : MewtocolExtTypeInit2Word { private int bitLength; @@ -64,7 +59,7 @@ namespace MewtocolNet { get { if (bitIndex > bitLength - 1) throw new IndexOutOfRangeException($"The DWord bit index was out of range ({bitIndex}/{bitLength - 1})"); - + return (value & (1 << bitIndex)) != 0; } set { @@ -76,7 +71,7 @@ namespace MewtocolNet { } } - public void ClearBits () => this.value = 0; + public void ClearBits() => this.value = 0; public override bool Equals(object obj) { @@ -96,13 +91,13 @@ namespace MewtocolNet { public override string ToString() => $"0x{value.ToString("X8")}"; - public string ToStringBits () { + public string ToStringBits() { return Convert.ToString(value, 2).PadLeft(bitLength, '0'); - + } - public string ToStringBitsPlc () { + public string ToStringBitsPlc() { var parts = Convert.ToString(value, 2) .PadLeft(Marshal.SizeOf(value) * 8, '0') @@ -112,6 +107,8 @@ namespace MewtocolNet { } + public int GetIntialPlcByteSize() => 4; + } } diff --git a/MewtocolNet/CustomTypes/MewtocolExtensionType.cs b/MewtocolNet/CustomTypes/MewtocolExtensionType.cs index 85cb76c..6c3dccb 100644 --- a/MewtocolNet/CustomTypes/MewtocolExtensionType.cs +++ b/MewtocolNet/CustomTypes/MewtocolExtensionType.cs @@ -1,7 +1,7 @@ namespace MewtocolNet { - internal interface MewtocolExtensionTypeDT { } + internal interface MewtocolExtTypeInit1Word { } - internal interface MewtocolExtensionTypeDDT { } + internal interface MewtocolExtTypeInit2Word { } } diff --git a/MewtocolNet/CustomTypes/Word.cs b/MewtocolNet/CustomTypes/Word.cs index b544e51..b10bd0d 100644 --- a/MewtocolNet/CustomTypes/Word.cs +++ b/MewtocolNet/CustomTypes/Word.cs @@ -1,18 +1,13 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Drawing; using System.Linq; -using System.Numerics; using System.Runtime.InteropServices; -using System.Text; namespace MewtocolNet { /// /// A word is a 16 bit value of 2 bytes /// - public struct Word : MewtocolExtensionTypeDT { + public struct Word : MewtocolExtTypeInit1Word { private int bitLength; @@ -64,7 +59,7 @@ namespace MewtocolNet { get { if (bitIndex > bitLength - 1) throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})"); - + return (value & (1 << bitIndex)) != 0; } set { @@ -76,7 +71,7 @@ namespace MewtocolNet { } } - public void ClearBits () => this.value = 0; + public void ClearBits() => this.value = 0; public override bool Equals(object obj) { @@ -96,13 +91,13 @@ namespace MewtocolNet { public override string ToString() => $"0x{value.ToString("X4")}"; - public string ToStringBits () { + public string ToStringBits() { return Convert.ToString(value, 2).PadLeft(bitLength, '0'); - + } - public string ToStringBitsPlc () { + public string ToStringBitsPlc() { var parts = Convert.ToString(value, 2) .PadLeft(Marshal.SizeOf(value) * 8, '0') diff --git a/MewtocolNet/Documentation/PlcCodeTestedAttribute.cs b/MewtocolNet/Documentation/PlcCodeTestedAttribute.cs index a29c9f8..416c6d3 100644 --- a/MewtocolNet/Documentation/PlcCodeTestedAttribute.cs +++ b/MewtocolNet/Documentation/PlcCodeTestedAttribute.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace MewtocolNet.Documentation { @@ -8,7 +6,7 @@ namespace MewtocolNet.Documentation { internal class PlcCodeTestedAttribute : Attribute { public PlcCodeTestedAttribute() { } - + } } \ No newline at end of file diff --git a/MewtocolNet/Documentation/PlcEXRTAttribute.cs b/MewtocolNet/Documentation/PlcEXRTAttribute.cs index ecffa7f..b2ce4c5 100644 --- a/MewtocolNet/Documentation/PlcEXRTAttribute.cs +++ b/MewtocolNet/Documentation/PlcEXRTAttribute.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; -using System.Text; namespace MewtocolNet.Documentation { [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] internal class PlcEXRTAttribute : Attribute { - - public PlcEXRTAttribute() {} - + + public PlcEXRTAttribute() { } + } } diff --git a/MewtocolNet/Documentation/PlcLegacyAttribute.cs b/MewtocolNet/Documentation/PlcLegacyAttribute.cs index 555ef52..7541d2e 100644 --- a/MewtocolNet/Documentation/PlcLegacyAttribute.cs +++ b/MewtocolNet/Documentation/PlcLegacyAttribute.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; -using System.Text; namespace MewtocolNet.Documentation { [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] internal class PlcLegacyAttribute : Attribute { - - public PlcLegacyAttribute() {} - + + public PlcLegacyAttribute() { } + } } diff --git a/MewtocolNet/Exceptions/MewtocolException.cs b/MewtocolNet/Exceptions/MewtocolException.cs index 8709822..40b8c2c 100644 --- a/MewtocolNet/Exceptions/MewtocolException.cs +++ b/MewtocolNet/Exceptions/MewtocolException.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; +using MewtocolNet.Registers; +using System; namespace MewtocolNet.Exceptions { @@ -17,25 +16,25 @@ namespace MewtocolNet.Exceptions { System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - internal static MewtocolException NotConnectedSend () { + internal static MewtocolException NotConnectedSend() { return new MewtocolException($"Can not send a message to the PLC if it isn't connected"); } - internal static MewtocolException DupeRegister (IRegisterInternal register) { + internal static MewtocolException DupeRegister(Register register) { return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}"); } - internal static MewtocolException DupeNameRegister (IRegisterInternal register) { + 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 (IRegisterInternal registerA, IRegisterInternal registerB) { + 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/Extensions/AsyncExtensions.cs b/MewtocolNet/Extensions/AsyncExtensions.cs index 120dea0..f7f5c8a 100644 --- a/MewtocolNet/Extensions/AsyncExtensions.cs +++ b/MewtocolNet/Extensions/AsyncExtensions.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; using System.Threading; -using System.Net.Sockets; +using System.Threading.Tasks; namespace MewtocolNet { @@ -18,7 +15,7 @@ namespace MewtocolNet { } } - if(task.IsCanceled) return default(T); + if (task.IsCanceled) return default(T); return task.Result; diff --git a/MewtocolNet/Extensions/SerialPortExtensions.cs b/MewtocolNet/Extensions/SerialPortExtensions.cs index 573f608..e94fb50 100644 --- a/MewtocolNet/Extensions/SerialPortExtensions.cs +++ b/MewtocolNet/Extensions/SerialPortExtensions.cs @@ -1,20 +1,18 @@ using System; -using System.Collections.Generic; using System.IO.Ports; -using System.Text; using System.Threading.Tasks; namespace MewtocolNet { internal static class SerialPortExtensions { - public async static Task WriteAsync (this SerialPort serialPort, byte[] buffer, int offset, int count) { + public async static Task WriteAsync(this SerialPort serialPort, byte[] buffer, int offset, int count) { await serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length); } - public async static Task ReadAsync (this SerialPort serialPort, byte[] buffer, int offset, int count) { + public async static Task ReadAsync(this SerialPort serialPort, byte[] buffer, int offset, int count) { var bytesToRead = count; var temp = new byte[count]; @@ -25,12 +23,12 @@ namespace MewtocolNet { } } - public async static Task ReadAsync (this SerialPort serialPort, int count) { + public async static Task ReadAsync(this SerialPort serialPort, int count) { var buffer = new byte[count]; await serialPort.ReadAsync(buffer, 0, count); return buffer; } - + } } diff --git a/MewtocolNet/Helpers/AsyncQueue.cs b/MewtocolNet/Helpers/AsyncQueue.cs index b2c9d47..42fd8c9 100644 --- a/MewtocolNet/Helpers/AsyncQueue.cs +++ b/MewtocolNet/Helpers/AsyncQueue.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; -using System.Linq; namespace MewtocolNet.Helpers { diff --git a/MewtocolNet/Helpers/CRCCalculator.cs b/MewtocolNet/Helpers/CRCCalculator.cs index 388d221..eaa70b8 100644 --- a/MewtocolNet/Helpers/CRCCalculator.cs +++ b/MewtocolNet/Helpers/CRCCalculator.cs @@ -2,7 +2,7 @@ using System.Text; namespace MewtocolNet { - + internal static class CRCCalculator { private static readonly ushort[] crcTable_CRC16_MCRF4XX = { @@ -40,7 +40,7 @@ namespace MewtocolNet { 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78, }; - internal static string BCC_Mew (this string msg) { + internal static string BCC_Mew(this string msg) { byte[] bytes = Encoding.ASCII.GetBytes(msg); byte crc = CRC8(bytes); @@ -49,7 +49,7 @@ namespace MewtocolNet { } - internal static string BCC_Mew7 (this string msg) { + internal static string BCC_Mew7(this string msg) { byte[] bytes = Encoding.ASCII.GetBytes(msg); ushort crc = CRC16_MCRF4XX(bytes); @@ -58,10 +58,10 @@ namespace MewtocolNet { } - private static ushort CRC16_MCRF4XX (byte[] bytes) { + private static ushort CRC16_MCRF4XX(byte[] bytes) { int icrc = 0xFFFF; - + for (int i = 0; i < bytes.Length; i++) { icrc = (icrc >> 8) ^ crcTable_CRC16_MCRF4XX[(icrc ^ bytes[i]) & 0xff]; } @@ -72,7 +72,7 @@ namespace MewtocolNet { } - private static byte CRC8 (byte[] bytes) { + private static byte CRC8(byte[] bytes) { byte xorTotalByte = 0; diff --git a/MewtocolNet/Helpers/LinqHelpers.cs b/MewtocolNet/Helpers/LinqHelpers.cs index 03d9595..a64e140 100644 --- a/MewtocolNet/Helpers/LinqHelpers.cs +++ b/MewtocolNet/Helpers/LinqHelpers.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; namespace MewtocolNet.Helpers { diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index 7f3d009..0cb7bf4 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -5,10 +5,10 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Security.Cryptography; +using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; -using static MewtocolNet.RegisterBuilding.RBuild; namespace MewtocolNet { @@ -19,6 +19,23 @@ namespace MewtocolNet { #region Byte and string operation helpers + public static int DetermineTypeByteSize(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.Namespace.StartsWith("System")) return Marshal.SizeOf(type); + + if (typeof(MewtocolExtTypeInit1Word).IsAssignableFrom(type)) return 2; + if (typeof(MewtocolExtTypeInit2Word).IsAssignableFrom(type)) return 4; + + throw new Exception("Type not supported"); + + } + /// /// Searches a byte array for a pattern /// @@ -53,23 +70,6 @@ namespace MewtocolNet { } - /// - /// Parses the byte string from a incoming RD message - /// - internal static string ParseDTByteString(this string _onString, int _blockSize = 4) { - - if (_onString == null) - return null; - - var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(.{" + _blockSize + "})").Match(_onString); - if (res.Success) { - string val = res.Groups[2].Value; - return val; - } - return null; - - } - /// /// Parses a return message as RCS single bit /// @@ -95,7 +95,7 @@ namespace MewtocolNet { var res = new Regex(@"\%([0-9a-fA-F]{2})\$RC(?(?:0|1){0,8})(..)").Match(_onString); if (res.Success) { - + string val = res.Groups["bits"].Value; return new BitArray(val.Select(c => c == '1').ToArray()); @@ -119,7 +119,7 @@ namespace MewtocolNet { /// /// /// A or null of failed - internal static byte[] ParseDTRawStringAsBytes (this string _onString) { + internal static byte[] ParseDTRawStringAsBytes(this string _onString) { _onString = _onString.Replace("\r", ""); @@ -160,7 +160,7 @@ namespace MewtocolNet { /// /// Converts a hex string (AB01C1) to a byte array /// - internal static byte[] HexStringToByteArray (this string hex) { + internal static byte[] HexStringToByteArray(this string hex) { if (hex == null) return null; return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) @@ -173,14 +173,14 @@ namespace MewtocolNet { /// /// Seperator between the hex numbers /// The byte array - public static string ToHexString (this byte[] arr, string seperator = "") { + public static string ToHexString(this byte[] arr, string seperator = "") { StringBuilder sb = new StringBuilder(); for (int i = 0; i < arr.Length; i++) { byte b = arr[i]; sb.Append(b.ToString("X2")); - if(i < arr.Length - 1) sb.Append(seperator); + if (i < arr.Length - 1) sb.Append(seperator); } return sb.ToString(); @@ -219,7 +219,7 @@ namespace MewtocolNet { /// /// Checks if the register type is boolean /// - internal static bool IsBoolean (this RegisterType type) { + internal static bool IsBoolean(this RegisterType type) { return type == RegisterType.X || type == RegisterType.Y || type == RegisterType.R; @@ -228,7 +228,7 @@ namespace MewtocolNet { /// /// Checks if the register type numeric /// - internal static bool IsNumericDTDDT (this RegisterType type) { + internal static bool IsNumericDTDDT(this RegisterType type) { return type == RegisterType.DT || type == RegisterType.DDT; @@ -243,10 +243,10 @@ namespace MewtocolNet { } - internal static bool CompareIsDuplicate (this IRegisterInternal reg1, IRegisterInternal compare) { + internal static bool CompareIsDuplicate(this Register reg1, Register compare) { bool valCompare = reg1.RegisterType == compare.RegisterType && - reg1.MemoryAddress == compare.MemoryAddress && + reg1.MemoryAddress == compare.MemoryAddress && reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() && reg1.GetSpecialAddress() == compare.GetSpecialAddress(); @@ -254,7 +254,7 @@ namespace MewtocolNet { } - internal static bool CompareIsDuplicateNonCast (this BaseRegister toInsert, BaseRegister compare, List allowOverlappingTypes) { + internal static bool CompareIsDuplicateNonCast(this Register toInsert, Register compare, List allowOverlappingTypes) { foreach (var type in allowOverlappingTypes) { @@ -271,9 +271,9 @@ namespace MewtocolNet { } - internal static bool CompareIsNameDuplicate(this IRegisterInternal reg1, IRegisterInternal compare) { + internal static bool CompareIsNameDuplicate(this Register reg1, Register compare) { - return ( reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name; + return (reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name; } @@ -284,7 +284,7 @@ namespace MewtocolNet { /// /// Converts the enum to a plc name string /// - public static string ToName (this PlcType plcT) { + public static string ToName(this PlcType plcT) { if (plcT == 0) return "Unknown"; @@ -295,7 +295,7 @@ namespace MewtocolNet { /// /// Converts the enum to a decomposed struct /// - public static ParsedPlcName[] ToNameDecompose (this PlcType plcT) { + public static ParsedPlcName[] ToNameDecompose(this PlcType plcT) { if ((int)plcT == 0) return Array.Empty(); @@ -306,7 +306,7 @@ namespace MewtocolNet { /// /// Checks if the PLC type is discontinued /// - public static bool IsDiscontinued (this PlcType plcT) { + public static bool IsDiscontinued(this PlcType plcT) { var memberInfos = plcT.GetType().GetMember(plcT.ToString()); var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == plcT.GetType()); @@ -320,9 +320,9 @@ namespace MewtocolNet { } - #if DEBUG +#if DEBUG - internal static bool WasTestedLive (this PlcType plcT) { + internal static bool WasTestedLive(this PlcType plcT) { var memberInfos = plcT.GetType().GetMember(plcT.ToString()); var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == plcT.GetType()); @@ -336,7 +336,7 @@ namespace MewtocolNet { } - internal static bool IsEXRTPLC (this PlcType plcT) { + internal static bool IsEXRTPLC(this PlcType plcT) { var memberInfos = plcT.GetType().GetMember(plcT.ToString()); var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == plcT.GetType()); @@ -350,7 +350,51 @@ namespace MewtocolNet { } - #endif +#endif + + #endregion + + #region Mapping + + /// + /// Maps the source object to target object. + /// + /// Type of target object. + /// Type of source object. + /// Target object. + /// Source object. + /// Updated target object. + internal static T Map(this T target, TU source) { + + var flags = BindingFlags.NonPublic | BindingFlags.Instance; + + var tprops = target.GetType().GetProperties(); + + tprops.Where(x => x.CanWrite == true).ToList().ForEach(prop => { + // check whether source object has the the property + var sp = source.GetType().GetProperty(prop.Name); + if (sp != null) { + // if yes, copy the value to the matching property + var value = sp.GetValue(source, null); + target.GetType().GetProperty(prop.Name).SetValue(target, value, null); + } + }); + + var tfields = target.GetType().GetFields(flags); + tfields.ToList().ForEach(field => { + + var sp = source.GetType().GetField(field.Name, flags); + + if (sp != null) { + // if yes, copy the value to the matching property + var value = sp.GetValue(source); + target.GetType().GetField(field.Name, flags).SetValue(target, value); + } + + }); + + return target; + } #endregion diff --git a/MewtocolNet/Helpers/ParsedPlcName.cs b/MewtocolNet/Helpers/ParsedPlcName.cs index 395cf08..b2c8a2c 100644 --- a/MewtocolNet/Helpers/ParsedPlcName.cs +++ b/MewtocolNet/Helpers/ParsedPlcName.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; using System.Text.RegularExpressions; namespace MewtocolNet { - + /// /// A structure containing the PLC name parsed /// @@ -20,12 +19,12 @@ namespace MewtocolNet { /// /// The family group of the PLC /// - public string Group { get; internal set; } + public string Group { get; internal set; } /// /// The Memory size of the PLC /// - public float Size { get; internal set; } + public float Size { get; internal set; } /// /// The subtype strings of the plc @@ -35,7 +34,7 @@ namespace MewtocolNet { /// public override string ToString() => WholeName; - internal static ParsedPlcName[] PlcDeconstruct (PlcType plcT) { + internal static ParsedPlcName[] PlcDeconstruct(PlcType plcT) { string wholeStr = plcT.ToString(); @@ -59,7 +58,7 @@ namespace MewtocolNet { string wholeName = $"{groupStr} {sizeFl:0.##}k{(subTypes.Length > 0 ? " " : "")}{string.Join(",", subTypes)}"; if (string.IsNullOrEmpty(subTypes[0])) - subTypes = Array.Empty(); + subTypes = Array.Empty(); retList.Add(new ParsedPlcName { Group = groupStr, diff --git a/MewtocolNet/Helpers/PlcFormat.cs b/MewtocolNet/Helpers/PlcFormat.cs index e080859..45022b3 100644 --- a/MewtocolNet/Helpers/PlcFormat.cs +++ b/MewtocolNet/Helpers/PlcFormat.cs @@ -1,7 +1,5 @@ using System; using System.Collections; -using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -18,7 +16,7 @@ namespace MewtocolNet { /// /// /// - public static string ToPlcTime (this TimeSpan timespan) { + public static string ToPlcTime(this TimeSpan timespan) { if (timespan == null || timespan == TimeSpan.Zero) return $"T#0s"; @@ -41,19 +39,19 @@ namespace MewtocolNet { } - public static TimeSpan ParsePlcTime (string plcTimeFormat) { + public static TimeSpan ParsePlcTime(string plcTimeFormat) { var reg = new Regex(@"(?:T|t)#(?:(?[0-9]{1,2})d)?(?:(?[0-9]{1,2})h)?(?:(?[0-9]{1,2})m)?(?:(?[0-9]{1,2})s)?(?:(?[0-9]{1,3})ms)?"); var match = reg.Match(plcTimeFormat); - if(match.Success) { + if (match.Success) { var days = match.Groups["d"].Value; var hours = match.Groups["h"].Value; var minutes = match.Groups["m"].Value; var seconds = match.Groups["s"].Value; var milliseconds = match.Groups["ms"].Value; - + TimeSpan retTime = TimeSpan.Zero; if (!string.IsNullOrEmpty(days)) retTime += TimeSpan.FromDays(int.Parse(days)); diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index 1582f32..7e0993c 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -60,7 +60,7 @@ namespace MewtocolNet { /// /// Provides an anonymous interface for register reading and writing without memory management /// - RBuild Register { get; } + RBuildAnon Register { get; } /// /// Tries to establish a connection with the device asynchronously diff --git a/MewtocolNet/IPlcEthernet.cs b/MewtocolNet/IPlcEthernet.cs index 0fa9ec4..9fae5f6 100644 --- a/MewtocolNet/IPlcEthernet.cs +++ b/MewtocolNet/IPlcEthernet.cs @@ -1,6 +1,4 @@ -using MewtocolNet.RegisterAttributes; -using System; -using System.Net; +using System.Net; using System.Threading.Tasks; namespace MewtocolNet { diff --git a/MewtocolNet/IPlcSerial.cs b/MewtocolNet/IPlcSerial.cs index 760a9fa..e378598 100644 --- a/MewtocolNet/IPlcSerial.cs +++ b/MewtocolNet/IPlcSerial.cs @@ -1,8 +1,5 @@ -using MewtocolNet.RegisterAttributes; -using System; -using System.Collections.Generic; +using System; using System.IO.Ports; -using System.Text; using System.Threading.Tasks; namespace MewtocolNet { diff --git a/MewtocolNet/InternalEnums/DynamicSizeState.cs b/MewtocolNet/InternalEnums/DynamicSizeState.cs new file mode 100644 index 0000000..bdba8e6 --- /dev/null +++ b/MewtocolNet/InternalEnums/DynamicSizeState.cs @@ -0,0 +1,17 @@ +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 1aaa327..5e8e44f 100644 --- a/MewtocolNet/Mewtocol.cs +++ b/MewtocolNet/Mewtocol.cs @@ -3,14 +3,12 @@ using MewtocolNet.RegisterAttributes; using MewtocolNet.SetupClasses; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO.Ports; using System.Linq; using System.Net; -using System.Text; namespace MewtocolNet { - + /// /// Builder helper for mewtocol interfaces /// @@ -25,7 +23,7 @@ namespace MewtocolNet { /// /// Plc station number 0xEE for direct communication /// - public static PostInit Ethernet (string ip, int port = 9094, int station = 0xEE) { + public static PostInit Ethernet(string ip, int port = 9094, int station = 0xEE) { var instance = new MewtocolInterfaceTcp(); instance.ConfigureConnection(ip, port, station); @@ -62,7 +60,7 @@ namespace MewtocolNet { /// Stop bits of the plc toolport /// Plc station number 0xEE for direct communication /// - public static PostInit Serial (string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 0xEE) { + public static PostInit Serial(string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 0xEE) { TestPortName(portName); @@ -80,7 +78,7 @@ namespace MewtocolNet { /// /// Plc station number 0xEE for direct communication /// - public static PostInit SerialAuto (string portName, int station = 0xEE) { + public static PostInit SerialAuto(string portName, int station = 0xEE) { TestPortName(portName); @@ -93,7 +91,7 @@ namespace MewtocolNet { } - private static void TestPortName (string portName) { + private static void TestPortName(string portName) { var portnames = SerialPort.GetPortNames(); @@ -106,39 +104,6 @@ namespace MewtocolNet { #region Build Order 2 - public class MemoryManagerSettings { - - /// - /// - /// This feature can improve read write times by a big margin but also - /// block outgoing messages inbetween polling cycles more frequently - /// - /// The max distance of the gap between registers (if there is a gap between - /// adjacent registers) to merge them into one request
- /// Example:
- /// - /// We have a register at DT100 (1 word long) and a - /// register at DT101 (1 word long)
- /// - If the max distance is 0 it will not merge them into one request
- /// - If the max distance is 1 it will merge them into one request
- /// - If the max distance is 2 and the next register is at DT102 it will also merge them and ignore the spacer byte in the response
- ///
- ///
- - public int MaxOptimizationDistance { get; set; } = 4; - - /// - /// The max number of registers per request group - /// - public int MaxRegistersPerGroup { get; set; } = -1; - - /// - /// Wether or not to throw an exception when a byte array overlap or duplicate is detected - /// - public bool AllowByteRegisterDupes { get; set; } = false; - - } - public class PollLevelConfigurator { internal Dictionary levelConfigs = new Dictionary(); @@ -148,9 +113,9 @@ namespace MewtocolNet { ///
/// The level to reference /// Delay between poll requests - public PollLevelConfigurator SetLevel (int level, TimeSpan interval) { + public PollLevelConfigurator SetLevel(int level, TimeSpan interval) { - if(level <= 1) + if (level <= 1) throw new NotSupportedException($"The poll level {level} is not configurable"); if (!levelConfigs.ContainsKey(level)) { @@ -230,20 +195,25 @@ namespace MewtocolNet { /// /// General setting for the memory manager /// - public PostInit WithMemoryManagerSettings (Action settings) { + public PostInit WithInterfaceSettings(Action settings) { - var res = new MemoryManagerSettings(); + var res = new InterfaceSettings(); settings.Invoke(res); if (res.MaxOptimizationDistance < 0) throw new NotSupportedException($"A value lower than 0 is not allowed for " + - $"{nameof(MemoryManagerSettings.MaxOptimizationDistance)}"); + $"{nameof(InterfaceSettings.MaxOptimizationDistance)}"); + + if (res.MaxDataBlocksPerWrite < 1) + throw new NotSupportedException($"A value lower than 1 is not allowed for " + + $"{nameof(InterfaceSettings.MaxDataBlocksPerWrite)}"); if (intf is MewtocolInterface imew) { imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance; - imew.memoryManager.maxRegistersPerGroup = res.MaxRegistersPerGroup; - imew.memoryManager.allowByteRegDupes = res.AllowByteRegisterDupes; + imew.memoryManager.pollLevelOrMode = res.PollLevelOverwriteMode; + + imew.maxDataBlocksPerWrite = res.MaxDataBlocksPerWrite; } @@ -254,7 +224,7 @@ namespace MewtocolNet { /// /// A builder for poll custom levels /// - public PostInit WithCustomPollLevels (Action levels) { + public PostInit WithCustomPollLevels(Action levels) { var res = new PollLevelConfigurator(); levels.Invoke(res); @@ -274,17 +244,25 @@ namespace MewtocolNet { /// public EndInit WithRegisterCollections(Action collector) { - var res = new RegCollector(); - collector.Invoke(res); + try { + + var res = new RegCollector(); + collector.Invoke(res); + + if (intf is MewtocolInterface imew) { + imew.WithRegisterCollections(res.collections); + } + + return new EndInit { + postInit = this + }; + + } catch { + + throw; - if (intf is MewtocolInterface imew) { - imew.WithRegisterCollections(res.collections); } - return new EndInit { - postInit = this - }; - } /// diff --git a/MewtocolNet/MewtocolFrameResponse.cs b/MewtocolNet/MewtocolFrameResponse.cs index 5678466..31d65b1 100644 --- a/MewtocolNet/MewtocolFrameResponse.cs +++ b/MewtocolNet/MewtocolFrameResponse.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using MewtocolNet.Helpers; +using MewtocolNet.Helpers; namespace MewtocolNet { @@ -16,10 +13,10 @@ namespace MewtocolNet { public string Error { get; private set; } public static MewtocolFrameResponse Timeout => new MewtocolFrameResponse(403, "Request timed out"); - + public static MewtocolFrameResponse NotIntialized => new MewtocolFrameResponse(405, "PLC was not initialized"); - public MewtocolFrameResponse (string response) { + public MewtocolFrameResponse(string response) { Success = true; ErrorCode = 0; @@ -47,12 +44,12 @@ namespace MewtocolNet { } /// - public static bool operator == (MewtocolFrameResponse c1, MewtocolFrameResponse c2) { + public static bool operator ==(MewtocolFrameResponse c1, MewtocolFrameResponse c2) { return c1.Equals(c2); } /// - public static bool operator != (MewtocolFrameResponse c1, MewtocolFrameResponse c2) { + public static bool operator !=(MewtocolFrameResponse c1, MewtocolFrameResponse c2) { return !c1.Equals(c2); } diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 6b8fb62..4a15561 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -1,6 +1,7 @@ -using MewtocolNet.Logging; -using MewtocolNet.Helpers; +using MewtocolNet.Helpers; +using MewtocolNet.Logging; using MewtocolNet.Registers; +using MewtocolNet.UnderlyingRegisters; using System; using System.Collections.Generic; using System.ComponentModel; @@ -11,7 +12,6 @@ using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using MewtocolNet.UnderlyingRegisters; namespace MewtocolNet { @@ -136,7 +136,7 @@ namespace MewtocolNet { #region Methods - private protected MewtocolInterface () { + private protected MewtocolInterface() { memoryManager = new MemoryAreaManager(this); @@ -156,11 +156,11 @@ namespace MewtocolNet { private void OnRegisterChanged(IRegister o) { - var asInternal = (BaseRegister)o; + var asInternal = (Register)o; var sb = new StringBuilder(); sb.Append(asInternal.GetMewName()); - if(asInternal.Name != null) { + if (asInternal.Name != null) { sb.Append(asInternal.autoGenerated ? $" (Auto)" : $" ({o.Name})"); } sb.Append($" {asInternal.underlyingSystemType.Name}"); @@ -168,20 +168,24 @@ namespace MewtocolNet { Logger.Log(sb.ToString(), LogLevel.Change, this); - OnRegisterChangedUpdateProps((IRegisterInternal)o); + OnRegisterChangedUpdateProps((Register)o); } /// - public virtual Task ConnectAsync() => throw new NotImplementedException(); + public virtual async Task ConnectAsync() { + + await memoryManager.OnPlcConnected(); + + } /// public async Task AwaitFirstDataCycleAsync() => await firstPollTask; /// - public async Task DisconnectAsync () { + public async Task DisconnectAsync() { - if(pollCycleTask != null) await pollCycleTask; + if (pollCycleTask != null) await pollCycleTask; Disconnect(); @@ -211,10 +215,10 @@ namespace MewtocolNet { } /// - public virtual string GetConnectionInfo () => throw new NotImplementedException(); + public virtual string GetConnectionInfo() => throw new NotImplementedException(); /// - public async Task SendCommandAsync (string _msg, bool withTerminator = true, int timeoutMs = -1, Action onReceiveProgress = null) { + public async Task SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1, Action onReceiveProgress = null) { //send request queuedMessages++; @@ -224,7 +228,7 @@ namespace MewtocolNet { if (await Task.WhenAny(tempResponse, Task.Delay(timeoutMs)) != tempResponse) { // timeout logic return MewtocolFrameResponse.Timeout; - } + } tcpMessagesSentThisCycle++; queuedMessages--; @@ -233,7 +237,7 @@ namespace MewtocolNet { } - private protected async Task SendFrameAsync (string frame, bool useBcc = true, bool useCr = true, Action onReceiveProgress = null) { + private protected async Task SendFrameAsync(string frame, bool useBcc = true, bool useCr = true, Action onReceiveProgress = null) { try { @@ -254,16 +258,16 @@ namespace MewtocolNet { //calculate the expected number of frames from the message request int? wordsCountRequested = null; - if(onReceiveProgress != null) { + if (onReceiveProgress != null) { var match = Regex.Match(frame, @"RDD(?[0-9]{5})(?[0-9]{5})"); if (match.Success) { var from = int.Parse(match.Groups["from"].Value); var to = int.Parse(match.Groups["to"].Value); - wordsCountRequested = (to - from) + 1; + wordsCountRequested = (to - from) + 1; } - + } //calc upstream speed @@ -301,7 +305,13 @@ namespace MewtocolNet { for (int j = 0; j < split.Length; j++) { split[j] = split[j].Replace("\r", ""); - split[j] = split[j].Substring(0, split[j].Length - 2); + + if (j < split.Length - 1) { + //on last frame include csum + split[j] = split[j].Substring(0, split[j].Length - 2); + + } + if (j > 0) split[j] = split[j].Replace($"%{GetStationNumber()}", ""); } @@ -324,7 +334,7 @@ namespace MewtocolNet { } - private protected async Task<(byte[], bool)> ReadCommandAsync (int? wordsCountRequested = null, Action onReceiveProgress = null) { + private protected async Task<(byte[], bool)> ReadCommandAsync(int? wordsCountRequested = null, Action onReceiveProgress = null) { //read total List totalResponse = new List(); @@ -334,9 +344,12 @@ namespace MewtocolNet { bool needsRead = false; int readFrames = 0; + int readBytesPayload = 0; do { + if (onReceiveProgress != null && wordsCountRequested != null) onReceiveProgress(0); + SetDownstreamStopWatchStart(); byte[] buffer = new byte[RecBufferSize]; @@ -358,15 +371,6 @@ namespace MewtocolNet { if (commandRes == CommandState.RequestedNextFrame) { - //calc frame progress - if(onReceiveProgress != null && wordsCountRequested != null) { - - var frameBytesCount = tempMsg.Length - 6; - double prog = (double)frameBytesCount / wordsCountRequested.Value; - onReceiveProgress(prog); - - } - //request next frame var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r"); await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length); @@ -375,6 +379,21 @@ namespace MewtocolNet { } + //calc frame progress + if (onReceiveProgress != null && wordsCountRequested != null) { + + if (readFrames == 0) { + readBytesPayload += received.Length - 9; + } else { + readBytesPayload += received.Length - 7; + } + + var frameBytesPlayloadCount = readBytesPayload / 2; + double prog = (double)frameBytesPlayloadCount / (wordsCountRequested.Value * 2); + onReceiveProgress(prog); + + } + readFrames++; } while (needsRead); @@ -385,7 +404,7 @@ namespace MewtocolNet { } - private protected CommandState ParseBufferFrame (byte[] received) { + private protected CommandState ParseBufferFrame(byte[] received) { const char CR = '\r'; const char DELIMITER = '&'; @@ -407,7 +426,7 @@ namespace MewtocolNet { } - private protected int CheckForErrorMsg (string msg) { + private protected int CheckForErrorMsg(string msg) { //error catching Regex errorcheck = new Regex(@"\%..\!([0-9]{2})", RegexOptions.IgnoreCase); @@ -446,7 +465,7 @@ namespace MewtocolNet { } - private protected virtual void OnConnected (PLCInfo plcinf) { + private protected virtual void OnConnected(PLCInfo plcinf) { Logger.Log("Connected to PLC", LogLevel.Info, this); Logger.Log($"{plcinf.ToString()}", LogLevel.Verbose, this); @@ -469,7 +488,7 @@ namespace MewtocolNet { } - private protected virtual void OnDisconnect () { + private protected virtual void OnDisconnect() { BytesPerSecondDownstream = 0; BytesPerSecondUpstream = 0; @@ -483,7 +502,7 @@ namespace MewtocolNet { } - private void SetUpstreamStopWatchStart () { + private void SetUpstreamStopWatchStart() { if (speedStopwatchUpstr == null) { speedStopwatchUpstr = Stopwatch.StartNew(); @@ -496,7 +515,7 @@ namespace MewtocolNet { } - private void SetDownstreamStopWatchStart () { + private void SetDownstreamStopWatchStart() { if (speedStopwatchDownstr == null) { speedStopwatchDownstr = Stopwatch.StartNew(); @@ -509,7 +528,7 @@ namespace MewtocolNet { } - private void CalcUpstreamSpeed (int byteCount) { + private void CalcUpstreamSpeed(int byteCount) { bytesTotalCountedUpstream += byteCount; @@ -519,7 +538,7 @@ namespace MewtocolNet { } - private void CalcDownstreamSpeed (int byteCount) { + private void CalcDownstreamSpeed(int byteCount) { bytesTotalCountedDownstream += byteCount; diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index ecb9288..b79d981 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -1,19 +1,11 @@ -using MewtocolNet.Exceptions; -using MewtocolNet.Logging; +using MewtocolNet.Logging; using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterBuilding; using MewtocolNet.Registers; -using MewtocolNet.SetupClasses; -using MewtocolNet.UnderlyingRegisters; using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; using System.Threading.Tasks; namespace MewtocolNet { @@ -27,7 +19,7 @@ namespace MewtocolNet { private List registerCollections = new List(); - internal IEnumerable RegistersInternal => GetAllRegistersInternal(); + internal IEnumerable RegistersInternal => GetAllRegistersInternal(); public IEnumerable Registers => GetAllRegisters(); @@ -46,7 +38,7 @@ namespace MewtocolNet { } /// - public RBuild Register => new RBuild(this); + public RBuildAnon Register => new RBuildAnon(this); #region Register Polling @@ -200,7 +192,7 @@ namespace MewtocolNet { if (registerCollections.Count != 0) throw new NotSupportedException("Register collections can only be build once"); - var regBuild = RBuild.Factory; + var regBuild = new RBuildMult(this); foreach (var collection in collections) { @@ -259,7 +251,7 @@ namespace MewtocolNet { /// /// Writes back the values changes of the underlying registers to the corrosponding property /// - private void OnRegisterChangedUpdateProps(IRegisterInternal reg) { + private void OnRegisterChangedUpdateProps(Register reg) { var collection = reg.ContainedCollection; if (collection == null) return; @@ -278,19 +270,19 @@ namespace MewtocolNet { #region Register Adding - internal void AddRegisters (params BaseRegister[] registers) { + internal void AddRegisters(params Register[] registers) { InsertRegistersToMemoryStack(registers.ToList()); } - internal void InsertRegistersToMemoryStack (List registers) { + internal void InsertRegistersToMemoryStack(List registers) { memoryManager.LinkAndMergeRegisters(registers); } - private bool CheckDuplicateRegister (IRegisterInternal instance, out IRegisterInternal foundDupe) { + private bool CheckDuplicateRegister(Register instance, out Register foundDupe) { foundDupe = RegistersInternal.FirstOrDefault(x => x.CompareIsDuplicate(instance)); @@ -298,7 +290,7 @@ namespace MewtocolNet { } - private bool CheckDuplicateRegister(IRegisterInternal instance) { + private bool CheckDuplicateRegister(Register instance) { var foundDupe = RegistersInternal.FirstOrDefault(x => x.CompareIsDuplicate(instance)); @@ -306,13 +298,13 @@ namespace MewtocolNet { } - private bool CheckDuplicateNameRegister(IRegisterInternal instance) { + private bool CheckDuplicateNameRegister(Register instance) { return RegistersInternal.Any(x => x.CompareIsNameDuplicate(instance)); } - private bool CheckOverlappingRegister (IRegisterInternal instance, out IRegisterInternal regB) { + private bool CheckOverlappingRegister(Register instance, out Register regB) { //ignore bool registers, they have their own address spectrum regB = null; @@ -349,20 +341,20 @@ namespace MewtocolNet { #region Register accessing /// > - public IRegister GetRegister (string name) { + public IRegister GetRegister(string name) { return RegistersInternal.FirstOrDefault(x => x.Name == name); } /// - public IEnumerable GetAllRegisters () { + public IEnumerable GetAllRegisters() { return memoryManager.GetAllRegisters().Cast(); } - internal IEnumerable GetAllRegistersInternal () { + internal IEnumerable GetAllRegistersInternal() { return memoryManager.GetAllRegisters(); @@ -378,7 +370,7 @@ namespace MewtocolNet { for (int i = 0; i < internals.Count; i++) { - var reg = (IRegisterInternal)internals[i]; + var reg = (Register)internals[i]; reg.ClearValue(); } @@ -391,7 +383,7 @@ namespace MewtocolNet { } - internal void InvokeRegisterChanged(BaseRegister reg) { + internal void InvokeRegisterChanged(Register reg) { RegisterChanged?.Invoke(reg); diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index a44fd16..37054c9 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -1,19 +1,16 @@ using MewtocolNet.Exceptions; using MewtocolNet.Logging; -using MewtocolNet.Registers; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; using System.Threading.Tasks; namespace MewtocolNet { public abstract partial class MewtocolInterface { + internal int maxDataBlocksPerWrite = 8; + #region PLC info getters /// @@ -23,7 +20,7 @@ namespace MewtocolNet { public async Task GetPLCInfoAsync(int timeout = -1) { var resRT = await SendCommandAsync("%EE#RT", timeoutMs: timeout); - + if (!resRT.Success) { //timeouts are ok and dont throw @@ -54,7 +51,7 @@ namespace MewtocolNet { } - PlcInfo = plcInf; + PlcInfo = plcInf; return plcInf; @@ -65,7 +62,7 @@ namespace MewtocolNet { #region Operation mode changing /// - public async Task SetOperationModeAsync (bool setRun) { + public async Task SetOperationModeAsync(bool setRun) { string modeChar = setRun ? "R" : "P"; @@ -84,7 +81,7 @@ namespace MewtocolNet { #endregion - #region Byte range writingv / reading to registers + #region Byte range writing / reading to registers /// /// Writes a byte array to a span over multiple registers at once, @@ -94,19 +91,15 @@ namespace MewtocolNet { /// /// start address of the array /// /// - public async Task WriteByteRange (int start, byte[] byteArr, bool flipBytes = false) { + public async Task WriteByteRange(int start, byte[] byteArr) { - string byteString; + if (!IsConnected) + throw MewtocolException.NotConnectedSend(); - if(flipBytes) { - byteString = byteArr.BigToMixedEndian().ToHexString(); - } else { - byteString = byteArr.ToHexString(); - } + string byteString = byteArr.ToHexString(); var wordLength = byteArr.Length / 2; - if (byteArr.Length % 2 != 0) - wordLength++; + if (byteArr.Length % 2 != 0) wordLength++; string startStr = start.ToString().PadLeft(5, '0'); string endStr = (start + wordLength - 1).ToString().PadLeft(5, '0'); @@ -123,52 +116,85 @@ namespace MewtocolNet { /// doesn't block the receive thread /// /// Start adress - /// Number of bytes to get - /// Flips bytes from big to mixed endian - /// Gets invoked when the progress changes, contains the progress as a double - /// A byte array or null of there was an error - public async Task ReadByteRangeNonBlocking (int start, int count, bool flipBytes = false, Action onProgress = null) { + /// Number of bytes to get + /// Gets invoked when the progress changes, contains the progress as a double from 0 - 1.0 + /// A byte array of the requested DT area + public async Task ReadByteRangeNonBlocking(int start, int byteCount, Action onProgress = null) { - var byteList = new List(); + if (!IsConnected) + throw MewtocolException.NotConnectedSend(); - var wordLength = count / 2; - if (count % 2 != 0) - wordLength++; + onProgress += (p) => Console.WriteLine($"{p * 100:N2}%"); - int blockSize = 8; + //on odd bytes add one word + var wordLength = byteCount / 2; + if (byteCount % 2 != 0) wordLength++; - //read blocks of max 4 words per msg - for (int i = 0; i < wordLength; i += blockSize) { + int maxReadBlockSize = maxDataBlocksPerWrite; - int curWordStart = start + i; - int curWordEnd = curWordStart + blockSize - 1; + if (byteCount < (maxReadBlockSize * 2)) maxReadBlockSize = wordLength; - string startStr = curWordStart.ToString().PadLeft(5, '0'); - string endStr = (curWordEnd).ToString().PadLeft(5, '0'); + int blocksToReadNoOverflow = wordLength / maxReadBlockSize; + int blocksOverflow = wordLength % maxReadBlockSize; + int totalBlocksToRead = blocksOverflow != 0 ? blocksToReadNoOverflow + 1 : blocksToReadNoOverflow; + + List readBytes = new List(); + + async Task ReadBlock (int wordStart, int wordEnd, Action readProg) { + + int blockSize = wordEnd - wordStart + 1; + string startStr = wordStart.ToString().PadLeft(5, '0'); + string endStr = wordEnd.ToString().PadLeft(5, '0'); string requeststring = $"%{GetStationNumber()}#RDD{startStr}{endStr}"; - var result = await SendCommandAsync(requeststring); + + var result = await SendCommandAsync(requeststring, onReceiveProgress: readProg); if (result.Success && !string.IsNullOrEmpty(result.Response)) { - var bytes = result.Response.ParseDTByteString(blockSize * 4).HexStringToByteArray(); - - if (bytes == null) return null; - - if (flipBytes) { - byteList.AddRange(bytes.BigToMixedEndian().Take(count).ToArray()); - } else { - byteList.AddRange(bytes.Take(count).ToArray()); - } + var bytes = result.Response.ParseDTRawStringAsBytes(); + readBytes.AddRange(bytes); } - if (onProgress != null) - onProgress((double)i / wordLength); + } + + //get all full blocks + for (int i = 0; i < blocksToReadNoOverflow; i++) { + + int curWordStart, curWordEnd; + + curWordStart = start + (i * maxReadBlockSize); + curWordEnd = curWordStart + maxReadBlockSize - 1; + + await ReadBlock(curWordStart, curWordEnd, (p) => { + + if (onProgress != null && p != 0) { + var toplevelProg = (double)(i + 1) / totalBlocksToRead; + onProgress(toplevelProg * p); + } + + }); + + //read remaining block + if (i == blocksToReadNoOverflow - 1 && blocksOverflow != 0) { + + if (onProgress != null) + onProgress((double)readBytes.Count / byteCount); + + curWordStart = start + ((i + 1) * maxReadBlockSize); + curWordEnd = curWordStart + blocksOverflow - 1; + + await ReadBlock(curWordStart, curWordEnd, (p) => {}); + + } } - return byteList.ToArray(); + if (onProgress != null) + onProgress((double)1); + + return readBytes.ToArray(); } @@ -181,7 +207,7 @@ namespace MewtocolNet { if (StationNumber != 0xEE && StationNumber > 99) throw new NotSupportedException("Station number was greater 99"); - if(StationNumber == 0xEE) return "EE"; + if (StationNumber == 0xEE) return "EE"; return StationNumber.ToString().PadLeft(2, '0'); diff --git a/MewtocolNet/MewtocolInterfaceSerial.cs b/MewtocolNet/MewtocolInterfaceSerial.cs index 35684a8..0068424 100644 --- a/MewtocolNet/MewtocolInterfaceSerial.cs +++ b/MewtocolNet/MewtocolInterfaceSerial.cs @@ -1,19 +1,14 @@ using MewtocolNet.Logging; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.IO.Ports; using System.Linq; using System.Net.Sockets; -using System.Net; using System.Text; using System.Threading.Tasks; -using System.Threading; -using MewtocolNet.RegisterAttributes; namespace MewtocolNet { - + public sealed class MewtocolInterfaceSerial : MewtocolInterface, IPlcSerial { private bool autoSerial; @@ -40,11 +35,11 @@ namespace MewtocolNet { //Serial internal SerialPort serialClient; - internal MewtocolInterfaceSerial () : base() { } + internal MewtocolInterfaceSerial() : base() { } /// - public IPlcSerial WithPoller () { - + public IPlcSerial WithPoller() { + usePoller = true; return this; @@ -81,12 +76,12 @@ namespace MewtocolNet { } /// - public void ConfigureConnection (string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 0xEE) { + public void ConfigureConnection(string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 0xEE) { PortName = _portName; SerialBaudRate = _baudRate; - SerialDataBits = _dataBits; - SerialParity = _parity; + SerialDataBits = _dataBits; + SerialParity = _parity; SerialStopBits = _stopBits; stationNumber = _station; @@ -98,7 +93,7 @@ namespace MewtocolNet { } - internal void ConfigureConnectionAuto () { + internal void ConfigureConnectionAuto() { autoSerial = true; @@ -107,7 +102,7 @@ namespace MewtocolNet { public override async Task ConnectAsync() => await ConnectAsync(null); /// - public async Task ConnectAsync (Action onTryingConfig = null) { + public async Task ConnectAsync(Action onTryingConfig = null) { void OnTryConfig() { onTryingConfig(); @@ -120,7 +115,7 @@ namespace MewtocolNet { PLCInfo? gotInfo = null; - if(autoSerial) { + if (autoSerial) { Logger.Log($"Connecting [AUTO CONFIGURE]: {PortName}", LogLevel.Info, this); gotInfo = await TryConnectAsyncMulti(); @@ -132,8 +127,9 @@ namespace MewtocolNet { } - if(gotInfo != null) { + if (gotInfo != null) { + await base.ConnectAsync(); OnConnected(gotInfo.Value); } else { @@ -155,7 +151,7 @@ namespace MewtocolNet { } - private async Task TryConnectAsyncMulti () { + private async Task TryConnectAsyncMulti() { var baudRates = Enum.GetValues(typeof(BaudRate)).Cast(); @@ -171,7 +167,7 @@ namespace MewtocolNet { BaudRate._4800, BaudRate._38400, BaudRate._57600, - BaudRate._230400, + BaudRate._230400, }; var dataBits = Enum.GetValues(typeof(DataBits)).Cast(); @@ -187,7 +183,7 @@ namespace MewtocolNet { foreach (var stopBit in stopBits) { var res = await TryConnectAsyncSingle(PortName, (int)baud, (int)databit, parity, stopBit); - if(res != null) return res; + if (res != null) return res; } @@ -201,7 +197,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 { @@ -251,15 +247,15 @@ namespace MewtocolNet { } - private void CloseClient () { + private void CloseClient() { - if(serialClient.IsOpen) { + if (serialClient.IsOpen) { serialClient.Close(); Logger.Log($"Closed [SERIAL]", LogLevel.Verbose, this); } - + } private protected override void OnDisconnect() { @@ -274,7 +270,7 @@ namespace MewtocolNet { } - private void OnSerialPropsChanged () { + private void OnSerialPropsChanged() { OnPropChange(nameof(PortName)); OnPropChange(nameof(SerialBaudRate)); diff --git a/MewtocolNet/MewtocolInterfaceTcp.cs b/MewtocolNet/MewtocolInterfaceTcp.cs index 46f467d..4a7269b 100644 --- a/MewtocolNet/MewtocolInterfaceTcp.cs +++ b/MewtocolNet/MewtocolInterfaceTcp.cs @@ -1,6 +1,5 @@ using MewtocolNet.Exceptions; using MewtocolNet.Logging; -using MewtocolNet.RegisterAttributes; using System; using System.Net; using System.Net.Sockets; @@ -27,12 +26,12 @@ namespace MewtocolNet { /// public IPEndPoint HostEndpoint { get; set; } - internal MewtocolInterfaceTcp () : base() { } + internal MewtocolInterfaceTcp() : base() { } #region TCP connection state handling /// - public void ConfigureConnection (string ip, int port = 9094, int station = 0xEE) { + 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"); @@ -63,7 +62,7 @@ namespace MewtocolNet { } /// - public override async Task ConnectAsync () { + public override async Task ConnectAsync() { try { @@ -110,6 +109,8 @@ namespace MewtocolNet { if (plcinf != null) { + await base.ConnectAsync(); + OnConnected(plcinf.Value); } else { diff --git a/MewtocolNet/PLCInfo.cs b/MewtocolNet/PLCInfo.cs index ce3ae79..6b0e9fd 100644 --- a/MewtocolNet/PLCInfo.cs +++ b/MewtocolNet/PLCInfo.cs @@ -21,7 +21,7 @@ namespace MewtocolNet { /// /// Hardware information flags about the PLC /// - public HWInformation HardwareInformation { get; private set; } + public HWInformation HardwareInformation { get; private set; } /// /// Program capacity in 1K steps @@ -43,11 +43,11 @@ namespace MewtocolNet { /// public bool IsRunMode => OperationMode.HasFlag(OPMode.RunMode); - internal bool TryExtendFromEXRT (string msg) { + internal bool TryExtendFromEXRT(string msg) { var regexEXRT = new Regex(@"\%EE\$EX00RT00(?..)(?..)..(?..)(?..)..(?..)(?....)(?..)(?..)(?.)(?....)(?....)(?....).*", RegexOptions.IgnoreCase); - var match = regexEXRT.Match(msg); - if(match.Success) { + var match = regexEXRT.Match(msg); + if (match.Success) { byte typeCodeByte = byte.Parse(match.Groups["mc"].Value, NumberStyles.HexNumber); @@ -63,7 +63,7 @@ namespace MewtocolNet { } - internal static bool TryFromRT (string msg, out PLCInfo inf) { + internal static bool TryFromRT(string msg, out PLCInfo inf) { var regexRT = new Regex(@"\%EE\$RT(?..)(?..)(?..)(?..)..(?..)(?....).*", RegexOptions.IgnoreCase); var match = regexRT.Match(msg); @@ -99,16 +99,16 @@ namespace MewtocolNet { OperationMode = 0, ProgramCapacity = 0, TypeCode = 0, - + }; /// - public static bool operator == (PLCInfo c1, PLCInfo c2) { + public static bool operator ==(PLCInfo c1, PLCInfo c2) { return c1.Equals(c2); } /// - public static bool operator != (PLCInfo c1, PLCInfo c2) { + public static bool operator !=(PLCInfo c1, PLCInfo c2) { return !c1.Equals(c2); } diff --git a/MewtocolNet/PublicEnums/BaudRate.cs b/MewtocolNet/PublicEnums/BaudRate.cs index 772eefd..8326f42 100644 --- a/MewtocolNet/PublicEnums/BaudRate.cs +++ b/MewtocolNet/PublicEnums/BaudRate.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace MewtocolNet { +namespace MewtocolNet { public enum BaudRate { diff --git a/MewtocolNet/PublicEnums/HWInformation.cs b/MewtocolNet/PublicEnums/HWInformation.cs index 2f9b06d..2105d7f 100644 --- a/MewtocolNet/PublicEnums/HWInformation.cs +++ b/MewtocolNet/PublicEnums/HWInformation.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace MewtocolNet { @@ -13,7 +11,7 @@ namespace MewtocolNet { /// /// Has user ROM /// - UserROM = 1, + UserROM = 1, /// /// Has IC card /// diff --git a/MewtocolNet/PublicEnums/MewtocolVersion.cs b/MewtocolNet/PublicEnums/MewtocolVersion.cs index 8031360..857ffce 100644 --- a/MewtocolNet/PublicEnums/MewtocolVersion.cs +++ b/MewtocolNet/PublicEnums/MewtocolVersion.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Text; +namespace MewtocolNet { -namespace MewtocolNet { - public enum MewtocolVersion { /// /// Unable to specify the version /// - Unknown = 0, + Unknown = 0, /// /// Uses the standard mewtocol lib /// diff --git a/MewtocolNet/PublicEnums/PlcVarType.cs b/MewtocolNet/PublicEnums/PlcVarType.cs index d7ac5de..0c10927 100644 --- a/MewtocolNet/PublicEnums/PlcVarType.cs +++ b/MewtocolNet/PublicEnums/PlcVarType.cs @@ -1,6 +1,4 @@ -using System.Text; - -namespace MewtocolNet { +namespace MewtocolNet { public enum PlcVarType { diff --git a/MewtocolNet/PublicEnums/PollLevelOverwriteMode.cs b/MewtocolNet/PublicEnums/PollLevelOverwriteMode.cs new file mode 100644 index 0000000..ec4aa2f --- /dev/null +++ b/MewtocolNet/PublicEnums/PollLevelOverwriteMode.cs @@ -0,0 +1,16 @@ +namespace MewtocolNet { + + public enum PollLevelOverwriteMode { + + /// + /// The lowest average poll level for overlapping registers gets used + /// + Lowest, + /// + /// The highest average poll level for overlapping registers gets used + /// + Highest, + + } + +} diff --git a/MewtocolNet/PublicEnums/RegisterBuildSource.cs b/MewtocolNet/PublicEnums/RegisterBuildSource.cs index 3b62a58..696e5a7 100644 --- a/MewtocolNet/PublicEnums/RegisterBuildSource.cs +++ b/MewtocolNet/PublicEnums/RegisterBuildSource.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace MewtocolNet.PublicEnums { +namespace MewtocolNet.PublicEnums { public enum RegisterBuildSource { Anonymous, Manual, diff --git a/MewtocolNet/PublicEnums/RegisterType.cs b/MewtocolNet/PublicEnums/RegisterType.cs index 4dcf5ce..13376c0 100644 --- a/MewtocolNet/PublicEnums/RegisterType.cs +++ b/MewtocolNet/PublicEnums/RegisterType.cs @@ -1,6 +1,4 @@ -using System; - -namespace MewtocolNet { +namespace MewtocolNet { /// /// The register prefixed type diff --git a/MewtocolNet/RegisterAttributes/PollLevelAttribute.cs b/MewtocolNet/RegisterAttributes/PollLevelAttribute.cs index 9203657..737739a 100644 --- a/MewtocolNet/RegisterAttributes/PollLevelAttribute.cs +++ b/MewtocolNet/RegisterAttributes/PollLevelAttribute.cs @@ -9,7 +9,7 @@ namespace MewtocolNet.RegisterAttributes { public class PollLevelAttribute : Attribute { internal int pollLevel; - + public PollLevelAttribute(int level) { pollLevel = level; diff --git a/MewtocolNet/RegisterAttributes/RegisterAttribute.cs b/MewtocolNet/RegisterAttributes/RegisterAttribute.cs index 4fd66c9..9d65f73 100644 --- a/MewtocolNet/RegisterAttributes/RegisterAttribute.cs +++ b/MewtocolNet/RegisterAttributes/RegisterAttribute.cs @@ -1,5 +1,4 @@ -using MewtocolNet.RegisterBuilding; -using System; +using System; namespace MewtocolNet.RegisterAttributes { @@ -10,7 +9,7 @@ namespace MewtocolNet.RegisterAttributes { public class RegisterAttribute : Attribute { internal string MewAddress = null; - internal string TypeDef = null; + internal string TypeDef = null; /// /// Builds automatic data transfer between the property below this and @@ -20,8 +19,8 @@ namespace MewtocolNet.RegisterAttributes { /// The type definition from the PLC (STRING[n], ARRAY [0..2] OF ...) public RegisterAttribute(string mewAddress, string plcTypeDef = null) { - MewAddress = mewAddress; - TypeDef = plcTypeDef; + MewAddress = mewAddress; + TypeDef = plcTypeDef; } diff --git a/MewtocolNet/RegisterAttributes/RegisterCollection.cs b/MewtocolNet/RegisterAttributes/RegisterCollection.cs index 497abf1..5217e8a 100644 --- a/MewtocolNet/RegisterAttributes/RegisterCollection.cs +++ b/MewtocolNet/RegisterAttributes/RegisterCollection.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using MewtocolNet.Registers; +using System.ComponentModel; using System.Runtime.CompilerServices; namespace MewtocolNet.RegisterAttributes { diff --git a/MewtocolNet/RegisterAttributes/RegisterPropTarget.cs b/MewtocolNet/RegisterAttributes/RegisterPropTarget.cs index 4468440..ddafcbb 100644 --- a/MewtocolNet/RegisterAttributes/RegisterPropTarget.cs +++ b/MewtocolNet/RegisterAttributes/RegisterPropTarget.cs @@ -15,7 +15,7 @@ namespace MewtocolNet.RegisterAttributes { var sb = new StringBuilder(); sb.Append($"{BoundProperty}"); - if(LinkLength != null) sb.Append($" -Len: {LinkLength}"); + if (LinkLength != null) sb.Append($" -Len: {LinkLength}"); return sb.ToString(); diff --git a/MewtocolNet/RegisterBuilding/ParseResultState.cs b/MewtocolNet/RegisterBuilding/ParseResultState.cs new file mode 100644 index 0000000..142ac3c --- /dev/null +++ b/MewtocolNet/RegisterBuilding/ParseResultState.cs @@ -0,0 +1,19 @@ +namespace MewtocolNet.RegisterBuilding { + internal enum ParseResultState { + + /// + /// The parse try failed at the intial regex match + /// + FailedSoft, + /// + /// The parse try failed at the afer- regex match + /// + FailedHard, + /// + /// The parse try did work + /// + Success, + + } + +} diff --git a/MewtocolNet/RegisterBuilding/RBuildAnon.cs b/MewtocolNet/RegisterBuilding/RBuildAnon.cs new file mode 100644 index 0000000..40ab58f --- /dev/null +++ b/MewtocolNet/RegisterBuilding/RBuildAnon.cs @@ -0,0 +1,99 @@ +using MewtocolNet.Registers; +using System.Threading.Tasks; + +namespace MewtocolNet.RegisterBuilding { + + /// + /// Anonymous register builder + /// + public class RBuildAnon : RBuildBase { + + public RBuildAnon(MewtocolInterface plc) : base(plc) { } + + /// + public SAddress Address(string plcAddrName) { + + return new SAddress { + attachedPlc = this.attachedPLC, + addrString = plcAddrName + }; + + } + + public new class SAddress { + + protected internal MewtocolInterface attachedPlc; + protected internal string addrString; + protected internal string name; + + /// + /// Writes data to the register and bypasses the memory manager
+ ///
+ /// The value to write + /// True if success + public async Task WriteToAsync(T value) { + + try { + + var tempRegister = AssembleTemporaryRegister(); + return await tempRegister.WriteAsync(value); + + } catch { + + throw; + + } + + } + + /// + /// Reads data from the register and bypasses the memory manager
+ ///
+ /// The value read or null if failed + public async Task ReadFromAsync() { + + try { + + var tempRegister = AssembleTemporaryRegister(); + return (T)await tempRegister.ReadAsync(); + + } catch { + + throw; + + } + + } + + private Register AssembleTemporaryRegister() { + + var temp = new RBuildMult(attachedPlc).Address(addrString).AsType(); + + var assembler = new RegisterAssembler(attachedPlc); + return assembler.Assemble(temp.Data); + + } + + } + + } + + public class RBuildSingle : RBuildBase { + + public RBuildSingle(MewtocolInterface plc) : base(plc) { } + + /// + public SAddress Address(string plcAddrName, string name = null) { + + var data = ParseAddress(plcAddrName, name); + + return new SAddress { + Data = data, + builder = this, + }; + + } + + } + +} diff --git a/MewtocolNet/RegisterBuilding/RBuild.cs b/MewtocolNet/RegisterBuilding/RBuildBase.cs similarity index 65% rename from MewtocolNet/RegisterBuilding/RBuild.cs rename to MewtocolNet/RegisterBuilding/RBuildBase.cs index 1ca9264..9db4803 100644 --- a/MewtocolNet/RegisterBuilding/RBuild.cs +++ b/MewtocolNet/RegisterBuilding/RBuildBase.cs @@ -1,63 +1,27 @@ using MewtocolNet.PublicEnums; -using MewtocolNet.RegisterAttributes; -using MewtocolNet.UnderlyingRegisters; using System; -using System.Collections; using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml.Linq; -using static MewtocolNet.RegisterBuilding.RBuild; namespace MewtocolNet.RegisterBuilding { - internal enum ParseResultState { + public class RBuildBase { - /// - /// The parse try failed at the intial regex match - /// - FailedSoft, - /// - /// The parse try failed at the afer- regex match - /// - FailedHard, - /// - /// The parse try did work - /// - Success, + protected internal MewtocolInterface attachedPLC; - } + public RBuildBase() { } - /// - /// Contains useful tools for register creation - /// - public class RBuild { + internal RBuildBase(MewtocolInterface plc) => attachedPLC = plc; - private MewtocolInterface attachedPLC; + internal List unfinishedList = new List(); - public RBuild () { } - - internal RBuild (MewtocolInterface plc) { - - attachedPLC = plc; - - } - - public static RBuild Factory => new RBuild(); - - internal List unfinishedList = new List(); - - #region String parse stage + #region Parser stage //methods to test the input string on - private static List> parseMethods = new List>() { + protected static List> parseMethods = new List>() { (x) => TryBuildBoolean(x), (x) => TryBuildNumericBased(x), @@ -65,55 +29,28 @@ namespace MewtocolNet.RegisterBuilding { }; - internal class SData { - - internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous; - - internal bool wasAddressStringRangeBased; - internal string originalParseStr; - internal string name; - internal RegisterType regType; - internal uint memAddress; - internal byte specialAddress; - internal Type dotnetVarType; - - //optional - internal uint? byteSize; - internal uint? bitSize; - internal int? stringSize; - - internal int pollLevel = 1; - - //only for building from attributes - internal RegisterCollection regCollection; - internal PropertyInfo boundProperty; - - internal string typeDef; - - } - public class SBase { public SBase() { } - internal SBase(SData data, RBuild bldr) { + internal SBase(StepData data, RBuildBase bldr) { Data = data; builder = bldr; } - internal SData Data { get; set; } + internal StepData Data; - internal RBuild builder; + internal RBuildBase builder; } - internal struct ParseResult { + internal protected struct ParseResult { - public ParseResultState state; + internal ParseResultState state; - public string hardFailReason; + internal string hardFailReason; - public SData stepData; + internal StepData stepData; } @@ -200,7 +137,7 @@ namespace MewtocolNet.RegisterBuilding { return new ParseResult { state = ParseResultState.Success, - stepData = new SData { + stepData = new StepData { regType = (RegisterType)(int)regType, memAddress = areaAdd, specialAddress = specialAdd, @@ -248,7 +185,7 @@ namespace MewtocolNet.RegisterBuilding { return new ParseResult { state = ParseResultState.Success, - stepData = new SData { + stepData = new StepData { regType = regType, memAddress = areaAdd, } @@ -288,7 +225,7 @@ namespace MewtocolNet.RegisterBuilding { uint areaAdd = 0; //try cast the prefix - if (!Enum.TryParse(prefix, out regType) || regType != RegisterType.DT) { + if (!Enum.TryParse(prefix, out regType)) { return new ParseResult { state = ParseResultState.FailedHard, @@ -312,31 +249,28 @@ namespace MewtocolNet.RegisterBuilding { return new ParseResult { state = ParseResultState.Success, - stepData = new SData { + stepData = new StepData { regType = RegisterType.DT_BYTE_RANGE, wasAddressStringRangeBased = true, dotnetVarType = typeof(byte[]), memAddress = addresses[0], - byteSize = (addresses[1] - addresses[0] + 1) * 2 + byteSizeHint = (addresses[1] - addresses[0] + 1) * 2 } }; } - /// - /// Starts the register builder for a new mewtocol address
- /// Examples: - /// Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName") - ///
- /// Address name formatted as FP-Address like in FP-Winpro - /// Custom name for the register to referr to it later - public SAddress Address (string plcAddrName, string name = null) { + #endregion + + #region Addressing stage + + internal StepData ParseAddress(string plcAddrName, string name = null) { foreach (var method in parseMethods) { var res = method.Invoke(plcAddrName); - if(res.state == ParseResultState.Success) { + if (res.state == ParseResultState.Success) { if (!string.IsNullOrEmpty(name)) res.stepData.name = name; @@ -345,12 +279,9 @@ namespace MewtocolNet.RegisterBuilding { unfinishedList.Add(res.stepData); - return new SAddress { - Data = res.stepData, - builder = this, - }; + return res.stepData; - } else if(res.state == ParseResultState.FailedHard) { + } else if (res.state == ParseResultState.FailedHard) { throw new Exception(res.hardFailReason); @@ -362,19 +293,9 @@ namespace MewtocolNet.RegisterBuilding { } - //internal use only, adds a type definition (for use when building from attibute) - internal SAddress AddressFromAttribute (string plcAddrName, string typeDef) { - - var built = Address(plcAddrName); - built.Data.typeDef = typeDef; - built.Data.buildSource = RegisterBuildSource.Attribute; - return built; - - } - #endregion - #region Type determination stage + #region Typing stage public class SAddress : SBase { @@ -384,7 +305,7 @@ namespace MewtocolNet.RegisterBuilding { /// /// /// - public TempRegister AsType () { + public TempRegister AsType(int? sizeHint = null) { if (!typeof(T).IsAllowedPlcCastingType()) { @@ -392,6 +313,7 @@ namespace MewtocolNet.RegisterBuilding { } + Data.byteSizeHint = (uint?)sizeHint; Data.dotnetVarType = typeof(T); return new TempRegister(Data, builder); @@ -405,7 +327,7 @@ namespace MewtocolNet.RegisterBuilding { /// /// /// - public TempRegister AsType (Type type) { + public TempRegister AsType(Type type) { //was ranged syntax array build if (Data.wasAddressStringRangeBased && type.IsArray && type.GetArrayRank() == 1) { @@ -416,35 +338,25 @@ namespace MewtocolNet.RegisterBuilding { var elementType = type.GetElementType(); - if (!elementType.IsAllowedPlcCastingType()) { + if (type != typeof(byte[]) && !elementType.IsAllowedPlcCastingType()) { throw new NotSupportedException($"The dotnet type {elementType}, is not supported for PLC type casting"); } - bool isExtensionTypeDT = typeof(MewtocolExtensionTypeDT).IsAssignableFrom(elementType); - bool isExtensionTypeDDT = typeof(MewtocolExtensionTypeDDT).IsAssignableFrom(elementType); - - int byteSizePerItem = 0; - if(elementType.Namespace.StartsWith("System")) { - byteSizePerItem = Marshal.SizeOf(elementType); - } else if (isExtensionTypeDT) { - byteSizePerItem = 2; - } else if (isExtensionTypeDDT) { - byteSizePerItem = 4; - } + int byteSizePerItem = elementType.DetermineTypeByteSize(); //check if it fits without remainder - if(Data.byteSize % byteSizePerItem != 0) { + 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[] { //element count - new int[] { (int)((Data.byteSize / byteSizePerItem) / 2) } + new int[] { (int)((Data.byteSizeHint / byteSizePerItem)) } }); - } else if(Data.wasAddressStringRangeBased) { + } else if (Data.wasAddressStringRangeBased) { throw new NotSupportedException("DT range building is only allowed for 1 dimensional arrays"); @@ -468,7 +380,7 @@ namespace MewtocolNet.RegisterBuilding { } } - + if (!type.IsAllowedPlcCastingType()) { throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting"); @@ -484,7 +396,7 @@ namespace MewtocolNet.RegisterBuilding { /// /// Sets the register type as a predefined /// - public TempRegister AsType (PlcVarType type) { + public TempRegister AsType(PlcVarType type) { Data.dotnetVarType = type.GetDefaultDotnetType(); @@ -509,7 +421,7 @@ namespace MewtocolNet.RegisterBuilding { /// DWORD32 bit double word interpreted as /// ///
- public TempRegister AsType (string type) { + public TempRegister 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); @@ -521,7 +433,7 @@ namespace MewtocolNet.RegisterBuilding { } else if (stringMatch.Success) { Data.dotnetVarType = typeof(string); - Data.stringSize = int.Parse(stringMatch.Groups["len"].Value); + Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value); } else if (arrayMatch.Success) { @@ -540,10 +452,10 @@ namespace MewtocolNet.RegisterBuilding { var arrEnd = arrayMatch.Groups[$"E{i}"]?.Value; if (string.IsNullOrEmpty(arrStart) || string.IsNullOrEmpty(arrEnd)) break; - var arrStartInt = int.Parse(arrStart); + var arrStartInt = int.Parse(arrStart); var arrEndInt = int.Parse(arrEnd); - indices.Add(arrEndInt - arrStartInt + 1); + indices.Add(arrEndInt - arrStartInt + 1); } @@ -553,10 +465,15 @@ namespace MewtocolNet.RegisterBuilding { MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray)); MethodInfo generic = method.MakeGenericMethod(arrType); - return (TempRegister)generic.Invoke(this, new object[] { + var tmp = (TempRegister)generic.Invoke(this, new object[] { indices.ToArray() }); + tmp.builder = builder; + tmp.Data = Data; + + return tmp; + } else { throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized"); @@ -565,7 +482,7 @@ namespace MewtocolNet.RegisterBuilding { } else { throw new NotSupportedException($"The FP type '{type}' was not recognized"); - + } return new TempRegister(Data, builder); @@ -590,7 +507,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 TempRegister AsTypeArray(params int[] indicies) { if (!typeof(T).IsArray) throw new NotSupportedException($"The type {typeof(T)} was no array"); @@ -601,7 +518,7 @@ namespace MewtocolNet.RegisterBuilding { if (arrRank > 3) throw new NotSupportedException($"4+ dimensional arrays are not supported"); - if (!elBaseType.IsAllowedPlcCastingType()) + if (typeof(T) != typeof(byte[]) && !elBaseType.IsAllowedPlcCastingType()) throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC array type casting"); if (arrRank != indicies.Length) @@ -609,30 +526,14 @@ namespace MewtocolNet.RegisterBuilding { Data.dotnetVarType = typeof(T); - return new TempRegister(Data, builder); + int byteSizePerItem = elBaseType.DetermineTypeByteSize(); + int calcedTotalByteSize = indicies.Aggregate((a, x) => a * x) * byteSizePerItem; - } + Data.byteSizeHint = (uint)calcedTotalByteSize; + Data.arrayIndicies = indicies; - /// - /// Automatically finds the best type for the register - /// - public TempRegister AutoType() { - - switch (Data.regType) { - case RegisterType.X: - case RegisterType.Y: - case RegisterType.R: - Data.dotnetVarType = typeof(bool); - break; - case RegisterType.DT: - Data.dotnetVarType = typeof(short); - break; - case RegisterType.DDT: - Data.dotnetVarType = typeof(int); - break; - case RegisterType.DT_BYTE_RANGE: - Data.dotnetVarType = typeof(string); - break; + if (Data.byteSizeHint % byteSizePerItem != 0) { + throw new NotSupportedException($"The array element type {elBaseType} doesn't fit into the adress range"); } return new TempRegister(Data, builder); @@ -647,114 +548,38 @@ namespace MewtocolNet.RegisterBuilding { public class TempRegister : SBase { - internal TempRegister(SData data, RBuild bldr) : base(data, bldr) {} + internal TempRegister() { } + + internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { } /// /// Sets the poll level of the register /// - public TempRegister PollLevel (int level) { + public TempRegister PollLevel(int level) { Data.pollLevel = level; - return this; } - /// - /// Writes data to the register and bypasses the memory manager
- ///
- /// The value to write - /// True if success - public async Task WriteToAsync (T value) => await builder.WriteAnonymousAsync(this, value); - - /// - /// Reads data from the register and bypasses the memory manager
- ///
- /// The value read or null if failed - public async Task ReadFromAsync () => await builder.ReadAnonymousAsync(this); - } public class TempRegister : SBase { - internal TempRegister(SData data, RBuild bldr) : base(data, bldr) { } + internal TempRegister() { } + + internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { } /// /// Sets the poll level of the register /// - public TempRegister PollLevel (int level) { + public TempRegister PollLevel(int level) { Data.pollLevel = level; - return this; } - /// - /// Writes data to the register and bypasses the memory manager
- ///
- /// The value to write - /// True if success - public async Task WriteToAsync(object value) => await builder.WriteAnonymousAsync(this, value); - - /// - /// Reads data from the register and bypasses the memory manager
- ///
- /// The value read or null if failed - public async Task ReadFromAsync () => await builder.ReadAnonymousAsync(this); - - internal TempRegister RegCollection(RegisterCollection col) { - - Data.regCollection = col; - - return this; - - } - - internal TempRegister BoundProp(PropertyInfo prop) { - - Data.boundProperty = prop; - - return this; - - } - - } - - #endregion - - #region Anonymous read/write bindings - - private async Task WriteAnonymousAsync (TempRegister reg, object value) { - - var assembler = new RegisterAssembler(attachedPLC); - var tempRegister = assembler.Assemble(reg.Data); - return await tempRegister.WriteAsync(value); - - } - - private async Task WriteAnonymousAsync(TempRegister reg, object value) { - - var assembler = new RegisterAssembler(attachedPLC); - var tempRegister = assembler.Assemble(reg.Data); - return await tempRegister.WriteAsync(value); - - } - - private async Task ReadAnonymousAsync (TempRegister reg) { - - var assembler = new RegisterAssembler(attachedPLC); - var tempRegister = assembler.Assemble(reg.Data); - return await tempRegister.ReadAsync(); - - } - - private async Task ReadAnonymousAsync(TempRegister reg) { - - var assembler = new RegisterAssembler(attachedPLC); - var tempRegister = assembler.Assemble(reg.Data); - return (T)await tempRegister.ReadAsync(); - } #endregion diff --git a/MewtocolNet/RegisterBuilding/RBuildMult.cs b/MewtocolNet/RegisterBuilding/RBuildMult.cs new file mode 100644 index 0000000..c832669 --- /dev/null +++ b/MewtocolNet/RegisterBuilding/RBuildMult.cs @@ -0,0 +1,129 @@ +using MewtocolNet.PublicEnums; +using MewtocolNet.RegisterAttributes; +using MewtocolNet.Registers; +using System; +using System.Reflection; + +namespace MewtocolNet.RegisterBuilding { + + /// + /// Contains useful tools for bunch register creation + /// + public class RBuildMult : RBuildBase { + + public RBuildMult(MewtocolInterface plc) : base(plc) { } + + #region String parse stage + + /// + /// Starts the register builder for a new mewtocol address
+ /// Examples: + /// Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName") + ///
+ /// Address name formatted as FP-Address like in FP-Winpro + /// Custom name for the register to referr to it later + public SAddress Address(string plcAddrName, string name = null) { + + var data = ParseAddress(plcAddrName, name); + + return new SAddress { + Data = data, + builder = this, + }; + + } + + //internal use only, adds a type definition (for use when building from attibute) + internal SAddress AddressFromAttribute(string plcAddrName, string typeDef) { + + var built = Address(plcAddrName); + built.Data.typeDef = typeDef; + built.Data.buildSource = RegisterBuildSource.Attribute; + return built; + + } + + #endregion + + #region Typing stage + + public new class SAddress : RBuildBase.SAddress { + + public new TempRegister AsType(int? sizeHint = null) => new TempRegister().Map(base.AsType(sizeHint)); + + public new TempRegister AsType(Type type) => new TempRegister().Map(base.AsType(type)); + + public new TempRegister AsType(PlcVarType type) => new TempRegister().Map(base.AsType(type)); + + public new TempRegister AsType(string type) => new TempRegister().Map(base.AsType(type)); + + public new TempRegister AsTypeArray(params int[] indicies) => new TempRegister().Map(base.AsTypeArray(indicies)); + + } + + #endregion + + #region Options stage + + 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; + + } + + } + + 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; + + } + + } + + #endregion + + } + +} diff --git a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs index 7c32095..136d3c0 100644 --- a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs +++ b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs @@ -1,27 +1,112 @@ -using System; +using MewtocolNet.Registers; +using System; +using System.Linq; +using System.Threading.Tasks; namespace MewtocolNet.RegisterBuilding { + public static class RegBuilderExtensions { - public static IPlc AddTrackedRegisters(this IPlc plc, Action builder) { - - if (plc.IsConnected) - throw new Exception("Can't add registers if the PLC is connected"); - - var regBuilder = new RBuild(); - builder.Invoke(regBuilder); + /// + /// Adds a single register to the plc stack and returns the generated
+ /// This waits for the memory manager to size all dynamic registers correctly + ///
+ /// The generated + public static IRegister AddRegister(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()); + 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(); + + } + + /// + /// 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

+ /// WARNING! This will not wait for the memory manager to account for dynamically sized registers + /// like ones with the type..
+ /// use + /// for this case + ///
+ public static IPlc AddRegisters (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()); + + 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 24da53f..9b4c305 100644 --- a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs +++ b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs @@ -3,121 +3,150 @@ using MewtocolNet.RegisterAttributes; using MewtocolNet.Registers; using System; using System.Collections.Generic; -using System.Data; using System.Reflection; using System.Runtime.InteropServices; -using static MewtocolNet.RegisterBuilding.RBuild; namespace MewtocolNet.RegisterBuilding { + internal class RegisterAssembler { internal RegisterCollection collectionTarget; internal MewtocolInterface onInterface; - internal RegisterAssembler (MewtocolInterface interf) { + internal RegisterAssembler(MewtocolInterface interf) { onInterface = interf; } - internal List AssembleAll (RBuild rBuildData, bool flagAutoGenerated = false) { + internal List AssembleAll(RBuildBase rBuildData, bool flagAutoGenerated = false) { - List generatedInstances = new List(); + List generatedInstances = new List(); foreach (var data in rBuildData.unfinishedList) { var generatedInstance = Assemble(data); + generatedInstance.autoGenerated = flagAutoGenerated; + data.registerOut?.Invoke(generatedInstance); + generatedInstances.Add(generatedInstance); - + } return generatedInstances; - } + } - internal BaseRegister Assemble (SData data) { + internal Register Assemble(StepData data) { - //parse all others where the type is known - Type registerClassType = data.dotnetVarType.GetDefaultRegisterHoldingType(); + Register generatedInstance = null; - BaseRegister generatedInstance = null; - - if(data.dotnetVarType.IsArray) { - - Console.WriteLine(); - return new ArrayRegister(0, 0); - - } - - if (data.dotnetVarType.IsEnum) { + if (data.dotnetVarType.IsArray) { //------------------------------------------- - //as numeric register with enum target + //as array register - var underlying = Enum.GetUnderlyingType(data.dotnetVarType); - int numericSize = Marshal.SizeOf(underlying); + Type elementType = data.dotnetVarType.GetElementType(); - if (numericSize > 4) - throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported"); + uint numericSizePerElement = (uint)elementType.DetermineTypeByteSize(); + + if (elementType.IsEnum && numericSizePerElement > 4) { + if (data.boundProperty != null) { + throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported ({data.boundProperty})"); + } else { + throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported"); + } + } + + 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 + }; var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - Type paramedClass = typeof(NumberRegister<>).MakeGenericType(data.dotnetVarType); - ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] { typeof(uint), typeof(string) }, null); + + Type paramedClass = typeof(ArrayRegister<>).MakeGenericType(data.dotnetVarType); + ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] { + typeof(uint), + typeof(uint), + typeof(int[]), + typeof(DynamicSizeState), + typeof(string) + }, null); - var parameters = new object[] { data.memAddress, data.name }; - var instance = (BaseRegister)constr.Invoke(parameters); + var instance = (Register)constr.Invoke(parameters); - instance.RegisterType = numericSize > 2 ? RegisterType.DDT : RegisterType.DT; + instance.RegisterType = RegisterType.DT_BYTE_RANGE; + + if (data.boundProperty != null && data.boundProperty.PropertyType != data.dotnetVarType) + throw new TypeAccessException($"The bound property {data.boundProperty} must by of type: {data.dotnetVarType}"); generatedInstance = instance; - } else if (registerClassType.IsGenericType) { + } else if (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType()) { //------------------------------------------- - //as numeric register + //as single register - //create a new bregister instance - var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + uint numericSize = (uint)data.dotnetVarType.DetermineTypeByteSize(); - //int _adress, Type _enumType = null, string _name = null - var parameters = new object[] { data.memAddress, data.name }; - var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null); + if (data.dotnetVarType.IsEnum && numericSize > 4) { + if (data.boundProperty != null) { + throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported ({data.boundProperty})"); + } else { + throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported"); + } + } - int numericSize = 0; - bool isExtensionTypeDT = typeof(MewtocolExtensionTypeDT).IsAssignableFrom(data.dotnetVarType); - bool isExtensionTypeDDT = typeof(MewtocolExtensionTypeDDT).IsAssignableFrom(data.dotnetVarType); + var sizeStateFlags = DynamicSizeState.None; + + //string with size hint + if(data.dotnetVarType == typeof(string) && data.byteSizeHint != null) { + + numericSize = (uint)data.byteSizeHint + 4; + sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated; + + } else if (data.dotnetVarType == typeof(string)) { + + sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate; - if (data.dotnetVarType.Namespace == "System") { - numericSize = Marshal.SizeOf(data.dotnetVarType); - } else if(isExtensionTypeDT) { - numericSize = 2; - } else if(isExtensionTypeDDT) { - numericSize = 4; - } else { - throw new NotSupportedException($"The type {data.dotnetVarType} is not supported for NumberRegisters"); } - instance.RegisterType = numericSize > 2 ? RegisterType.DDT : RegisterType.DT; + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - generatedInstance = instance; + Type paramedClass = typeof(SingleRegister<>).MakeGenericType(data.dotnetVarType); + ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] { + typeof(uint), typeof(uint), typeof(DynamicSizeState) ,typeof(string) + }, null); - } else if (registerClassType == typeof(ArrayRegister) && data.byteSize != null) { - - //------------------------------------------- - //as byte range register - - ArrayRegister instance = new ArrayRegister(data.memAddress, (uint)data.byteSize, data.name); - generatedInstance = instance; - - } else if (registerClassType == typeof(StringRegister)) { - - //------------------------------------------- - //as byte range register - var instance = (BaseRegister)new StringRegister(data.memAddress, data.name) { - ReservedSize = (short)(data.stringSize ?? 0), + var parameters = new object[] { + data.memAddress, + numericSize, + sizeStateFlags, + data.name }; + + var instance = (Register)constr.Invoke(parameters); + generatedInstance = instance; } else if (data.regType.IsBoolean()) { @@ -152,7 +181,7 @@ namespace MewtocolNet.RegisterBuilding { generatedInstance.underlyingSystemType = data.dotnetVarType; generatedInstance.pollLevel = data.pollLevel; - return generatedInstance; + return generatedInstance; } diff --git a/MewtocolNet/RegisterBuilding/StepData.cs b/MewtocolNet/RegisterBuilding/StepData.cs new file mode 100644 index 0000000..2b44007 --- /dev/null +++ b/MewtocolNet/RegisterBuilding/StepData.cs @@ -0,0 +1,39 @@ +using MewtocolNet.PublicEnums; +using MewtocolNet.RegisterAttributes; +using MewtocolNet.Registers; +using System; +using System.Reflection; + +namespace MewtocolNet.RegisterBuilding { + + internal class StepData { + + //for referencing the output at builder level + internal Action registerOut; + + internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous; + + internal bool wasAddressStringRangeBased; + internal string originalParseStr; + internal string name; + internal RegisterType regType; + internal uint memAddress; + internal byte specialAddress; + internal Type dotnetVarType; + + //optional + internal uint? byteSizeHint; + internal uint? perElementByteSizeHint; + internal int[] arrayIndicies; + + internal int pollLevel = 1; + + //only for building from attributes + internal RegisterCollection regCollection; + internal PropertyInfo boundProperty; + + internal string typeDef; + + } + +} diff --git a/MewtocolNet/Registers/ArrayRegister.cs b/MewtocolNet/Registers/ArrayRegister.cs index 04facc4..894f0ec 100644 --- a/MewtocolNet/Registers/ArrayRegister.cs +++ b/MewtocolNet/Registers/ArrayRegister.cs @@ -1,10 +1,8 @@ using MewtocolNet.Exceptions; using System; using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; +using System.Linq; using System.Text; -using System.Threading; using System.Threading.Tasks; namespace MewtocolNet.Registers { @@ -12,7 +10,9 @@ namespace MewtocolNet.Registers { /// /// Defines a register containing a string /// - public class ArrayRegister : BaseRegister { + public class ArrayRegister : Register { + + internal int[] indicies; internal uint addressLength; @@ -21,24 +21,21 @@ namespace MewtocolNet.Registers { /// public uint AddressLength => addressLength; - internal uint ReservedBytesSize { get; set; } - - internal ushort? ReservedBitSize { get; set; } - [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] public ArrayRegister() => throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); - internal ArrayRegister(uint _address, uint _reservedByteSize, string _name = null) { + internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indicies , DynamicSizeState dynamicSizeSt, string _name = null) { name = _name; memoryAddress = _address; - ReservedBytesSize = _reservedByteSize; + dynamicSizeState = dynamicSizeSt; + indicies = _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 - var byteSize = ReservedBytesSize; - if (ReservedBytesSize % 2 != 0) byteSize++; + var byteSize = _reservedByteSize; + if (byteSize % 2 != 0) byteSize++; RegisterType = RegisterType.DT_BYTE_RANGE; addressLength = Math.Max((byteSize / 2), 1); @@ -53,27 +50,8 @@ namespace MewtocolNet.Registers { if (Value == null) return "null"; - if(Value != null && Value is BitArray bitArr) { + return ((byte[])Value).ToHexString("-"); - return bitArr.ToBitString(); - - } else { - - return ((byte[])Value).ToHexString("-"); - - } - - } - - /// - public override string BuildMewtocolQuery() { - - StringBuilder asciistring = new StringBuilder("D"); - - asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append((MemoryAddress + AddressLength - 1).ToString().PadLeft(5, '0')); - - return asciistring.ToString(); } /// @@ -82,6 +60,62 @@ namespace MewtocolNet.Registers { /// public override uint GetRegisterAddressLen() => AddressLength; + /// + 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); + + if (res) { + + //find the underlying memory + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + .FirstOrDefault(x => x.IsSameAddressAndType(this)); + + if (matchingReg != null) + matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded); + + AddSuccessWrite(); + UpdateHoldingValue(value); + + } + + return res; + + } + + /// + 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; + + //var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + //.FirstOrDefault(x => x.IsSameAddressAndType(this)); + + //if (matchingReg != null) + // matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); + + return SetValueFromBytes(res); + + } + + internal override object SetValueFromBytes(byte[] bytes) { + + AddSuccessRead(); + + var parsed = PlcValueParser.ParseArray(this, indicies, bytes); + UpdateHoldingValue(parsed); + return parsed; + + } + /// internal override void UpdateHoldingValue(object val) { diff --git a/MewtocolNet/Registers/Interfaces/IRegister.cs b/MewtocolNet/Registers/Base/IRegister.cs similarity index 91% rename from MewtocolNet/Registers/Interfaces/IRegister.cs rename to MewtocolNet/Registers/Base/IRegister.cs index c75937b..5e8ae85 100644 --- a/MewtocolNet/Registers/Interfaces/IRegister.cs +++ b/MewtocolNet/Registers/Base/IRegister.cs @@ -1,9 +1,7 @@ -using MewtocolNet.Registers; -using System; -using System.Text; +using System; using System.Threading.Tasks; -namespace MewtocolNet { +namespace MewtocolNet.Registers { /// /// An interface for all register types @@ -18,7 +16,7 @@ namespace MewtocolNet { /// /// Type of the underlying register /// - RegisterType RegisterType { get; } + RegisterType RegisterType { get; } /// /// The name of the register @@ -28,7 +26,7 @@ namespace MewtocolNet { /// /// Gets the register address name as in the plc /// - string PLCAddressName { get; } + string PLCAddressName { get; } /// /// The current value of the register diff --git a/MewtocolNet/Registers/BaseRegister.cs b/MewtocolNet/Registers/Base/Register.cs similarity index 70% rename from MewtocolNet/Registers/BaseRegister.cs rename to MewtocolNet/Registers/Base/Register.cs index fc80712..e8e3c81 100644 --- a/MewtocolNet/Registers/BaseRegister.cs +++ b/MewtocolNet/Registers/Base/Register.cs @@ -3,13 +3,13 @@ using MewtocolNet.UnderlyingRegisters; using System; using System.Collections.Generic; using System.ComponentModel; -using System.Reflection; +using System.Linq; using System.Text; using System.Threading.Tasks; namespace MewtocolNet.Registers { - public abstract class BaseRegister : IRegister, IRegisterInternal, INotifyPropertyChanged { + public abstract class Register : IRegister, INotifyPropertyChanged { /// /// Gets called whenever the value was changed @@ -26,6 +26,8 @@ namespace MewtocolNet.Registers { internal IMemoryArea underlyingMemory; internal bool autoGenerated; + internal DynamicSizeState dynamicSizeState; + internal object lastValue = null; internal string name; internal uint memoryAddress; @@ -34,8 +36,10 @@ namespace MewtocolNet.Registers { internal uint successfulReads = 0; internal uint successfulWrites = 0; + internal bool wasOverlapFitted = false; + /// - public RegisterCollection ContainedCollection => containedCollection; + public RegisterCollection ContainedCollection => containedCollection; /// public MewtocolInterface AttachedInterface => attachedInterface; @@ -67,7 +71,7 @@ namespace MewtocolNet.Registers { internal virtual void UpdateHoldingValue(object val) { - if(lastValue?.ToString() != val?.ToString()) { + if (lastValue?.ToString() != val?.ToString()) { lastValue = val; @@ -80,10 +84,10 @@ namespace MewtocolNet.Registers { internal virtual object SetValueFromBytes(byte[] bytes) => throw new NotImplementedException(); - internal void WithRegisterCollection (RegisterCollection collection) => containedCollection = collection; + internal void WithRegisterCollection(RegisterCollection collection) => containedCollection = collection; internal void WithBoundProperty(RegisterPropTarget propInfo) => boundProperties.Add(propInfo); - + internal void WithBoundProperties(IEnumerable propInfos) { foreach (var item in propInfos) @@ -97,30 +101,19 @@ namespace MewtocolNet.Registers { public virtual Task WriteAsync(object data) => throw new NotImplementedException(); + internal virtual Task UpdateDynamicSize () => throw new NotImplementedException(); + #endregion #region Default accessors - public RegisterType GetRegisterType() => RegisterType; - - public virtual string BuildMewtocolQuery() { - - StringBuilder asciistring = new StringBuilder("D"); - asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - return asciistring.ToString(); - - } - - public virtual string GetStartingMemoryArea() => MemoryAddress.ToString(); - public virtual byte? GetSpecialAddress() => null; public virtual string GetValueString() => Value?.ToString() ?? "null"; - public virtual string GetAsPLC () => Value?.ToString() ?? "null"; + public virtual string GetAsPLC() => Value?.ToString() ?? "null"; - public virtual string GetRegisterString() => RegisterType.ToString(); + public virtual string GetRegisterString() => RegisterType == RegisterType.DT_BYTE_RANGE ? "DT" : RegisterType.ToString(); public virtual string GetCombinedName() => $"{GetContainerName()}{(GetContainerName() != null ? "." : "")}{Name ?? "Unnamed"}"; @@ -132,11 +125,11 @@ namespace MewtocolNet.Registers { public virtual uint GetRegisterAddressEnd() => MemoryAddress + GetRegisterAddressLen() - 1; - public string GetRegisterWordRangeString() => $"{GetMewName()} - {MemoryAddress + GetRegisterAddressLen() - 1}"; + public string GetRegisterWordRangeString() => $"{GetMewName()} - {MemoryAddress + GetRegisterAddressLen() - 1}"; #endregion - protected virtual void CheckAddressOverflow (uint addressStart, uint addressLen) { + protected virtual void CheckAddressOverflow(uint addressStart, uint addressLen) { if (addressStart < 0) throw new NotSupportedException("The area address can't be negative"); @@ -146,31 +139,58 @@ namespace MewtocolNet.Registers { } - protected virtual void AddSuccessRead () { + protected virtual void AddSuccessRead() { if (successfulReads == uint.MaxValue) successfulReads = 0; else successfulReads++; } - protected virtual void AddSuccessWrite () { + protected virtual void AddSuccessWrite() { if (successfulWrites == uint.MaxValue) successfulWrites = 0; else successfulWrites++; } - internal virtual bool IsSameAddressAndType (BaseRegister toCompare) { + internal virtual bool IsSameAddressAndType(Register toCompare) { return this.MemoryAddress == toCompare.MemoryAddress && this.RegisterType == toCompare.RegisterType && - this.underlyingSystemType == toCompare.underlyingSystemType && + this.underlyingSystemType == toCompare.underlyingSystemType && this.GetRegisterAddressLen() == toCompare.GetRegisterAddressLen() && this.GetSpecialAddress() == toCompare.GetSpecialAddress(); } - internal virtual bool IsSameAddress (BaseRegister toCompare) { + internal int AveragePollLevel(List testAgainst, PollLevelOverwriteMode mode) { - return (this.MemoryAddress == toCompare.MemoryAddress) && - (this.GetRegisterAddressLen() == toCompare.GetRegisterAddressLen()) && - (this.GetSpecialAddress() == toCompare.GetSpecialAddress()); + var whereAddressFitsInto = this.CanFitAddressRange(testAgainst) + .Where(x => !x.wasOverlapFitted).ToList(); + + this.wasOverlapFitted = true; + if (whereAddressFitsInto.Count == 0) return this.pollLevel; + + whereAddressFitsInto.Add(this); + + int avgLvl = mode == PollLevelOverwriteMode.Highest ? + whereAddressFitsInto.Max(x => x.pollLevel) : whereAddressFitsInto.Min(x => x.pollLevel); + + whereAddressFitsInto.ForEach(x => x.pollLevel = avgLvl); + + return avgLvl; + + } + + internal IEnumerable CanFitAddressRange(List testAgainst) { + + foreach (var reg in testAgainst) { + + if (reg == this) continue; + + bool otherFitsInsideSelf = (reg.MemoryAddress >= this.MemoryAddress) && + (reg.GetRegisterAddressEnd() <= this.GetRegisterAddressEnd()) && + (reg.GetSpecialAddress() == this.GetSpecialAddress()); + + if (otherFitsInsideSelf) yield return reg; + + } } @@ -178,7 +198,7 @@ namespace MewtocolNet.Registers { var sb = new StringBuilder(); sb.Append(GetMewName()); - if(Name != null) sb.Append($" ({Name})"); + if (Name != null) sb.Append($" ({Name})"); sb.Append($" [{this.GetType().Name}({underlyingSystemType.Name})]"); if (Value != null) sb.Append($" Val: {GetValueString()}"); @@ -186,7 +206,7 @@ namespace MewtocolNet.Registers { } - public virtual string ToString (bool additional) { + public virtual string ToString(bool additional) { if (!additional) return this.ToString(); @@ -201,36 +221,32 @@ namespace MewtocolNet.Registers { } - public virtual string Explain () { + public virtual string Explain() { StringBuilder sb = new StringBuilder(); sb.Append($"Address: {GetRegisterWordRangeString()}\n"); if (GetType().IsGenericType) - sb.Append($"Type: {RegisterType}, NumberRegister<{GetType().GenericTypeArguments[0]}>\n"); + sb.Append($"Type: {RegisterType}, {GetType().Name}<{GetType().GenericTypeArguments[0]}>\n"); else sb.AppendLine($"Type: {RegisterType}, {GetType().Name}\n"); sb.Append($"Name: {Name ?? "Not named"}\n"); - if(Value != null) + if (Value != null) sb.Append($"Value: {GetValueString()}\n"); - - sb.Append($"Reads: {successfulReads}, Writes: {successfulWrites}\n"); - sb.Append($"Underlying System Type: {underlyingSystemType}\n"); - - if (this is StringRegister sr) - sb.Append($"Reserved: {sr.ReservedSize}, Used: {sr.UsedSize}\n"); - if (GetSpecialAddress() != null) + sb.Append($"Reads: {successfulReads}, Writes: {successfulWrites}\n"); + + if (GetSpecialAddress() != null) sb.Append($"SPAddress: {GetSpecialAddress():X1}\n"); - if (containedCollection != null) + if (containedCollection != null) sb.Append($"In collection: {containedCollection.GetType()}\n"); - if (boundProperties != null && boundProperties.Count > 0) + if (boundProperties != null && boundProperties.Count > 0) sb.Append($"Bound props: \n\t{string.Join(",\n\t", boundProperties)}"); - else + else sb.Append("No bound properties"); return sb.ToString(); diff --git a/MewtocolNet/Registers/BoolRegister.cs b/MewtocolNet/Registers/BoolRegister.cs index 80fa9ed..3ac657b 100644 --- a/MewtocolNet/Registers/BoolRegister.cs +++ b/MewtocolNet/Registers/BoolRegister.cs @@ -1,19 +1,12 @@ -using MewtocolNet.Exceptions; -using MewtocolNet.RegisterBuilding; -using MewtocolNet.UnderlyingRegisters; -using System; -using System.ComponentModel; -using System.Net; +using System; using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace MewtocolNet.Registers { /// /// Defines a register containing a boolean /// - public class BoolRegister : BaseRegister { + public class BoolRegister : Register { internal byte specialAddress; /// @@ -22,9 +15,9 @@ namespace MewtocolNet.Registers { public byte SpecialAddress => specialAddress; [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] - public BoolRegister() => - throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); - + public BoolRegister() => + throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); + internal BoolRegister(IOType _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) { lastValue = null; @@ -57,24 +50,6 @@ namespace MewtocolNet.Registers { /// public override byte? GetSpecialAddress() => SpecialAddress; - /// - public override string BuildMewtocolQuery() { - - //(R|X|Y)(area add [3] + special add [1]) - StringBuilder asciistring = new StringBuilder(); - - string prefix = RegisterType.ToString(); - string mem = MemoryAddress.ToString(); - string sp = SpecialAddress.ToString("X1"); - - asciistring.Append(prefix); - asciistring.Append(mem.PadLeft(3, '0')); - asciistring.Append(sp); - - return asciistring.ToString(); - - } - /// public override string GetMewName() { @@ -97,7 +72,7 @@ namespace MewtocolNet.Registers { } /// - public override uint GetRegisterAddressLen () => 1; + public override uint GetRegisterAddressLen() => 1; } diff --git a/MewtocolNet/Registers/Interfaces/IRegisterInternal.cs b/MewtocolNet/Registers/Interfaces/IRegisterInternal.cs deleted file mode 100644 index 911cf44..0000000 --- a/MewtocolNet/Registers/Interfaces/IRegisterInternal.cs +++ /dev/null @@ -1,65 +0,0 @@ -using MewtocolNet.RegisterAttributes; -using MewtocolNet.Registers; -using System; -using System.Threading.Tasks; - -namespace MewtocolNet { - internal interface IRegisterInternal { - - event Action ValueChanged; - - //props - - MewtocolInterface AttachedInterface { get; } - - RegisterType RegisterType { get; } - - string Name { get; } - - object Value { get; } - - uint MemoryAddress { get; } - - RegisterCollection ContainedCollection { get; } - - // setters - - void ClearValue(); - - // Accessors - - string GetRegisterString(); - - string GetCombinedName(); - - string GetContainerName(); - - string GetMewName(); - - byte? GetSpecialAddress(); - - string GetStartingMemoryArea(); - - string GetValueString(); - - string BuildMewtocolQuery(); - - uint GetRegisterAddressLen(); - - string GetRegisterWordRangeString(); - - //others - - void TriggerNotifyChange(); - - Task ReadAsync(); - - Task WriteAsync(object data); - - string ToString(); - - string ToString(bool detailed); - - } - -} diff --git a/MewtocolNet/Registers/NumberRegister.cs b/MewtocolNet/Registers/SingleRegister.cs similarity index 53% rename from MewtocolNet/Registers/NumberRegister.cs rename to MewtocolNet/Registers/SingleRegister.cs index 24354c7..5361477 100644 --- a/MewtocolNet/Registers/NumberRegister.cs +++ b/MewtocolNet/Registers/SingleRegister.cs @@ -1,14 +1,10 @@ using MewtocolNet.Exceptions; -using MewtocolNet.UnderlyingRegisters; +using MewtocolNet.Logging; using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -using static MewtocolNet.RegisterBuilding.RBuild; namespace MewtocolNet.Registers { @@ -16,65 +12,34 @@ namespace MewtocolNet.Registers { /// Defines a register containing a number /// /// The type of the numeric value - public class NumberRegister : BaseRegister { + public class SingleRegister : Register { + + internal uint addressLength; + + /// + /// The rgisters memory length + /// + public uint AddressLength => addressLength; + [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] - public NumberRegister() => + public SingleRegister() => throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); - internal NumberRegister (uint _address, string _name = null) { + internal SingleRegister(uint _address, uint _reservedByteSize, DynamicSizeState dynamicSizeSt, string _name = null) { memoryAddress = _address; name = _name; + dynamicSizeState = dynamicSizeSt; + addressLength = _reservedByteSize / 2; - Type numType = typeof(T); - uint areaLen = 0; + if (_reservedByteSize == 2) RegisterType = RegisterType.DT; + if(_reservedByteSize == 4) RegisterType = RegisterType.DDT; + if (typeof(T) == typeof(string)) RegisterType = RegisterType.DT_BYTE_RANGE; - if (typeof(T).IsEnum) { + CheckAddressOverflow(memoryAddress, addressLength); - //for enums - - var underlyingType = typeof(T).GetEnumUnderlyingType(); //the numeric type - areaLen = (uint)(Marshal.SizeOf(underlyingType) / 2) - 1; - - if (areaLen == 0) RegisterType = RegisterType.DT; - if (areaLen == 1) RegisterType = RegisterType.DDT; - if (areaLen >= 2) RegisterType = RegisterType.DT_BYTE_RANGE; - - lastValue = null; - Console.WriteLine(); - - } else { - - //for all others known pre-defined numeric structs - - var allowedTypes = PlcValueParser.GetAllowDotnetTypes(); - if (!allowedTypes.Contains(numType)) - throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); - - areaLen = (uint)(Marshal.SizeOf(numType) / 2) - 1; - RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT; - - lastValue = null; - - } - - CheckAddressOverflow(memoryAddress, areaLen); - - } - - /// - public override string BuildMewtocolQuery() { - - StringBuilder asciistring = new StringBuilder("D"); - asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - - int offsetAddress = 0; - if(RegisterType == RegisterType.DDT) - offsetAddress = 1; - - asciistring.Append((MemoryAddress + offsetAddress).ToString().PadLeft(5, '0')); - return asciistring.ToString(); + lastValue = null; } @@ -90,7 +55,7 @@ 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()}]"; @@ -116,24 +81,27 @@ namespace MewtocolNet.Registers { } /// - public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2); + public override uint GetRegisterAddressLen() => AddressLength; /// - public override async Task WriteAsync (object value) { + 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); - if(res) { + if (res) { //find the underlying memory var matchingReg = attachedInterface.memoryManager.GetAllRegisters() .FirstOrDefault(x => x.IsSameAddressAndType(this)); - if (matchingReg != null) + if (matchingReg != null) matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded); AddSuccessWrite(); @@ -151,19 +119,62 @@ namespace MewtocolNet.Registers { if (!attachedInterface.IsConnected) throw MewtocolException.NotConnectedSend(); - var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2, false); + if(dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate)) + await UpdateDynamicSize(); + + var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); if (res == null) return null; - var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() .FirstOrDefault(x => x.IsSameAddressAndType(this)); - if (matchingReg != null) + if (matchingReg != null) { + + 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"); + + } + + 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(); diff --git a/MewtocolNet/Registers/StringRegister.cs b/MewtocolNet/Registers/StringRegister.cs deleted file mode 100644 index 1531d0e..0000000 --- a/MewtocolNet/Registers/StringRegister.cs +++ /dev/null @@ -1,101 +0,0 @@ -using MewtocolNet.Exceptions; -using MewtocolNet.Logging; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; - -namespace MewtocolNet.Registers { - - /// - /// Defines a register containing a string - /// - public class StringRegister : BaseRegister { - - internal short ReservedSize { get; set; } - - internal short UsedSize { get; set; } - - internal uint WordsSize { get; set; } - - internal bool isCalibratedFromPlc = false; - - [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] - public StringRegister() => - throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); - - internal StringRegister (uint _address, string _name = null) { - - name = _name; - memoryAddress = _address; - RegisterType = RegisterType.DT_BYTE_RANGE; - - CheckAddressOverflow(memoryAddress, 0); - - lastValue = null; - - } - - /// - public override string BuildMewtocolQuery() { - - StringBuilder asciistring = new StringBuilder("D"); - - asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append((MemoryAddress + Math.Max(1, WordsSize) - 1).ToString().PadLeft(5, '0')); - - return asciistring.ToString(); - } - - /// - public override string GetValueString() => Value == null ? "null" : $"'{Value}'"; - - /// - public override string GetRegisterString() => "DT"; - - /// - public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize); - - internal async Task CalibrateFromPLC () { - - Logger.Log($"Calibrating string ({PLCAddressName}) from PLC source", LogLevel.Verbose, attachedInterface); - - //get the string describer bytes - var bytes = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, 4, false); - - if (bytes == null || bytes.Length == 0 || bytes.All(x => x == 0x0)) { - - throw new MewtocolException($"The string register ({PLCAddressName}) doesn't exist in the PLC program"); - - } - - ReservedSize = BitConverter.ToInt16(bytes, 0); - UsedSize = BitConverter.ToInt16(bytes, 2); - WordsSize = Math.Max(0, (uint)(2 + (ReservedSize + 1) / 2)); - - CheckAddressOverflow(memoryAddress, WordsSize); - - isCalibratedFromPlc = true; - - } - - /// - internal override void UpdateHoldingValue(object val) { - - if ((val == null && lastValue != null) || val != lastValue) { - - lastValue = val; - - TriggerNotifyChange(); - attachedInterface.InvokeRegisterChanged(this); - - } - - } - - } - -} diff --git a/MewtocolNet/SetupClasses/InterfaceSettings.cs b/MewtocolNet/SetupClasses/InterfaceSettings.cs new file mode 100644 index 0000000..aa19277 --- /dev/null +++ b/MewtocolNet/SetupClasses/InterfaceSettings.cs @@ -0,0 +1,39 @@ +namespace MewtocolNet.SetupClasses { + + public class InterfaceSettings { + + /// + /// + /// This feature can improve read write times by a big margin but also + /// block outgoing messages inbetween polling cycles more frequently + /// + /// The max distance of the gap between registers (if there is a gap between + /// adjacent registers) to merge them into one request
+ /// Example:
+ /// + /// We have a register at DT100 (1 word long) and a + /// register at DT101 (1 word long)
+ /// - If the max distance is 0 it will not merge them into one request
+ /// - If the max distance is 1 it will merge them into one request
+ /// - If the max distance is 2 and the next register is at DT102 it will also merge them and ignore the spacer byte in the response
+ ///
+ ///
+ + public int MaxOptimizationDistance { get; set; } = 4; + + /// + /// The overwrite mode for poll levels
+ /// When set to the lowest average poll level for overlapping registers gets used
+ /// When set to the highest average poll level for overlapping registers gets used + ///
+ public PollLevelOverwriteMode PollLevelOverwriteMode { get; set; } = PollLevelOverwriteMode.Highest; + + /// + /// Defines how many WORD blocks the interface will send on a DT area write request before splitting up messages
+ /// Higher numbers will result in a longer send and receive thread blocking time + ///
+ public int MaxDataBlocksPerWrite { get; set; } = 8; + + } + +} diff --git a/MewtocolNet/SetupClasses/PollLevelConfig.cs b/MewtocolNet/SetupClasses/PollLevelConfig.cs index c0fb00c..9e44f10 100644 --- a/MewtocolNet/SetupClasses/PollLevelConfig.cs +++ b/MewtocolNet/SetupClasses/PollLevelConfig.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Text; namespace MewtocolNet.SetupClasses { diff --git a/MewtocolNet/TypeConversion/Conversions.cs b/MewtocolNet/TypeConversion/Conversions.cs index 1cc228a..07f1938 100644 --- a/MewtocolNet/TypeConversion/Conversions.cs +++ b/MewtocolNet/TypeConversion/Conversions.cs @@ -1,15 +1,12 @@ -using MewtocolNet.Registers; +using MewtocolNet.Exceptions; +using MewtocolNet.Registers; using System; -using System.Collections; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text; -using MewtocolNet.Helpers; -using MewtocolNet.Exceptions; namespace MewtocolNet.TypeConversion { - + internal static class Conversions { internal static Dictionary dictPlcTypeToRegisterType = new Dictionary { @@ -58,7 +55,7 @@ namespace MewtocolNet.TypeConversion { //default short DT conversion new PlcTypeConversion(RegisterType.DT) { - HoldingRegisterType = typeof(NumberRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.INT, FromRaw = (reg, bytes) => BitConverter.ToInt16(bytes, 0), ToRaw = (reg, value) => BitConverter.GetBytes(value), @@ -66,7 +63,7 @@ namespace MewtocolNet.TypeConversion { //default ushort DT conversion new PlcTypeConversion(RegisterType.DT) { - HoldingRegisterType = typeof(NumberRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.UINT, FromRaw = (reg, bytes) => BitConverter.ToUInt16(bytes, 0), ToRaw = (reg, value) => BitConverter.GetBytes(value), @@ -74,7 +71,7 @@ namespace MewtocolNet.TypeConversion { //default Word DT conversion new PlcTypeConversion(RegisterType.DT) { - HoldingRegisterType = typeof(NumberRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.WORD, FromRaw = (reg, bytes) => new Word(bytes), ToRaw = (reg, value) => value.ToByteArray(), @@ -82,7 +79,7 @@ namespace MewtocolNet.TypeConversion { //default int DDT conversion new PlcTypeConversion(RegisterType.DDT) { - HoldingRegisterType = typeof(NumberRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.DINT, FromRaw = (reg, bytes) => BitConverter.ToInt32(bytes, 0), ToRaw = (reg, value) => BitConverter.GetBytes(value), @@ -90,7 +87,7 @@ namespace MewtocolNet.TypeConversion { //default uint DDT conversion new PlcTypeConversion(RegisterType.DDT) { - HoldingRegisterType = typeof(NumberRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.UDINT, FromRaw = (reg, bytes) => BitConverter.ToUInt32(bytes, 0), ToRaw = (reg, value) => BitConverter.GetBytes(value), @@ -98,7 +95,7 @@ namespace MewtocolNet.TypeConversion { //default DWord DDT conversion new PlcTypeConversion(RegisterType.DDT) { - HoldingRegisterType = typeof(NumberRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.DWORD, FromRaw = (reg, bytes) => new DWord(bytes), ToRaw = (reg, value) => value.ToByteArray(), @@ -106,7 +103,7 @@ namespace MewtocolNet.TypeConversion { //default float DDT conversion new PlcTypeConversion(RegisterType.DDT) { - HoldingRegisterType = typeof(NumberRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.REAL, FromRaw = (reg, bytes) => BitConverter.ToSingle(bytes, 0), ToRaw = (reg, value) => BitConverter.GetBytes(value), @@ -114,7 +111,7 @@ namespace MewtocolNet.TypeConversion { //default TimeSpan DDT conversion new PlcTypeConversion(RegisterType.DDT) { - HoldingRegisterType = typeof(NumberRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.TIME, FromRaw = (reg, bytes) => { @@ -131,19 +128,11 @@ namespace MewtocolNet.TypeConversion { }, }, - - //default byte array DT Range conversion, direct pass through - new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { - HoldingRegisterType = typeof(ArrayRegister), - FromRaw = (reg, bytes) => bytes, - ToRaw = (reg, value) => value, - }, - //default string DT Range conversion Example bytes: (04 00 03 00 XX XX XX) //first 4 bytes are reserved size (2 bytes) and used size (2 bytes) //the remaining bytes are the ascii bytes for the string new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { - HoldingRegisterType = typeof(StringRegister), + HoldingRegisterType = typeof(SingleRegister), PlcVarType = PlcVarType.STRING, FromRaw = (reg, bytes) => { @@ -157,24 +146,25 @@ namespace MewtocolNet.TypeConversion { short actualLen = BitConverter.ToInt16(bytes, 2); //skip 4 bytes because they only describe the length - return Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray()); - + string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray()); + + return gotVal; + }, ToRaw = (reg, value) => { - var sReg = (StringRegister)reg; - - if(value.Length > sReg.ReservedSize) - value = value.Substring(0, sReg.ReservedSize); - - int padLen = sReg.ReservedSize; - if(sReg.ReservedSize % 2 != 0) padLen++; + int padLen = value.Length; + if(value.Length % 2 != 0) padLen++; var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0')); - List finalBytes = new List(); - finalBytes.AddRange(BitConverter.GetBytes(sReg.ReservedSize)); - finalBytes.AddRange(BitConverter.GetBytes((short)value.Length)); + List finalBytes = new List(); + + short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4); + short used = (short)value.Length; + + finalBytes.AddRange(BitConverter.GetBytes(reserved)); + finalBytes.AddRange(BitConverter.GetBytes(used)); finalBytes.AddRange(strBytes); return finalBytes.ToArray(); diff --git a/MewtocolNet/TypeConversion/IPlcTypeConverter.cs b/MewtocolNet/TypeConversion/IPlcTypeConverter.cs index cdbffb5..164a8cf 100644 --- a/MewtocolNet/TypeConversion/IPlcTypeConverter.cs +++ b/MewtocolNet/TypeConversion/IPlcTypeConverter.cs @@ -1,12 +1,13 @@ -using System; +using MewtocolNet.Registers; +using System; namespace MewtocolNet { internal interface IPlcTypeConverter { - object FromRawData(IRegister register, byte[] data); + object FromRawData(Register register, byte[] data); - byte[] ToRawData(IRegister register, object value); + byte[] ToRawData(Register register, object value); Type GetDotnetType(); diff --git a/MewtocolNet/TypeConversion/PlcTypeConversion.cs b/MewtocolNet/TypeConversion/PlcTypeConversion.cs index b65f961..8f76db4 100644 --- a/MewtocolNet/TypeConversion/PlcTypeConversion.cs +++ b/MewtocolNet/TypeConversion/PlcTypeConversion.cs @@ -1,5 +1,5 @@ -using System; -using System.ComponentModel; +using MewtocolNet.Registers; +using System; namespace MewtocolNet { @@ -13,9 +13,9 @@ namespace MewtocolNet { public Type HoldingRegisterType { get; set; } - public Func FromRaw { get; set; } + public Func FromRaw { get; set; } - public Func ToRaw { get; set; } + public Func ToRaw { get; set; } public PlcTypeConversion(RegisterType plcType) { @@ -32,9 +32,9 @@ namespace MewtocolNet { public PlcVarType GetPlcVarType() => PlcVarType; - public object FromRawData(IRegister register, byte[] data) => FromRaw.Invoke(register, data); + public object FromRawData(Register register, byte[] data) => FromRaw.Invoke(register, data); - public byte[] ToRawData(IRegister register, object value) => ToRaw.Invoke(register, (T)value); + public byte[] ToRawData(Register register, object value) => ToRaw.Invoke(register, (T)value); } diff --git a/MewtocolNet/TypeConversion/PlcValueParser.cs b/MewtocolNet/TypeConversion/PlcValueParser.cs index cf8c887..9731abc 100644 --- a/MewtocolNet/TypeConversion/PlcValueParser.cs +++ b/MewtocolNet/TypeConversion/PlcValueParser.cs @@ -3,7 +3,6 @@ using MewtocolNet.Registers; using MewtocolNet.TypeConversion; using System; using System.Collections.Generic; -using System.Data; using System.Linq; namespace MewtocolNet { @@ -12,71 +11,173 @@ namespace MewtocolNet { private static List conversions => Conversions.items; - internal static T Parse (IRegister register, byte[] bytes) { + internal static T Parse(Register register, byte[] bytes) { IPlcTypeConverter converter; + Type underlyingType; //special case for enums - if(typeof(T).IsEnum) { + if (typeof(T).IsEnum) { - var underlyingNumberType = typeof(T).GetEnumUnderlyingType(); - - converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingNumberType); + underlyingType = typeof(T).GetEnumUnderlyingType(); } else { - converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T)); + underlyingType = typeof(T); } + converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingType); + if (converter == null) - throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist"); + throw new MewtocolException($"A converter for the dotnet type {underlyingType} doesn't exist"); return (T)converter.FromRawData(register, bytes); } - internal static byte[] Encode (IRegister register, T value) { + internal static T ParseArray (Register register, int[] indices, byte[] bytes) { IPlcTypeConverter converter; + Type underlyingElementType; //special case for enums if (typeof(T).IsEnum) { - var underlyingNumberType = typeof(T).GetEnumUnderlyingType(); - - converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingNumberType); + underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType(); } else { - converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T)); + underlyingElementType = typeof(T).GetElementType(); } + converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingElementType); + if (converter == null) - throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist"); + throw new MewtocolException($"A converter for the dotnet type {underlyingElementType} doesn't exist"); + + //parse the array from one to n dimensions + var outArray = Array.CreateInstance(underlyingElementType, indices); + + if(outArray.GetType() == typeof(byte[])) { + + Console.WriteLine(); + + } + + int sizePerItem = underlyingElementType.DetermineTypeByteSize(); + 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; + + var currentItem = bytes.Skip(j).Take(sizePerItem).ToArray(); + var value = converter.FromRawData(register, currentItem); + + for (int remainder = i, k = indices.Length - 1; k >= 0; k--) { + + int currentDimension = indices[k]; + indexer[k] = remainder % currentDimension; + remainder = remainder / currentDimension; + + } + + outArray.SetValue(value, indexer); + + } + + return (T)(object)outArray; + + } + + static void ConvertFlatArrayToDim ( + IPlcTypeConverter converter, + Register register, + byte[] source, + Array target, + int sizePerVal, + int[] dims, + int currentIndex, + int currentArrayIndex + ) { + + if (currentIndex == dims.Length - 1) { + + for (int i = 0; i < dims[currentIndex]; i++) { + + byte[] rawDataItem = source.Skip(currentArrayIndex).Take(sizePerVal).ToArray(); + var value = converter.FromRawData(register, rawDataItem); + + target.SetValue(value, i); + currentArrayIndex += sizePerVal; + + } + + } else { + + for (int i = 0; i < dims[currentIndex]; i++) { + + Array innerArray = (Array)target.GetValue(i); + ConvertFlatArrayToDim(converter, register, source, innerArray, sizePerVal, dims, currentIndex + 1, currentArrayIndex); + currentArrayIndex += innerArray.Length * sizePerVal; + + } + + } + + } + + + internal static byte[] Encode(Register register, T value) { + + IPlcTypeConverter converter; + Type underlyingType; + + //special case for enums + if (typeof(T).IsEnum) { + + underlyingType = typeof(T).GetEnumUnderlyingType(); + + } else { + + underlyingType = typeof(T); + + } + + converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingType); + + if (converter == null) + throw new MewtocolException($"A converter for the type {underlyingType} doesn't exist"); return converter.ToRawData(register, value); } - public static List GetAllowDotnetTypes () => conversions.Select(x => x.GetDotnetType()).ToList(); + //internal static byte[] EncodeArray (IRegister register, T value) { - public static List GetAllowRegisterTypes () => conversions.Select(x => x.GetHoldingRegisterType()).ToList(); - public static RegisterType? GetDefaultRegisterType (Type type) => + //} + + public static List GetAllowDotnetTypes() => conversions.Select(x => x.GetDotnetType()).ToList(); + + public static List GetAllowRegisterTypes() => conversions.Select(x => x.GetHoldingRegisterType()).ToList(); + + public static RegisterType? GetDefaultRegisterType(Type type) => conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetPlcRegisterType(); - public static Type GetDefaultRegisterHoldingType (this PlcVarType type) => + public static Type GetDefaultRegisterHoldingType(this PlcVarType type) => conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetHoldingRegisterType(); - public static Type GetDefaultRegisterHoldingType (this Type type) => + public static Type GetDefaultRegisterHoldingType(this Type type) => conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetHoldingRegisterType(); - public static Type GetDefaultDotnetType (this PlcVarType type) => + public static Type GetDefaultDotnetType(this PlcVarType type) => conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetDotnetType(); - public static PlcVarType? GetDefaultPlcVarType (this Type type) => + public static PlcVarType? GetDefaultPlcVarType(this Type type) => conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetPlcVarType(); } diff --git a/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs b/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs index 79ce5f4..c7e4874 100644 --- a/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs +++ b/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs @@ -1,10 +1,7 @@ using MewtocolNet.Exceptions; using MewtocolNet.Registers; -using MewtocolNet.TypeConversion; using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; namespace MewtocolNet { @@ -30,7 +27,7 @@ namespace MewtocolNet { internal static bool IsAllowedPlcCastingType(this Type type) { - if (type.IsEnum) return true; + if (type.IsEnum || type == typeof(string)) return true; return allowedCastingTypes.Contains(type); @@ -52,7 +49,7 @@ namespace MewtocolNet { } - internal static PlcVarType ToPlcVarType (this Type type) { + internal static PlcVarType ToPlcVarType(this Type type) { var found = type.GetDefaultPlcVarType().Value; diff --git a/MewtocolNet/UnderlyingRegisters/DTArea.cs b/MewtocolNet/UnderlyingRegisters/DTArea.cs index b3a24dc..6ea8adb 100644 --- a/MewtocolNet/UnderlyingRegisters/DTArea.cs +++ b/MewtocolNet/UnderlyingRegisters/DTArea.cs @@ -1,9 +1,7 @@ using MewtocolNet.Registers; -using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading; using System.Threading.Tasks; namespace MewtocolNet.UnderlyingRegisters { @@ -25,19 +23,19 @@ namespace MewtocolNet.UnderlyingRegisters { public ulong AddressStart => addressStart; public ulong AddressEnd => addressEnd; - internal DTArea (MewtocolInterface mewIf) { + internal DTArea(MewtocolInterface mewIf) { mewInterface = mewIf; - + } - internal void BoundaryUdpdate (uint? addrFrom = null, uint? addrTo = null) { + internal void BoundaryUdpdate(uint? addrFrom = null, uint? addrTo = null) { var addFrom = addrFrom ?? addressStart; var addTo = addrTo ?? addressEnd; var oldFrom = addressStart; - var oldUnderlying = underlyingBytes.ToArray(); + var oldUnderlying = underlyingBytes.ToArray(); underlyingBytes = new byte[(addTo + 1 - addFrom) * 2]; @@ -64,7 +62,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - internal async Task RequestByteReadAsync (ulong addStart, ulong addEnd) { + internal async Task RequestByteReadAsync(ulong addStart, ulong addEnd) { await CheckDynamicallySizedRegistersAsync(); @@ -73,7 +71,7 @@ namespace MewtocolNet.UnderlyingRegisters { string requeststring = $"%{station}#RD{GetMewtocolIdent(addStart, addEnd)}"; var result = await mewInterface.SendCommandAsync(requeststring); - if(result.Success) { + if (result.Success) { var resBytes = result.Response.ParseDTRawStringAsBytes(); SetUnderlyingBytes(resBytes, addStart); @@ -84,7 +82,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - public byte[] GetUnderlyingBytes(BaseRegister reg) { + public byte[] GetUnderlyingBytes(Register reg) { int byteLen = (int)(reg.GetRegisterAddressLen() * 2); @@ -92,7 +90,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - internal byte[] GetUnderlyingBytes (uint addStart, int addLen) { + internal byte[] GetUnderlyingBytes(uint addStart, int addLen) { int byteLen = (int)(addLen * 2); @@ -103,7 +101,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - public void SetUnderlyingBytes(BaseRegister reg, byte[] bytes) { + public void SetUnderlyingBytes(Register reg, byte[] bytes) { SetUnderlyingBytes(bytes, reg.MemoryAddress); @@ -118,24 +116,23 @@ namespace MewtocolNet.UnderlyingRegisters { } - private async Task CheckDynamicallySizedRegistersAsync () { + internal async Task CheckDynamicallySizedRegistersAsync() { //calibrating at runtime sized registers var uncalibratedStringRegisters = managedRegisters .SelectMany(x => x.Linked) - .Where(x => x is StringRegister sreg && !sreg.isCalibratedFromPlc) - .Cast() + .Where(x => x.dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate)) .ToList(); foreach (var register in uncalibratedStringRegisters) - await register.CalibrateFromPLC(); + await register.UpdateDynamicSize(); if (uncalibratedStringRegisters.Count > 0) mewInterface.memoryManager.LinkAndMergeRegisters(); } - private string GetMewtocolIdent () { + private string GetMewtocolIdent() { StringBuilder asciistring = new StringBuilder("D"); asciistring.Append(AddressStart.ToString().PadLeft(5, '0')); diff --git a/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs b/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs index 5dc8161..e593fa4 100644 --- a/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs +++ b/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs @@ -1,13 +1,12 @@ using MewtocolNet.Registers; -using System.Threading.Tasks; namespace MewtocolNet.UnderlyingRegisters { internal interface IMemoryArea { - byte[] GetUnderlyingBytes(BaseRegister reg); + byte[] GetUnderlyingBytes(Register reg); - void SetUnderlyingBytes(BaseRegister reg, byte[] bytes); + void SetUnderlyingBytes(Register reg, byte[] bytes); void UpdateAreaRegisterValues(); diff --git a/MewtocolNet/UnderlyingRegisters/LinkedRegisterGroup.cs b/MewtocolNet/UnderlyingRegisters/LinkedRegisterGroup.cs index 28110fa..1f3a91a 100644 --- a/MewtocolNet/UnderlyingRegisters/LinkedRegisterGroup.cs +++ b/MewtocolNet/UnderlyingRegisters/LinkedRegisterGroup.cs @@ -1,7 +1,5 @@ using MewtocolNet.Registers; -using System; using System.Collections.Generic; -using System.Text; namespace MewtocolNet.UnderlyingRegisters { @@ -9,9 +7,9 @@ namespace MewtocolNet.UnderlyingRegisters { internal uint AddressStart; - internal uint AddressEnd; + internal uint AddressEnd; - internal List Linked = new List(); + internal List Linked = new List(); } diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index b654888..f9fdbc0 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -1,12 +1,9 @@ -using MewtocolNet.Helpers; -using MewtocolNet.Registers; +using MewtocolNet.Registers; using MewtocolNet.SetupClasses; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Reflection; -using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -16,7 +13,7 @@ namespace MewtocolNet.UnderlyingRegisters { internal int maxOptimizationDistance = 8; internal int maxRegistersPerGroup = -1; - internal bool allowByteRegDupes; + internal PollLevelOverwriteMode pollLevelOrMode = PollLevelOverwriteMode.Highest; private int wrAreaSize; private int dtAreaSize; @@ -27,15 +24,15 @@ namespace MewtocolNet.UnderlyingRegisters { private uint pollIteration = 0; - internal MemoryAreaManager (MewtocolInterface mewIf, int wrSize = 512, int dtSize = 32_765) { + internal MemoryAreaManager(MewtocolInterface mewIf, int wrSize = 512, int dtSize = 32_765) { - mewInterface = mewIf; - Setup(wrSize, dtSize); + mewInterface = mewIf; + Setup(wrSize, dtSize); } // Later on pass memory area sizes here - internal void Setup (int wrSize, int dtSize) { + internal void Setup(int wrSize, int dtSize) { wrAreaSize = wrSize; dtAreaSize = dtSize; @@ -47,31 +44,31 @@ namespace MewtocolNet.UnderlyingRegisters { } - internal void LinkAndMergeRegisters (List registers = null) { + internal async Task OnPlcConnected () { + + //check all area for dynamic sized registers + await CheckAllDynamicallySizedAreas(); + + } + + internal void LinkAndMergeRegisters(List registers = null) { //for self calling - if (registers == null) registers = GetAllRegisters().ToList(); - - //pre combine per address - var groupedByAdd = registers - .GroupBy(x => new { - x.MemoryAddress, - len = x.GetRegisterAddressLen(), - spadd = x.GetSpecialAddress(), - }); - - //poll level merging - foreach (var addressGroup in groupedByAdd) { - - //determine highest poll level for same addresses - var highestPollLevel = addressGroup.Max(x => x.pollLevel); - - //apply poll level to all registers in same group - foreach (var reg in addressGroup) - reg.pollLevel = highestPollLevel; - + if (registers == null) { + + //get a copy of the current ones + registers = GetAllRegisters().ToList(); + //clear old ones + ClearAllRegisters(); + } + //maxes the highest poll level for all registers that contain each other + registers + .OrderByDescending(x => x.GetRegisterAddressLen()) + .ToList() + .ForEach(x => x.AveragePollLevel(registers, pollLevelOrMode)); + //insert into area foreach (var reg in registers) { @@ -93,7 +90,6 @@ namespace MewtocolNet.UnderlyingRegisters { } //order - foreach (var lvl in pollLevels) { foreach (var area in lvl.dataAreas) { @@ -102,36 +98,38 @@ namespace MewtocolNet.UnderlyingRegisters { } + lvl.dataAreas = lvl.dataAreas.OrderBy(x => x.AddressStart).ToList(); + } } - private void TestPollLevelExistence (BaseRegister reg) { + private void TestPollLevelExistence(Register reg) { - if(!pollLevelConfigs.ContainsKey(1)) { + if (!pollLevelConfigs.ContainsKey(1)) { pollLevelConfigs.Add(1, new PollLevelConfig { skipNth = 1, }); } - if(!pollLevels.Any(x => x.level == reg.pollLevel)) { + if (!pollLevels.Any(x => x.level == reg.pollLevel)) { pollLevels.Add(new PollLevel(wrAreaSize, dtAreaSize) { level = reg.pollLevel, }); - + //add config if it was not made at setup - if(!pollLevelConfigs.ContainsKey(reg.pollLevel)) { + if (!pollLevelConfigs.ContainsKey(reg.pollLevel)) { pollLevelConfigs.Add(reg.pollLevel, new PollLevelConfig { skipNth = reg.pollLevel, }); } } - + } - private bool AddToWRArea (BaseRegister insertReg) { + private bool AddToWRArea(Register insertReg) { var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel); @@ -151,12 +149,12 @@ namespace MewtocolNet.UnderlyingRegisters { WRArea area = collection.FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress); - if(area != null) { + if (area != null) { var existingLinkedRegister = area.linkedRegisters .FirstOrDefault(x => x.CompareIsDuplicate(insertReg)); - if(existingLinkedRegister != null) { + if (existingLinkedRegister != null) { return false; @@ -187,7 +185,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - private void AddToDTArea (BaseRegister insertReg) { + private void AddToDTArea(Register insertReg) { uint regInsAddStart = insertReg.MemoryAddress; uint regInsAddEnd = insertReg.MemoryAddress + insertReg.GetRegisterAddressLen() - 1; @@ -211,7 +209,7 @@ namespace MewtocolNet.UnderlyingRegisters { } //found adjacent before - if(dtArea.AddressEnd <= regInsAddStart) { + if (dtArea.AddressEnd <= regInsAddStart) { ulong distance = regInsAddStart - dtArea.AddressEnd; @@ -255,12 +253,12 @@ namespace MewtocolNet.UnderlyingRegisters { targetArea.BoundaryUdpdate(); dataAreas.Add(targetArea); - + } insertReg.underlyingMemory = targetArea; - if (insertReg.name == null) { + if (insertReg.autoGenerated && insertReg.name == null) { insertReg.name = $"auto_{Guid.NewGuid().ToString("N")}"; } @@ -274,7 +272,7 @@ namespace MewtocolNet.UnderlyingRegisters { AddressStart = insertReg.MemoryAddress, AddressEnd = insertReg.GetRegisterAddressEnd(), }; - targetArea.managedRegisters.Add(existinglinkedGroup); + targetArea.managedRegisters.Add(existinglinkedGroup); } //check if the linked group has duplicate type registers @@ -284,18 +282,33 @@ namespace MewtocolNet.UnderlyingRegisters { dupedTypeReg.WithBoundProperties(insertReg.boundProperties); } else { existinglinkedGroup.Linked.Add(insertReg); + existinglinkedGroup.Linked = existinglinkedGroup.Linked.OrderBy(x => x.MemoryAddress).ToList(); } } - internal async Task PollAllAreasAsync () { + 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) { var sw = Stopwatch.StartNew(); //determine to skip poll levels, first iteration is always polled - if(pollIteration > 0 && pollLevel.level > 1) { + if (pollIteration > 0 && pollLevel.level > 1) { var lvlConfig = pollLevelConfigs[pollLevel.level]; var skipIterations = lvlConfig.skipNth; @@ -306,11 +319,11 @@ namespace MewtocolNet.UnderlyingRegisters { //count delayed poll skips continue; - } else if(skipDelay != null) { + } else if (skipDelay != null) { //time delayed poll skips - if(lvlConfig.timeFromLastRead.Elapsed < skipDelay.Value) { + if (lvlConfig.timeFromLastRead.Elapsed < skipDelay.Value) { continue; @@ -321,10 +334,10 @@ namespace MewtocolNet.UnderlyingRegisters { } //set stopwatch for levels - if(pollLevelConfigs.ContainsKey(pollLevel.level)) { + if (pollLevelConfigs.ContainsKey(pollLevel.level)) { + + pollLevelConfigs[pollLevel.level].timeFromLastRead = Stopwatch.StartNew(); - pollLevelConfigs[pollLevel.level].timeFromLastRead = Stopwatch.StartNew(); - } //update registers in poll level @@ -339,7 +352,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - if(pollIteration == uint.MaxValue) { + if (pollIteration == uint.MaxValue) { pollIteration = uint.MinValue; } else { pollIteration++; @@ -347,7 +360,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - internal string ExplainLayout () { + internal string ExplainLayout() { var sb = new StringBuilder(); @@ -356,9 +369,9 @@ namespace MewtocolNet.UnderlyingRegisters { sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ===="); sb.AppendLine(); - if (pollLevelConfigs[pollLevel.level].delay != null) { + if (pollLevelConfigs.ContainsKey(pollLevel.level) && pollLevelConfigs[pollLevel.level].delay != null) { sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms"); - } else { + } else if (pollLevelConfigs.ContainsKey(pollLevel.level)) { sb.AppendLine($"> Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations"); } sb.AppendLine($"> Level read time: {pollLevel.lastReadTimeMs}ms"); @@ -381,7 +394,10 @@ namespace MewtocolNet.UnderlyingRegisters { foreach (var linkedG in area.managedRegisters) { - if (prevGroup != null && (linkedG.AddressStart - prevGroup.AddressEnd - 1 > 0)) { + if (prevGroup != null && + linkedG.AddressStart != prevGroup.AddressStart && + linkedG.AddressEnd > prevGroup.AddressEnd && + linkedG.AddressStart - prevGroup.AddressEnd > 1) { var dist = linkedG.AddressStart - prevGroup.AddressEnd - 1; @@ -409,7 +425,7 @@ namespace MewtocolNet.UnderlyingRegisters { sb.AppendLine($"* {new string('=', seperatorLen + 3)}"); - prevGroup = linkedG; + prevGroup = linkedG; } @@ -421,11 +437,21 @@ namespace MewtocolNet.UnderlyingRegisters { return sb.ToString(); - } + } - internal IEnumerable GetAllRegisters () { + internal void ClearAllRegisters () { - List registers = new List(); + foreach (var lvl in pollLevels) { + + lvl.dataAreas.Clear(); + + } + + } + + internal IEnumerable GetAllRegisters() { + + List registers = new List(); foreach (var lvl in pollLevels) { @@ -441,6 +467,6 @@ namespace MewtocolNet.UnderlyingRegisters { } - } + } } diff --git a/MewtocolNet/UnderlyingRegisters/PollLevel.cs b/MewtocolNet/UnderlyingRegisters/PollLevel.cs index 2d99b69..25fe5c5 100644 --- a/MewtocolNet/UnderlyingRegisters/PollLevel.cs +++ b/MewtocolNet/UnderlyingRegisters/PollLevel.cs @@ -4,9 +4,9 @@ namespace MewtocolNet.UnderlyingRegisters { internal class PollLevel { - internal int lastReadTimeMs = 0; + internal int lastReadTimeMs = 0; - internal PollLevel (int wrSize, int dtSize) { + internal PollLevel(int wrSize, int dtSize) { externalRelayInAreas = new List(wrSize * 16); externalRelayOutAreas = new List(wrSize * 16); diff --git a/MewtocolNet/UnderlyingRegisters/WRArea.cs b/MewtocolNet/UnderlyingRegisters/WRArea.cs index 409fc8d..21eba23 100644 --- a/MewtocolNet/UnderlyingRegisters/WRArea.cs +++ b/MewtocolNet/UnderlyingRegisters/WRArea.cs @@ -1,5 +1,4 @@ using MewtocolNet.Registers; -using System.Collections; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; @@ -15,7 +14,7 @@ namespace MewtocolNet.UnderlyingRegisters { internal byte[] wordData = new byte[2]; - internal List linkedRegisters = new List(); + internal List linkedRegisters = new List(); public ulong AddressStart => addressStart; @@ -25,37 +24,37 @@ namespace MewtocolNet.UnderlyingRegisters { } - public void UpdateAreaRegisterValues () { + public void UpdateAreaRegisterValues() { } - public void SetUnderlyingBytes(BaseRegister reg, byte[] bytes) { + public void SetUnderlyingBytes(Register reg, byte[] bytes) { } - public byte[] GetUnderlyingBytes(BaseRegister reg) { + public byte[] GetUnderlyingBytes(Register reg) { return null; } - public async Task ReadRegisterAsync(BaseRegister reg) { + public async Task ReadRegisterAsync(Register reg) { return true; } - public async Task WriteRegisterAsync(BaseRegister reg, byte[] bytes) { + public async Task WriteRegisterAsync(Register reg, byte[] bytes) { + + return true; - return true; - } public string GetMewtocolIdent() => GetMewtocolIdentsAllBits(); - public string GetMewtocolIdentsAllBits () { + public string GetMewtocolIdentsAllBits() { StringBuilder asciistring = new StringBuilder(); @@ -69,7 +68,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - public string GetMewtocolIdentSingleBit (byte specialAddress) { + public string GetMewtocolIdentSingleBit(byte specialAddress) { //(R|X|Y)(area add [3] + special add [1]) StringBuilder asciistring = new StringBuilder();