Multiple fixes

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

View File

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

View File

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

View File

@@ -1,46 +0,0 @@
using MewtocolNet.Registers;
using System;
namespace MewtocolNet.Exceptions {
[Serializable]
public class MewtocolException : Exception {
public MewtocolException() { }
public MewtocolException(string message) : base(message) { }
public MewtocolException(string message, Exception inner) : base(message, inner) { }
protected MewtocolException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
internal static MewtocolException NotConnectedSend() {
return new MewtocolException($"Can not send a message to the PLC if it isn't connected");
}
internal static MewtocolException DupeRegister(Register register) {
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}");
}
internal static MewtocolException DupeNameRegister(Register register) {
return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetMewName()}");
}
internal static MewtocolException OverlappingRegister(Register registerA, Register registerB) {
return new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " +
$"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}");
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,32 +25,6 @@ namespace MewtocolNet.RegisterBuilding {
interf.AddRegisters(registers.ToArray()); 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(); return registers.First();
} }
@@ -77,33 +51,6 @@ namespace MewtocolNet.RegisterBuilding {
interf.AddRegisters(registers.ToArray()); 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; return plc;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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