Simplify registers

- add array conversion
- fixed dt area merging
This commit is contained in:
Felix Weiß
2023-07-17 00:41:27 +02:00
parent 4666d3071b
commit d6c00097bc
68 changed files with 1479 additions and 1210 deletions

View File

@@ -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 {
/// </summary>
public class CassetteFinder {
public static async Task<IEnumerable<CassetteInformation>> FindClientsAsync (string ipSource = null, int timeoutMs = 100) {
public static async Task<IEnumerable<CassetteInformation>> FindClientsAsync(string ipSource = null, int timeoutMs = 100) {
var from = new IPEndPoint(IPAddress.Any, 0);
@@ -73,7 +71,7 @@ namespace MewtocolNet.ComCassette {
}
private static IEnumerable<NetworkInterface> GetUseableNetInterfaces () {
private static IEnumerable<NetworkInterface> GetUseableNetInterfaces() {
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) {
@@ -103,7 +101,7 @@ namespace MewtocolNet.ComCassette {
}
private static async Task<List<CassetteInformation>> FindClientsForEndpoint (IPEndPoint from, int timeoutMs, string ipEndpointName) {
private static async Task<List<CassetteInformation>> FindClientsForEndpoint(IPEndPoint from, int timeoutMs, string ipEndpointName) {
var cassettesFound = new List<CassetteInformation>();
@@ -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;

View File

@@ -116,7 +116,7 @@ namespace MewtocolNet.ComCassette {
}
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);

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.ComCassette {
namespace MewtocolNet.ComCassette {
/// <summary>
/// Needs a list of all status codes.. hard to reverse engineer

View File

@@ -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 {
/// <summary>
/// A DWord is a 16 bit value of 2 bytes
/// </summary>
public struct DWord : MewtocolExtensionTypeDDT {
public struct DWord : MewtocolExtTypeInit2Word {
private int bitLength;
@@ -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;
}
}

View File

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

View File

@@ -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 {
/// <summary>
/// A word is a 16 bit value of 2 bytes
/// </summary>
public struct Word : MewtocolExtensionTypeDT {
public struct Word : MewtocolExtTypeInit1Word {
private int bitLength;
@@ -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')

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.Documentation {

View File

@@ -1,13 +1,11 @@
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() { }
}

View File

@@ -1,13 +1,11 @@
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() { }
}

View File

@@ -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()}");

View File

@@ -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;

View File

@@ -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,7 +23,7 @@ namespace MewtocolNet {
}
}
public async static Task<byte[]> ReadAsync (this SerialPort serialPort, int count) {
public async static Task<byte[]> ReadAsync(this SerialPort serialPort, int count) {
var buffer = new byte[count];
await serialPort.ReadAsync(buffer, 0, count);
return buffer;

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
namespace MewtocolNet.Helpers {

View File

@@ -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,7 +58,7 @@ namespace MewtocolNet {
}
private static ushort CRC16_MCRF4XX (byte[] bytes) {
private static ushort CRC16_MCRF4XX(byte[] bytes) {
int icrc = 0xFFFF;
@@ -72,7 +72,7 @@ namespace MewtocolNet {
}
private static byte CRC8 (byte[] bytes) {
private static byte CRC8(byte[] bytes) {
byte xorTotalByte = 0;

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.Helpers {

View File

@@ -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");
}
/// <summary>
/// Searches a byte array for a pattern
/// </summary>
@@ -53,23 +70,6 @@ namespace MewtocolNet {
}
/// <summary>
/// Parses the byte string from a incoming RD message
/// </summary>
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;
}
/// <summary>
/// Parses a return message as RCS single bit
/// </summary>
@@ -119,7 +119,7 @@ namespace MewtocolNet {
/// </summary>
/// <param name="_onString"></param>
/// <returns>A <see cref="T:byte[]"/> or null of failed</returns>
internal static byte[] ParseDTRawStringAsBytes (this string _onString) {
internal static byte[] ParseDTRawStringAsBytes(this string _onString) {
_onString = _onString.Replace("\r", "");
@@ -160,7 +160,7 @@ namespace MewtocolNet {
/// <summary>
/// Converts a hex string (AB01C1) to a byte array
/// </summary>
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 {
/// </summary>
/// <param name="seperator">Seperator between the hex numbers</param>
/// <param name="arr">The byte array</param>
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 {
/// <summary>
/// Checks if the register type is boolean
/// </summary>
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 {
/// <summary>
/// Checks if the register type numeric
/// </summary>
internal static bool IsNumericDTDDT (this RegisterType type) {
internal static bool IsNumericDTDDT(this RegisterType type) {
return type == RegisterType.DT || type == RegisterType.DDT;
@@ -243,7 +243,7 @@ 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 &&
@@ -254,7 +254,7 @@ namespace MewtocolNet {
}
internal static bool CompareIsDuplicateNonCast (this BaseRegister toInsert, BaseRegister compare, List<Type> allowOverlappingTypes) {
internal static bool CompareIsDuplicateNonCast(this Register toInsert, Register compare, List<Type> 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 {
/// <summary>
/// Converts the enum to a plc name string
/// </summary>
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 {
/// <summary>
/// Converts the enum to a decomposed <see cref="ParsedPlcName"/> struct
/// </summary>
public static ParsedPlcName[] ToNameDecompose (this PlcType plcT) {
public static ParsedPlcName[] ToNameDecompose(this PlcType plcT) {
if ((int)plcT == 0) return Array.Empty<ParsedPlcName>();
@@ -306,7 +306,7 @@ namespace MewtocolNet {
/// <summary>
/// Checks if the PLC type is discontinued
/// </summary>
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
/// <summary>
/// Maps the source object to target object.
/// </summary>
/// <typeparam name="T">Type of target object.</typeparam>
/// <typeparam name="TU">Type of source object.</typeparam>
/// <param name="target">Target object.</param>
/// <param name="source">Source object.</param>
/// <returns>Updated target object.</returns>
internal static T Map<T, TU>(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

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace MewtocolNet {
@@ -35,7 +34,7 @@ namespace MewtocolNet {
/// <inheritdoc/>
public override string ToString() => WholeName;
internal static ParsedPlcName[] PlcDeconstruct (PlcType plcT) {
internal static ParsedPlcName[] PlcDeconstruct(PlcType plcT) {
string wholeStr = plcT.ToString();

View File

@@ -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 {
/// </summary>
/// <param name="timespan"></param>
/// <returns></returns>
public static string ToPlcTime (this TimeSpan timespan) {
public static string ToPlcTime(this TimeSpan timespan) {
if (timespan == null || timespan == TimeSpan.Zero)
return $"T#0s";
@@ -41,12 +39,12 @@ namespace MewtocolNet {
}
public static TimeSpan ParsePlcTime (string plcTimeFormat) {
public static TimeSpan ParsePlcTime(string plcTimeFormat) {
var reg = new Regex(@"(?:T|t)#(?:(?<d>[0-9]{1,2})d)?(?:(?<h>[0-9]{1,2})h)?(?:(?<m>[0-9]{1,2})m)?(?:(?<s>[0-9]{1,2})s)?(?:(?<ms>[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;

View File

@@ -60,7 +60,7 @@ namespace MewtocolNet {
/// <summary>
/// Provides an anonymous interface for register reading and writing without memory management
/// </summary>
RBuild Register { get; }
RBuildAnon Register { get; }
/// <summary>
/// Tries to establish a connection with the device asynchronously

View File

@@ -1,6 +1,4 @@
using MewtocolNet.RegisterAttributes;
using System;
using System.Net;
using System.Net;
using System.Threading.Tasks;
namespace MewtocolNet {

View File

@@ -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 {

View File

@@ -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,
}
}

View File

@@ -3,11 +3,9 @@ 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 {
@@ -25,7 +23,7 @@ namespace MewtocolNet {
/// <param name="port"></param>
/// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns>
public static PostInit<IPlcEthernet> Ethernet (string ip, int port = 9094, int station = 0xEE) {
public static PostInit<IPlcEthernet> Ethernet(string ip, int port = 9094, int station = 0xEE) {
var instance = new MewtocolInterfaceTcp();
instance.ConfigureConnection(ip, port, station);
@@ -62,7 +60,7 @@ namespace MewtocolNet {
/// <param name="stopBits">Stop bits of the plc toolport</param>
/// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns>
public static PostInit<IPlcSerial> 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<IPlcSerial> 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 {
/// <param name="portName"></param>
/// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns>
public static PostInit<IPlcSerial> SerialAuto (string portName, int station = 0xEE) {
public static PostInit<IPlcSerial> 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 {
/// <summary>
/// <code>
/// This feature can improve read write times by a big margin but also
/// block outgoing messages inbetween polling cycles more frequently
/// </code>
/// The max distance of the gap between registers (if there is a gap between
/// adjacent registers) to merge them into one request <br/>
/// Example: <br/>
/// <example>
/// We have a register at DT100 (1 word long) and a
/// register at DT101 (1 word long) <br/>
/// - If the max distance is 0 it will not merge them into one request<br/>
/// - If the max distance is 1 it will merge them into one request<br/>
/// - 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<br/>
/// </example>
/// </summary>
public int MaxOptimizationDistance { get; set; } = 4;
/// <summary>
/// The max number of registers per request group
/// </summary>
public int MaxRegistersPerGroup { get; set; } = -1;
/// <summary>
/// Wether or not to throw an exception when a byte array overlap or duplicate is detected
/// </summary>
public bool AllowByteRegisterDupes { get; set; } = false;
}
public class PollLevelConfigurator {
internal Dictionary<int, PollLevelConfig> levelConfigs = new Dictionary<int, PollLevelConfig>();
@@ -148,9 +113,9 @@ namespace MewtocolNet {
/// </summary>
/// <param name="level">The level to reference</param>
/// <param name="interval">Delay between poll requests</param>
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 {
/// <summary>
/// General setting for the memory manager
/// </summary>
public PostInit<T> WithMemoryManagerSettings (Action<MemoryManagerSettings> settings) {
public PostInit<T> WithInterfaceSettings(Action<InterfaceSettings> 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 {
/// <summary>
/// A builder for poll custom levels
/// </summary>
public PostInit<T> WithCustomPollLevels (Action<PollLevelConfigurator> levels) {
public PostInit<T> WithCustomPollLevels(Action<PollLevelConfigurator> levels) {
var res = new PollLevelConfigurator();
levels.Invoke(res);
@@ -274,6 +244,8 @@ namespace MewtocolNet {
/// </summary>
public EndInit<T> WithRegisterCollections(Action<RegCollector> collector) {
try {
var res = new RegCollector();
collector.Invoke(res);
@@ -285,6 +257,12 @@ namespace MewtocolNet {
postInit = this
};
} catch {
throw;
}
}
/// <summary>

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using MewtocolNet.Helpers;
using MewtocolNet.Helpers;
namespace MewtocolNet {
@@ -19,7 +16,7 @@ namespace MewtocolNet {
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 {
}
/// <inheritdoc/>
public static bool operator == (MewtocolFrameResponse c1, MewtocolFrameResponse c2) {
public static bool operator ==(MewtocolFrameResponse c1, MewtocolFrameResponse c2) {
return c1.Equals(c2);
}
/// <inheritdoc/>
public static bool operator != (MewtocolFrameResponse c1, MewtocolFrameResponse c2) {
public static bool operator !=(MewtocolFrameResponse c1, MewtocolFrameResponse c2) {
return !c1.Equals(c2);
}

View File

@@ -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);
}
/// <inheritdoc/>
public virtual Task ConnectAsync() => throw new NotImplementedException();
public virtual async Task ConnectAsync() {
await memoryManager.OnPlcConnected();
}
/// <inheritdoc/>
public async Task AwaitFirstDataCycleAsync() => await firstPollTask;
/// <inheritdoc/>
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 {
}
/// <inheritdoc/>
public virtual string GetConnectionInfo () => throw new NotImplementedException();
public virtual string GetConnectionInfo() => throw new NotImplementedException();
/// <inheritdoc/>
public async Task<MewtocolFrameResponse> SendCommandAsync (string _msg, bool withTerminator = true, int timeoutMs = -1, Action<double> onReceiveProgress = null) {
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1, Action<double> onReceiveProgress = null) {
//send request
queuedMessages++;
@@ -233,7 +237,7 @@ namespace MewtocolNet {
}
private protected async Task<MewtocolFrameResponse> SendFrameAsync (string frame, bool useBcc = true, bool useCr = true, Action<double> onReceiveProgress = null) {
private protected async Task<MewtocolFrameResponse> SendFrameAsync(string frame, bool useBcc = true, bool useCr = true, Action<double> onReceiveProgress = null) {
try {
@@ -254,7 +258,7 @@ 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(?<from>[0-9]{5})(?<to>[0-9]{5})");
@@ -301,7 +305,13 @@ namespace MewtocolNet {
for (int j = 0; j < split.Length; j++) {
split[j] = split[j].Replace("\r", "");
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<double> onReceiveProgress = null) {
private protected async Task<(byte[], bool)> ReadCommandAsync(int? wordsCountRequested = null, Action<double> onReceiveProgress = null) {
//read total
List<byte> totalResponse = new List<byte>();
@@ -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;

View File

@@ -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<RegisterCollection> registerCollections = new List<RegisterCollection>();
internal IEnumerable<BaseRegister> RegistersInternal => GetAllRegistersInternal();
internal IEnumerable<Register> RegistersInternal => GetAllRegistersInternal();
public IEnumerable<IRegister> Registers => GetAllRegisters();
@@ -46,7 +38,7 @@ namespace MewtocolNet {
}
/// <inheritdoc/>
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 {
/// <summary>
/// Writes back the values changes of the underlying registers to the corrosponding property
/// </summary>
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<BaseRegister> registers) {
internal void InsertRegistersToMemoryStack(List<Register> 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
/// <inheritdoc/>>
public IRegister GetRegister (string name) {
public IRegister GetRegister(string name) {
return RegistersInternal.FirstOrDefault(x => x.Name == name);
}
/// <inheritdoc/>
public IEnumerable<IRegister> GetAllRegisters () {
public IEnumerable<IRegister> GetAllRegisters() {
return memoryManager.GetAllRegisters().Cast<IRegister>();
}
internal IEnumerable<BaseRegister> GetAllRegistersInternal () {
internal IEnumerable<Register> 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);

View File

@@ -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
/// <summary>
@@ -65,7 +62,7 @@ namespace MewtocolNet {
#region Operation mode changing
/// <inheritdoc/>
public async Task<bool> SetOperationModeAsync (bool setRun) {
public async Task<bool> 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
/// <summary>
/// Writes a byte array to a span over multiple registers at once,
@@ -94,19 +91,15 @@ namespace MewtocolNet {
/// /// <param name="start">start address of the array</param>
/// <param name="byteArr"></param>
/// <returns></returns>
public async Task<bool> WriteByteRange (int start, byte[] byteArr, bool flipBytes = false) {
public async Task<bool> 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
/// </summary>
/// <param name="start">Start adress</param>
/// <param name="count">Number of bytes to get</param>
/// <param name="flipBytes">Flips bytes from big to mixed endian</param>
/// <param name="onProgress">Gets invoked when the progress changes, contains the progress as a double</param>
/// <returns>A byte array or null of there was an error</returns>
public async Task<byte[]> ReadByteRangeNonBlocking (int start, int count, bool flipBytes = false, Action<double> onProgress = null) {
/// <param name="byteCount">Number of bytes to get</param>
/// <param name="onProgress">Gets invoked when the progress changes, contains the progress as a double from 0 - 1.0</param>
/// <returns>A byte array of the requested DT area</returns>
public async Task<byte[]> ReadByteRangeNonBlocking(int start, int byteCount, Action<double> onProgress = null) {
var byteList = new List<byte>();
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<byte> readBytes = new List<byte>();
async Task ReadBlock (int wordStart, int wordEnd, Action<double> 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();
var bytes = result.Response.ParseDTRawStringAsBytes();
readBytes.AddRange(bytes);
if (bytes == null) return null;
}
}
//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) => {});
if (flipBytes) {
byteList.AddRange(bytes.BigToMixedEndian().Take(count).ToArray());
} else {
byteList.AddRange(bytes.Take(count).ToArray());
}
}
if (onProgress != null)
onProgress((double)i / wordLength);
onProgress((double)1);
}
return byteList.ToArray();
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');

View File

@@ -1,16 +1,11 @@
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 {
@@ -40,10 +35,10 @@ namespace MewtocolNet {
//Serial
internal SerialPort serialClient;
internal MewtocolInterfaceSerial () : base() { }
internal MewtocolInterfaceSerial() : base() { }
/// <inheritdoc/>
public IPlcSerial WithPoller () {
public IPlcSerial WithPoller() {
usePoller = true;
return this;
@@ -81,7 +76,7 @@ namespace MewtocolNet {
}
/// <inheritdoc/>
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;
@@ -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);
/// <inheritdoc/>
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<PLCInfo?> TryConnectAsyncMulti () {
private async Task<PLCInfo?> TryConnectAsyncMulti() {
var baudRates = Enum.GetValues(typeof(BaudRate)).Cast<BaudRate>();
@@ -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<PLCInfo?> TryConnectAsyncSingle (string port, int baud, int dbits, Parity par, StopBits sbits) {
private async Task<PLCInfo?> TryConnectAsyncSingle(string port, int baud, int dbits, Parity par, StopBits sbits) {
try {
@@ -251,9 +247,9 @@ namespace MewtocolNet {
}
private void CloseClient () {
private void CloseClient() {
if(serialClient.IsOpen) {
if (serialClient.IsOpen) {
serialClient.Close();
Logger.Log($"Closed [SERIAL]", LogLevel.Verbose, this);
@@ -274,7 +270,7 @@ namespace MewtocolNet {
}
private void OnSerialPropsChanged () {
private void OnSerialPropsChanged() {
OnPropChange(nameof(PortName));
OnPropChange(nameof(SerialBaudRate));

View File

@@ -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 {
/// <inheritdoc/>
public IPEndPoint HostEndpoint { get; set; }
internal MewtocolInterfaceTcp () : base() { }
internal MewtocolInterfaceTcp() : base() { }
#region TCP connection state handling
/// <inheritdoc/>
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 {
}
/// <inheritdoc/>
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 {

View File

@@ -43,11 +43,11 @@ namespace MewtocolNet {
/// </summary>
public bool IsRunMode => OperationMode.HasFlag(OPMode.RunMode);
internal bool TryExtendFromEXRT (string msg) {
internal bool TryExtendFromEXRT(string msg) {
var regexEXRT = new Regex(@"\%EE\$EX00RT00(?<icnt>..)(?<mc>..)..(?<cap>..)(?<op>..)..(?<flg>..)(?<sdiag>....)(?<ver>..)(?<hwif>..)(?<nprog>.)(?<progsz>....)(?<hdsz>....)(?<sysregsz>....).*", RegexOptions.IgnoreCase);
var match = regexEXRT.Match(msg);
if(match.Success) {
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(?<cputype>..)(?<cpuver>..)(?<cap>..)(?<op>..)..(?<flg>..)(?<sdiag>....).*", RegexOptions.IgnoreCase);
var match = regexRT.Match(msg);
@@ -103,12 +103,12 @@ namespace MewtocolNet {
};
/// <inheritdoc/>
public static bool operator == (PLCInfo c1, PLCInfo c2) {
public static bool operator ==(PLCInfo c1, PLCInfo c2) {
return c1.Equals(c2);
}
/// <inheritdoc/>
public static bool operator != (PLCInfo c1, PLCInfo c2) {
public static bool operator !=(PLCInfo c1, PLCInfo c2) {
return !c1.Equals(c2);
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet {
namespace MewtocolNet {
public enum BaudRate {

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet {

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet {
namespace MewtocolNet {
public enum MewtocolVersion {

View File

@@ -1,6 +1,4 @@
using System.Text;
namespace MewtocolNet {
namespace MewtocolNet {
public enum PlcVarType {

View File

@@ -0,0 +1,16 @@
namespace MewtocolNet {
public enum PollLevelOverwriteMode {
/// <summary>
/// The lowest average poll level for overlapping registers gets used
/// </summary>
Lowest,
/// <summary>
/// The highest average poll level for overlapping registers gets used
/// </summary>
Highest,
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.PublicEnums {
namespace MewtocolNet.PublicEnums {
public enum RegisterBuildSource {
Anonymous,
Manual,

View File

@@ -1,6 +1,4 @@
using System;
namespace MewtocolNet {
namespace MewtocolNet {
/// <summary>
/// The register prefixed type

View File

@@ -1,5 +1,4 @@
using MewtocolNet.RegisterBuilding;
using System;
using System;
namespace MewtocolNet.RegisterAttributes {

View File

@@ -1,4 +1,5 @@
using System.ComponentModel;
using MewtocolNet.Registers;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MewtocolNet.RegisterAttributes {

View File

@@ -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();

View File

@@ -0,0 +1,19 @@
namespace MewtocolNet.RegisterBuilding {
internal enum ParseResultState {
/// <summary>
/// The parse try failed at the intial regex match
/// </summary>
FailedSoft,
/// <summary>
/// The parse try failed at the afer- regex match
/// </summary>
FailedHard,
/// <summary>
/// The parse try did work
/// </summary>
Success,
}
}

View File

@@ -0,0 +1,99 @@
using MewtocolNet.Registers;
using System.Threading.Tasks;
namespace MewtocolNet.RegisterBuilding {
/// <summary>
/// Anonymous register builder
/// </summary>
public class RBuildAnon : RBuildBase {
public RBuildAnon(MewtocolInterface plc) : base(plc) { }
/// <inheritdoc cref="RBuildMult.Address(string, string)"/>
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;
/// <summary>
/// Writes data to the register and bypasses the memory manager <br/>
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>True if success</returns>
public async Task<bool> WriteToAsync<T>(T value) {
try {
var tempRegister = AssembleTemporaryRegister<T>();
return await tempRegister.WriteAsync(value);
} catch {
throw;
}
}
/// <summary>
/// Reads data from the register and bypasses the memory manager <br/>
/// </summary>
/// <returns>The value read or null if failed</returns>
public async Task<T> ReadFromAsync<T>() {
try {
var tempRegister = AssembleTemporaryRegister<T>();
return (T)await tempRegister.ReadAsync();
} catch {
throw;
}
}
private Register AssembleTemporaryRegister<T>() {
var temp = new RBuildMult(attachedPlc).Address(addrString).AsType<T>();
var assembler = new RegisterAssembler(attachedPlc);
return assembler.Assemble(temp.Data);
}
}
}
public class RBuildSingle : RBuildBase {
public RBuildSingle(MewtocolInterface plc) : base(plc) { }
/// <inheritdoc cref="RBuildMult.Address(string, string)"/>
public SAddress Address(string plcAddrName, string name = null) {
var data = ParseAddress(plcAddrName, name);
return new SAddress {
Data = data,
builder = this,
};
}
}
}

View File

@@ -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 {
/// <summary>
/// The parse try failed at the intial regex match
/// </summary>
FailedSoft,
/// <summary>
/// The parse try failed at the afer- regex match
/// </summary>
FailedHard,
/// <summary>
/// The parse try did work
/// </summary>
Success,
protected internal MewtocolInterface attachedPLC;
}
public RBuildBase() { }
/// <summary>
/// Contains useful tools for register creation
/// </summary>
public class RBuild {
internal RBuildBase(MewtocolInterface plc) => attachedPLC = plc;
private MewtocolInterface attachedPLC;
internal List<StepData> unfinishedList = new List<StepData>();
public RBuild () { }
internal RBuild (MewtocolInterface plc) {
attachedPLC = plc;
}
public static RBuild Factory => new RBuild();
internal List<SData> unfinishedList = new List<SData>();
#region String parse stage
#region Parser stage
//methods to test the input string on
private static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
protected static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
(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
}
};
}
/// <summary>
/// Starts the register builder for a new mewtocol address <br/>
/// Examples:
/// <code>Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName")</code>
/// </summary>
/// <param name="plcAddrName">Address name formatted as FP-Address like in FP-Winpro</param>
/// <param name="name">Custom name for the register to referr to it later</param>
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 {
/// <typeparam name="T">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </typeparam>
public TempRegister<T> AsType<T> () {
public TempRegister<T> AsType<T>(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<T>(Data, builder);
@@ -405,7 +327,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <param name="type">
/// <include file="../Documentation/docs.xml" path='extradoc/class[@name="support-conv-types"]/*' />
/// </param>
public TempRegister AsType (Type type) {
public 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");
@@ -484,7 +396,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <summary>
/// Sets the register type as a predefined <see cref="PlcVarType"/>
/// </summary>
public TempRegister AsType (PlcVarType type) {
public TempRegister AsType(PlcVarType type) {
Data.dotnetVarType = type.GetDefaultDotnetType();
@@ -509,7 +421,7 @@ namespace MewtocolNet.RegisterBuilding {
/// <item><term>DWORD</term><description>32 bit double word interpreted as <see cref="uint"/></description></item>
/// </list>
/// </summary>
public TempRegister AsType (string type) {
public TempRegister AsType(string type) {
var stringMatch = Regex.Match(type, @"STRING *\[(?<len>[0-9]*)\]", RegexOptions.IgnoreCase);
var arrayMatch = Regex.Match(type, @"ARRAY *\[(?<S1>[0-9]*)..(?<E1>[0-9]*)(?:\,(?<S2>[0-9]*)..(?<E2>[0-9]*))?(?:\,(?<S3>[0-9]*)..(?<E3>[0-9]*))?\] *OF {1,}(?<t>.*)", RegexOptions.IgnoreCase);
@@ -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) {
@@ -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");
@@ -590,7 +507,7 @@ namespace MewtocolNet.RegisterBuilding {
/// ARRAY [0..2, 0..3, 0..4] OF INT = <c>AsTypeArray&lt;short[,,]&gt;(3,4,5)</c><br/>
/// ARRAY [5..6, 0..2] OF DWORD = <c>AsTypeArray&lt;DWord[,]&gt;(2, 3)</c><br/>
/// </example>
public TempRegister AsTypeArray<T> (params int[] indicies) {
public TempRegister AsTypeArray<T>(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;
/// <summary>
/// Automatically finds the best type for the register
/// </summary>
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<T> : SBase {
internal TempRegister(SData data, RBuild bldr) : base(data, bldr) {}
internal TempRegister() { }
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
/// <summary>
/// Sets the poll level of the register
/// </summary>
public TempRegister<T> PollLevel (int level) {
public TempRegister<T> PollLevel(int level) {
Data.pollLevel = level;
return this;
}
/// <summary>
/// Writes data to the register and bypasses the memory manager <br/>
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>True if success</returns>
public async Task<bool> WriteToAsync (T value) => await builder.WriteAnonymousAsync(this, value);
/// <summary>
/// Reads data from the register and bypasses the memory manager <br/>
/// </summary>
/// <returns>The value read or null if failed</returns>
public async Task<T> 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) { }
/// <summary>
/// Sets the poll level of the register
/// </summary>
public TempRegister PollLevel (int level) {
public TempRegister PollLevel(int level) {
Data.pollLevel = level;
return this;
}
/// <summary>
/// Writes data to the register and bypasses the memory manager <br/>
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>True if success</returns>
public async Task<bool> WriteToAsync(object value) => await builder.WriteAnonymousAsync(this, value);
/// <summary>
/// Reads data from the register and bypasses the memory manager <br/>
/// </summary>
/// <returns>The value read or null if failed</returns>
public async Task<object> 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<bool> WriteAnonymousAsync (TempRegister reg, object value) {
var assembler = new RegisterAssembler(attachedPLC);
var tempRegister = assembler.Assemble(reg.Data);
return await tempRegister.WriteAsync(value);
}
private async Task<bool> WriteAnonymousAsync<T>(TempRegister<T> reg, object value) {
var assembler = new RegisterAssembler(attachedPLC);
var tempRegister = assembler.Assemble(reg.Data);
return await tempRegister.WriteAsync(value);
}
private async Task<object> ReadAnonymousAsync (TempRegister reg) {
var assembler = new RegisterAssembler(attachedPLC);
var tempRegister = assembler.Assemble(reg.Data);
return await tempRegister.ReadAsync();
}
private async Task<T> ReadAnonymousAsync<T>(TempRegister<T> reg) {
var assembler = new RegisterAssembler(attachedPLC);
var tempRegister = assembler.Assemble(reg.Data);
return (T)await tempRegister.ReadAsync();
}
#endregion

View File

@@ -0,0 +1,129 @@
using MewtocolNet.PublicEnums;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System;
using System.Reflection;
namespace MewtocolNet.RegisterBuilding {
/// <summary>
/// Contains useful tools for bunch register creation
/// </summary>
public class RBuildMult : RBuildBase {
public RBuildMult(MewtocolInterface plc) : base(plc) { }
#region String parse stage
/// <summary>
/// Starts the register builder for a new mewtocol address <br/>
/// Examples:
/// <code>Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName")</code>
/// </summary>
/// <param name="plcAddrName">Address name formatted as FP-Address like in FP-Winpro</param>
/// <param name="name">Custom name for the register to referr to it later</param>
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<T> AsType<T>(int? sizeHint = null) => new TempRegister<T>().Map(base.AsType<T>(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<T>(params int[] indicies) => new TempRegister().Map(base.AsTypeArray<T>(indicies));
}
#endregion
#region Options stage
public new class TempRegister<T> : RBuildBase.TempRegister<T> {
internal TempRegister() { }
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
///<inheritdoc cref="RBuildBase.TempRegister.PollLevel(int)"/>
public new TempRegister<T> PollLevel(int level) => new TempRegister<T>().Map(base.PollLevel(level));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public TempRegister<T> Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut;
return this;
}
}
public new class TempRegister : RBuildBase.TempRegister {
internal TempRegister() { }
internal TempRegister(StepData data, RBuildBase bldr) : base(data, bldr) { }
///<inheritdoc cref="RBuildBase.TempRegister.PollLevel(int)"/>
public new TempRegister PollLevel(int level) => new TempRegister().Map(base.PollLevel(level));
/// <summary>
/// Outputs the generated <see cref="IRegister"/>
/// </summary>
public TempRegister Out(Action<IRegister> registerOut) {
Data.registerOut = registerOut;
return this;
}
//internal use only
internal TempRegister RegCollection(RegisterCollection col) {
Data.regCollection = col;
return this;
}
internal TempRegister BoundProp(PropertyInfo prop) {
Data.boundProperty = prop;
return this;
}
}
#endregion
}
}

View File

@@ -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<RBuild> builder) {
if (plc.IsConnected)
throw new Exception("Can't add registers if the PLC is connected");
var regBuilder = new RBuild();
builder.Invoke(regBuilder);
/// <summary>
/// Adds a single register to the plc stack and returns the generated <see cref="IRegister"/><br/>
/// This waits for the memory manager to size all dynamic registers correctly
/// </summary>
/// <returns>The generated <see cref="IRegister"/></returns>
public static IRegister AddRegister(this IPlc plc, Action<RBuildSingle> builder) {
var assembler = new RegisterAssembler((MewtocolInterface)plc);
var regBuilder = new RBuildSingle((MewtocolInterface)plc);
builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder);
var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray());
Task.Run(interf.memoryManager.CheckAllDynamicallySizedAreas);
return registers.First();
}
/// <summary>
/// Adds a single register to the plc stack and returns the generated <see cref="IRegister"/>
/// Waits
/// </summary>
/// <returns>The generated <see cref="IRegister"/></returns>
public static async Task<IRegister> AddRegisterAsync (this IPlc plc, Action<RBuildSingle> builder) {
var assembler = new RegisterAssembler((MewtocolInterface)plc);
var regBuilder = new RBuildSingle((MewtocolInterface)plc);
builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder);
var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray());
await interf.memoryManager.CheckAllDynamicallySizedAreas();
return registers.First();
}
/// <summary>
/// Adds multiple registers to the plc stack at once <br/>
/// Using this over adding each register individually will result in better generation time performance
/// of the <see cref="UnderlyingRegisters.MemoryAreaManager"/> <br/><br/>
/// <b>WARNING!</b> This will not wait for the memory manager to account for dynamically sized registers
/// like ones with the <see cref="string"/> type.. <br/>
/// use <see cref="AddRegistersAsync"/>
/// for this case
/// </summary>
public static IPlc AddRegisters (this IPlc plc, Action<RBuildMult> builder) {
var assembler = new RegisterAssembler((MewtocolInterface)plc);
var regBuilder = new RBuildMult((MewtocolInterface)plc);
builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder);
var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray());
Task.Run(interf.memoryManager.CheckAllDynamicallySizedAreas);
return plc;
}
/// <summary>
/// Adds multiple registers to the plc stack at once <br/>
/// Using this over adding each register individually will result in better generation time performance
/// of the <see cref="UnderlyingRegisters.MemoryAreaManager"/><br/><br/>
/// This waits for the memory manager to size all dynamic registers correctly
/// </summary>
public static async Task<IPlc> AddRegistersAsync (this IPlc plc, Action<RBuildMult> builder) {
var assembler = new RegisterAssembler((MewtocolInterface)plc);
var regBuilder = new RBuildMult((MewtocolInterface)plc);
builder.Invoke(regBuilder);
var registers = assembler.AssembleAll(regBuilder);
var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray());
await interf.memoryManager.CheckAllDynamicallySizedAreas();
return plc;
}
}

View File

@@ -3,32 +3,34 @@ 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<BaseRegister> AssembleAll (RBuild rBuildData, bool flagAutoGenerated = false) {
internal List<Register> AssembleAll(RBuildBase rBuildData, bool flagAutoGenerated = false) {
List<BaseRegister> generatedInstances = new List<BaseRegister>();
List<Register> generatedInstances = new List<Register>();
foreach (var data in rBuildData.unfinishedList) {
var generatedInstance = Assemble(data);
generatedInstance.autoGenerated = flagAutoGenerated;
data.registerOut?.Invoke(generatedInstance);
generatedInstances.Add(generatedInstance);
}
@@ -37,87 +39,114 @@ namespace MewtocolNet.RegisterBuilding {
}
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();
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);
var parameters = new object[] { data.memAddress, data.name };
var instance = (BaseRegister)constr.Invoke(parameters);
instance.RegisterType = numericSize > 2 ? RegisterType.DDT : RegisterType.DT;
generatedInstance = instance;
} else if (registerClassType.IsGenericType) {
//-------------------------------------------
//as numeric register
//create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
//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);
int numericSize = 0;
bool isExtensionTypeDT = typeof(MewtocolExtensionTypeDT).IsAssignableFrom(data.dotnetVarType);
bool isExtensionTypeDDT = typeof(MewtocolExtensionTypeDDT).IsAssignableFrom(data.dotnetVarType);
if (data.dotnetVarType.Namespace == "System") {
numericSize = Marshal.SizeOf(data.dotnetVarType);
} else if(isExtensionTypeDT) {
numericSize = 2;
} else if(isExtensionTypeDDT) {
numericSize = 4;
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($"The type {data.dotnetVarType} is not supported for NumberRegisters");
throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported");
}
}
instance.RegisterType = numericSize > 2 ? RegisterType.DDT : RegisterType.DT;
var sizeStateFlags = DynamicSizeState.None;
generatedInstance = instance;
//string with size hint
if (elementType == typeof(string) && data.perElementByteSizeHint != null) {
} else if (registerClassType == typeof(ArrayRegister) && data.byteSize != null) {
numericSizePerElement = (uint)data.byteSizeHint + 4;
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
//-------------------------------------------
//as byte range register
} else if (elementType == typeof(string)) {
ArrayRegister instance = new ArrayRegister(data.memAddress, (uint)data.byteSize, data.name);
generatedInstance = instance;
sizeStateFlags = DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate;
} 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,
data.byteSizeHint,
data.arrayIndicies,
sizeStateFlags,
data.name
};
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
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 instance = (Register)constr.Invoke(parameters);
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 (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType()) {
//-------------------------------------------
//as single register
uint numericSize = (uint)data.dotnetVarType.DetermineTypeByteSize();
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");
}
}
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;
}
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.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);
var parameters = new object[] {
data.memAddress,
numericSize,
sizeStateFlags,
data.name
};
var instance = (Register)constr.Invoke(parameters);
generatedInstance = instance;
} else if (data.regType.IsBoolean()) {

View File

@@ -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<IRegister> 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;
}
}

View File

@@ -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 {
/// <summary>
/// Defines a register containing a string
/// </summary>
public class ArrayRegister : BaseRegister {
public class ArrayRegister<T> : Register {
internal int[] indicies;
internal uint addressLength;
@@ -21,24 +21,21 @@ namespace MewtocolNet.Registers {
/// </summary>
public uint AddressLength => addressLength;
internal uint ReservedBytesSize { get; set; }
internal ushort? ReservedBitSize { get; set; }
[Obsolete("Creating registers directly is not supported use IPlc.Register instead")]
public ArrayRegister() =>
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
internal ArrayRegister(uint _address, uint _reservedByteSize, string _name = null) {
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,35 +50,72 @@ namespace MewtocolNet.Registers {
if (Value == null) return "null";
if(Value != null && Value is BitArray bitArr) {
return bitArr.ToBitString();
} else {
return ((byte[])Value).ToHexString("-");
}
}
/// <inheritdoc/>
public override string BuildMewtocolQuery() {
StringBuilder asciistring = new StringBuilder("D");
asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0'));
asciistring.Append((MemoryAddress + AddressLength - 1).ToString().PadLeft(5, '0'));
return asciistring.ToString();
}
/// <inheritdoc/>
public override string GetRegisterString() => "DT";
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/>
public override async Task<bool> WriteAsync(object value) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var encoded = PlcValueParser.Encode(this, (T)value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
if (res) {
//find the underlying memory
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
.FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null)
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded);
AddSuccessWrite();
UpdateHoldingValue(value);
}
return res;
}
/// <inheritdoc/>
public override async Task<object> ReadAsync() {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var res = await 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<T>(this, indicies, bytes);
UpdateHoldingValue(parsed);
return parsed;
}
/// <inheritdoc/>
internal override void UpdateHoldingValue(object val) {

View File

@@ -1,9 +1,7 @@
using MewtocolNet.Registers;
using System;
using System.Text;
using System;
using System.Threading.Tasks;
namespace MewtocolNet {
namespace MewtocolNet.Registers {
/// <summary>
/// An interface for all register types

View File

@@ -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 {
/// <summary>
/// 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,6 +36,8 @@ namespace MewtocolNet.Registers {
internal uint successfulReads = 0;
internal uint successfulWrites = 0;
internal bool wasOverlapFitted = false;
/// <inheritdoc/>
public RegisterCollection ContainedCollection => containedCollection;
@@ -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,7 +84,7 @@ 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);
@@ -97,30 +101,19 @@ namespace MewtocolNet.Registers {
public virtual Task<bool> 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"}";
@@ -136,7 +129,7 @@ namespace MewtocolNet.Registers {
#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,17 +139,17 @@ 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 &&
@@ -166,11 +159,38 @@ namespace MewtocolNet.Registers {
}
internal virtual bool IsSameAddress (BaseRegister toCompare) {
internal int AveragePollLevel(List<Register> 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<Register> CanFitAddressRange(List<Register> 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,26 +221,22 @@ 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($"SPAddress: {GetSpecialAddress():X1}\n");

View File

@@ -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 {
/// <summary>
/// Defines a register containing a boolean
/// </summary>
public class BoolRegister : BaseRegister {
public class BoolRegister : Register {
internal byte specialAddress;
/// <summary>
@@ -57,24 +50,6 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override byte? GetSpecialAddress() => SpecialAddress;
/// <inheritdoc/>
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();
}
/// <inheritdoc/>
public override string GetMewName() {
@@ -97,7 +72,7 @@ namespace MewtocolNet.Registers {
}
/// <inheritdoc/>
public override uint GetRegisterAddressLen () => 1;
public override uint GetRegisterAddressLen() => 1;
}

View File

@@ -1,65 +0,0 @@
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System;
using System.Threading.Tasks;
namespace MewtocolNet {
internal interface IRegisterInternal {
event Action<object> 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<object> ReadAsync();
Task<bool> WriteAsync(object data);
string ToString();
string ToString(bool detailed);
}
}

View File

@@ -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
/// </summary>
/// <typeparam name="T">The type of the numeric value</typeparam>
public class NumberRegister<T> : BaseRegister {
public class SingleRegister<T> : Register {
internal uint addressLength;
/// <summary>
/// The rgisters memory length
/// </summary>
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) {
//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;
CheckAddressOverflow(memoryAddress, addressLength);
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);
}
/// <inheritdoc/>
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();
}
@@ -90,7 +55,7 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/>
public override string GetValueString() {
if(Value != null && typeof(T) == typeof(TimeSpan)) {
if (Value != null && typeof(T) == typeof(TimeSpan)) {
return $"{Value} [{((TimeSpan)Value).ToPlcTime()}]";
@@ -116,18 +81,21 @@ namespace MewtocolNet.Registers {
}
/// <inheritdoc/>
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
public override uint GetRegisterAddressLen() => AddressLength;
/// <inheritdoc/>
public override async Task<bool> WriteAsync (object value) {
public override async Task<bool> WriteAsync(object value) {
if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
if (dynamicSizeState.HasFlag(DynamicSizeState.DynamicallySized | DynamicSizeState.NeedsSizeUpdate))
await UpdateDynamicSize();
var encoded = PlcValueParser.Encode(this, (T)value);
var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
if(res) {
if (res) {
//find the underlying memory
var matchingReg = attachedInterface.memoryManager.GetAllRegisters()
@@ -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()
.FirstOrDefault(x => x.IsSameAddressAndType(this));
if (matchingReg != null)
if (matchingReg != null) {
if (matchingReg is SingleRegister<string> sreg && this is SingleRegister<string> selfSreg) {
sreg.addressLength = selfSreg.addressLength;
sreg.dynamicSizeState = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
}
matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res);
}
return SetValueFromBytes(res);
}
internal override async Task UpdateDynamicSize() {
if (typeof(T) == typeof(string)) await UpdateDynamicSizeString();
dynamicSizeState = DynamicSizeState.DynamicallySized | DynamicSizeState.WasSizeUpdated;
}
private async Task UpdateDynamicSizeString () {
Logger.Log($"Calibrating dynamic register ({GetRegisterWordRangeString()}) from PLC source", LogLevel.Verbose, attachedInterface);
//get the string describer bytes
var bytes = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, 4);
if (bytes == null || bytes.Length == 0 || bytes.All(x => x == 0x0)) {
throw new MewtocolException($"The string register ({GetMewName()}{MemoryAddress}) doesn't exist in the PLC program");
}
var reservedSize = BitConverter.ToInt16(bytes, 0);
var usedSize = BitConverter.ToInt16(bytes, 2);
var wordsSize = Math.Max(0, (uint)(2 + (reservedSize + 1) / 2));
addressLength = wordsSize;
CheckAddressOverflow(memoryAddress, wordsSize);
}
internal override object SetValueFromBytes(byte[] bytes) {
AddSuccessRead();

View File

@@ -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 {
/// <summary>
/// Defines a register containing a string
/// </summary>
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;
}
/// <inheritdoc/>
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();
}
/// <inheritdoc/>
public override string GetValueString() => Value == null ? "null" : $"'{Value}'";
/// <inheritdoc/>
public override string GetRegisterString() => "DT";
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
internal override void UpdateHoldingValue(object val) {
if ((val == null && lastValue != null) || val != lastValue) {
lastValue = val;
TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this);
}
}
}
}

View File

@@ -0,0 +1,39 @@
namespace MewtocolNet.SetupClasses {
public class InterfaceSettings {
/// <summary>
/// <code>
/// This feature can improve read write times by a big margin but also
/// block outgoing messages inbetween polling cycles more frequently
/// </code>
/// The max distance of the gap between registers (if there is a gap between
/// adjacent registers) to merge them into one request <br/>
/// Example: <br/>
/// <example>
/// We have a register at DT100 (1 word long) and a
/// register at DT101 (1 word long) <br/>
/// - If the max distance is 0 it will not merge them into one request<br/>
/// - If the max distance is 1 it will merge them into one request<br/>
/// - 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<br/>
/// </example>
/// </summary>
public int MaxOptimizationDistance { get; set; } = 4;
/// <summary>
/// The overwrite mode for poll levels <br/>
/// When set to <see cref="PollLevelOverwriteMode.Lowest"/> the lowest average poll level for overlapping registers gets used <br/>
/// When set to <see cref="PollLevelOverwriteMode.Highest"/> the highest average poll level for overlapping registers gets used
/// </summary>
public PollLevelOverwriteMode PollLevelOverwriteMode { get; set; } = PollLevelOverwriteMode.Highest;
/// <summary>
/// Defines how many WORD blocks the interface will send on a DT area write request before splitting up messages <br/>
/// Higher numbers will result in a longer send and receive thread blocking time
/// </summary>
public int MaxDataBlocksPerWrite { get; set; } = 8;
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace MewtocolNet.SetupClasses {

View File

@@ -1,12 +1,9 @@
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 {
@@ -58,7 +55,7 @@ namespace MewtocolNet.TypeConversion {
//default short DT conversion
new PlcTypeConversion<short>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<short>),
HoldingRegisterType = typeof(SingleRegister<short>),
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<ushort>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<ushort>),
HoldingRegisterType = typeof(SingleRegister<ushort>),
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<Word>(RegisterType.DT) {
HoldingRegisterType = typeof(NumberRegister<Word>),
HoldingRegisterType = typeof(SingleRegister<Word>),
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<int>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<int>),
HoldingRegisterType = typeof(SingleRegister<int>),
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<uint>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<uint>),
HoldingRegisterType = typeof(SingleRegister<uint>),
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<DWord>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<DWord>),
HoldingRegisterType = typeof(SingleRegister<DWord>),
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<float>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<float>),
HoldingRegisterType = typeof(SingleRegister<float>),
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<TimeSpan>(RegisterType.DDT) {
HoldingRegisterType = typeof(NumberRegister<TimeSpan>),
HoldingRegisterType = typeof(SingleRegister<TimeSpan>),
PlcVarType = PlcVarType.TIME,
FromRaw = (reg, bytes) => {
@@ -131,19 +128,11 @@ namespace MewtocolNet.TypeConversion {
},
},
//default byte array DT Range conversion, direct pass through
new PlcTypeConversion<byte[]>(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<string>(RegisterType.DT_BYTE_RANGE) {
HoldingRegisterType = typeof(StringRegister),
HoldingRegisterType = typeof(SingleRegister<string>),
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<byte> finalBytes = new List<byte>();
finalBytes.AddRange(BitConverter.GetBytes(sReg.ReservedSize));
finalBytes.AddRange(BitConverter.GetBytes((short)value.Length));
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();

View File

@@ -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();

View File

@@ -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<IRegister, byte[], T> FromRaw { get; set; }
public Func<Register, byte[], T> FromRaw { get; set; }
public Func<IRegister, T, byte[]> ToRaw { get; set; }
public Func<Register, T, byte[]> 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);
}

View File

@@ -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<IPlcTypeConverter> conversions => Conversions.items;
internal static T Parse<T> (IRegister register, byte[] bytes) {
internal static T Parse<T>(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<T> (IRegister register, T value) {
internal static T ParseArray <T>(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<T>(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<Type> GetAllowDotnetTypes () => conversions.Select(x => x.GetDotnetType()).ToList();
//internal static byte[] EncodeArray (IRegister register, T value) {
public static List<Type> GetAllowRegisterTypes () => conversions.Select(x => x.GetHoldingRegisterType()).ToList();
public static RegisterType? GetDefaultRegisterType (Type type) =>
//}
public static List<Type> GetAllowDotnetTypes() => conversions.Select(x => x.GetDotnetType()).ToList();
public static List<Type> 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();
}

View File

@@ -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;

View File

@@ -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,13 +23,13 @@ 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;
@@ -64,7 +62,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
internal async Task<bool> RequestByteReadAsync (ulong addStart, ulong addEnd) {
internal async Task<bool> 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<StringRegister>()
.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'));

View File

@@ -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();

View File

@@ -1,7 +1,5 @@
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.UnderlyingRegisters {
@@ -11,7 +9,7 @@ namespace MewtocolNet.UnderlyingRegisters {
internal uint AddressEnd;
internal List<BaseRegister> Linked = new List<BaseRegister>();
internal List<Register> Linked = new List<Register>();
}

View File

@@ -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,7 +24,7 @@ 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);
@@ -35,7 +32,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
// 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<BaseRegister> registers = null) {
internal async Task OnPlcConnected () {
//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;
//check all area for dynamic sized registers
await CheckAllDynamicallySizedAreas();
}
internal void LinkAndMergeRegisters(List<Register> registers = null) {
//for self calling
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,26 +98,28 @@ 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,
});
@@ -131,7 +129,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
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;
@@ -260,7 +258,7 @@ namespace MewtocolNet.UnderlyingRegisters {
insertReg.underlyingMemory = targetArea;
if (insertReg.name == null) {
if (insertReg.autoGenerated && insertReg.name == null) {
insertReg.name = $"auto_{Guid.NewGuid().ToString("N")}";
}
@@ -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,7 +334,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
//set stopwatch for levels
if(pollLevelConfigs.ContainsKey(pollLevel.level)) {
if (pollLevelConfigs.ContainsKey(pollLevel.level)) {
pollLevelConfigs[pollLevel.level].timeFromLastRead = Stopwatch.StartNew();
@@ -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;
@@ -423,9 +439,19 @@ namespace MewtocolNet.UnderlyingRegisters {
}
internal IEnumerable<BaseRegister> GetAllRegisters () {
internal void ClearAllRegisters () {
List<BaseRegister> registers = new List<BaseRegister>();
foreach (var lvl in pollLevels) {
lvl.dataAreas.Clear();
}
}
internal IEnumerable<Register> GetAllRegisters() {
List<Register> registers = new List<Register>();
foreach (var lvl in pollLevels) {

View File

@@ -6,7 +6,7 @@ namespace MewtocolNet.UnderlyingRegisters {
internal int lastReadTimeMs = 0;
internal PollLevel (int wrSize, int dtSize) {
internal PollLevel(int wrSize, int dtSize) {
externalRelayInAreas = new List<WRArea>(wrSize * 16);
externalRelayOutAreas = new List<WRArea>(wrSize * 16);

View File

@@ -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<BaseRegister> linkedRegisters = new List<BaseRegister>();
internal List<Register> linkedRegisters = new List<Register>();
public ulong AddressStart => addressStart;
@@ -25,29 +24,29 @@ 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<bool> ReadRegisterAsync(BaseRegister reg) {
public async Task<bool> ReadRegisterAsync(Register reg) {
return true;
}
public async Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes) {
public async Task<bool> WriteRegisterAsync(Register reg, byte[] bytes) {
return true;
@@ -55,7 +54,7 @@ namespace MewtocolNet.UnderlyingRegisters {
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();