Change register list source

- add poller levels
- add new register builder pattern
This commit is contained in:
Felix Weiß
2023-07-12 18:54:07 +02:00
parent 6c7e91f648
commit 82821e773d
30 changed files with 1209 additions and 1097 deletions

View File

@@ -17,6 +17,12 @@ namespace MewtocolNet.Exceptions {
System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 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 (IRegisterInternal register) { internal static MewtocolException DupeRegister (IRegisterInternal register) {
return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}"); return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}");
@@ -31,7 +37,7 @@ namespace MewtocolNet.Exceptions {
internal static MewtocolException OverlappingRegister (IRegisterInternal registerA, IRegisterInternal registerB) { internal static MewtocolException OverlappingRegister (IRegisterInternal registerA, IRegisterInternal registerB) {
throw new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " + return new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " +
$"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}"); $"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}");
} }

View File

@@ -59,7 +59,7 @@ namespace MewtocolNet {
if (_onString == null) if (_onString == null)
return null; return null;
var res = new Regex(@"\%([0-9]{2})\$RD(.{" + _blockSize + "})").Match(_onString); var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(.{" + _blockSize + "})").Match(_onString);
if (res.Success) { if (res.Success) {
string val = res.Groups[2].Value; string val = res.Groups[2].Value;
return val; return val;
@@ -75,7 +75,7 @@ namespace MewtocolNet {
_onString = _onString.Replace("\r", ""); _onString = _onString.Replace("\r", "");
var res = new Regex(@"\%([0-9]{2})\$RC(.)").Match(_onString); var res = new Regex(@"\%([0-9a-fA-F]{2})\$RC(.)").Match(_onString);
if (res.Success) { if (res.Success) {
string val = res.Groups[2].Value; string val = res.Groups[2].Value;
return val == "1"; return val == "1";
@@ -91,7 +91,7 @@ namespace MewtocolNet {
_onString = _onString.Replace("\r", ""); _onString = _onString.Replace("\r", "");
var res = new Regex(@"\%([0-9]{2})\$RC(?<bits>(?:0|1){0,8})(..)").Match(_onString); var res = new Regex(@"\%([0-9a-fA-F]{2})\$RC(?<bits>(?:0|1){0,8})(..)").Match(_onString);
if (res.Success) { if (res.Success) {
string val = res.Groups["bits"].Value; string val = res.Groups["bits"].Value;
@@ -121,7 +121,7 @@ namespace MewtocolNet {
_onString = _onString.Replace("\r", ""); _onString = _onString.Replace("\r", "");
var res = new Regex(@"\%([0-9]{2})\$RD(?<data>.*)(?<csum>..)").Match(_onString); var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(?<data>.*)(?<csum>..)").Match(_onString);
if (res.Success) { if (res.Success) {
string val = res.Groups["data"].Value; string val = res.Groups["data"].Value;
@@ -245,16 +245,20 @@ namespace MewtocolNet {
bool valCompare = reg1.RegisterType == compare.RegisterType && bool valCompare = reg1.RegisterType == compare.RegisterType &&
reg1.MemoryAddress == compare.MemoryAddress && reg1.MemoryAddress == compare.MemoryAddress &&
reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
reg1.GetSpecialAddress() == compare.GetSpecialAddress(); reg1.GetSpecialAddress() == compare.GetSpecialAddress();
return valCompare; return valCompare;
} }
internal static bool CompareIsDuplicateNonCast (this BaseRegister reg1, BaseRegister compare) { internal static bool CompareIsDuplicateNonCast (this BaseRegister reg1, BaseRegister compare, bool ingnoreByteRegisters = true) {
if (ingnoreByteRegisters && (compare.GetType() == typeof(BytesRegister) || reg1.GetType() == typeof(BytesRegister))) return false;
bool valCompare = reg1.GetType() != compare.GetType() && bool valCompare = reg1.GetType() != compare.GetType() &&
reg1.MemoryAddress == compare.MemoryAddress && reg1.MemoryAddress == compare.MemoryAddress &&
reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() &&
reg1.GetSpecialAddress() == compare.GetSpecialAddress(); reg1.GetSpecialAddress() == compare.GetSpecialAddress();
return valCompare; return valCompare;

View File

@@ -1,6 +1,8 @@
using MewtocolNet.Registers; using MewtocolNet.RegisterBuilding;
using MewtocolNet.Registers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MewtocolNet { namespace MewtocolNet {
@@ -8,7 +10,7 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// Provides a interface for Panasonic PLCs /// Provides a interface for Panasonic PLCs
/// </summary> /// </summary>
public interface IPlc : IDisposable { public interface IPlc : IDisposable, INotifyPropertyChanged {
/// <summary> /// <summary>
/// The current connection state of the interface /// The current connection state of the interface
@@ -35,11 +37,6 @@ namespace MewtocolNet {
/// </summary> /// </summary>
int QueuedMessages { get; } int QueuedMessages { get; }
/// <summary>
/// The registered data registers of the PLC
/// </summary>
IEnumerable<IRegister> Registers { get; }
/// <summary> /// <summary>
/// Generic information about the connected PLC /// Generic information about the connected PLC
/// </summary> /// </summary>
@@ -60,13 +57,24 @@ namespace MewtocolNet {
/// </summary> /// </summary>
int ConnectTimeout { get; set; } int ConnectTimeout { get; set; }
/// <summary>
/// Provides an anonymous interface for register reading and writing without memory management
/// </summary>
RBuild Register { get; }
/// <summary> /// <summary>
/// Tries to establish a connection with the device asynchronously /// Tries to establish a connection with the device asynchronously
/// </summary> /// </summary>
Task ConnectAsync(); Task ConnectAsync();
/// <summary> /// <summary>
/// Disconnects the devive from its current connection /// Disconnects the device from its current plc connection
/// and awaits the end of all asociated tasks
/// </summary>
Task DisconnectAsync();
/// <summary>
/// Disconnects the device from its current plc connection
/// </summary> /// </summary>
void Disconnect(); void Disconnect();
@@ -97,23 +105,13 @@ namespace MewtocolNet {
/// useful if you want to use a custom update frequency /// useful if you want to use a custom update frequency
/// </summary> /// </summary>
/// <returns>The number of inidvidual mewtocol commands sent</returns> /// <returns>The number of inidvidual mewtocol commands sent</returns>
Task<int> RunPollerCylceManual(); Task<int> RunPollerCylceManualAsync();
/// <summary> /// <summary>
/// Gets the connection info string /// Gets the connection info string
/// </summary> /// </summary>
string GetConnectionInfo(); string GetConnectionInfo();
/// <summary>
/// Adds a register to the plc
/// </summary>
void AddRegister(BaseRegister register);
/// <summary>
/// Adds a register to the plc
/// </summary>
void AddRegister(IRegister register);
/// <summary> /// <summary>
/// Gets a register from the plc by name /// Gets a register from the plc by name
/// </summary> /// </summary>

View File

@@ -1,5 +1,6 @@
using MewtocolNet.Exceptions; using MewtocolNet.Exceptions;
using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterAttributes;
using MewtocolNet.SetupClasses;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -15,14 +16,16 @@ namespace MewtocolNet {
/// </summary> /// </summary>
public static class Mewtocol { public static class Mewtocol {
#region Build Order 1
/// <summary> /// <summary>
/// Builds a ethernet based Mewtocol Interface /// Builds a ethernet based Mewtocol Interface
/// </summary> /// </summary>
/// <param name="ip"></param> /// <param name="ip"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="station">Plc station number</param> /// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns> /// <returns></returns>
public static PostInit<IPlcEthernet> Ethernet (string ip, int port = 9094, int station = 1) { public static PostInit<IPlcEthernet> Ethernet (string ip, int port = 9094, int station = 0xEE) {
var instance = new MewtocolInterfaceTcp(); var instance = new MewtocolInterfaceTcp();
instance.ConfigureConnection(ip, port, station); instance.ConfigureConnection(ip, port, station);
@@ -37,9 +40,9 @@ namespace MewtocolNet {
/// </summary> /// </summary>
/// <param name="ip"></param> /// <param name="ip"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="station">Plc station number</param> /// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns> /// <returns></returns>
public static PostInit<IPlcEthernet> Ethernet(IPAddress ip, int port = 9094, int station = 1) { public static PostInit<IPlcEthernet> Ethernet(IPAddress ip, int port = 9094, int station = 0xEE) {
var instance = new MewtocolInterfaceTcp(); var instance = new MewtocolInterfaceTcp();
instance.ConfigureConnection(ip, port, station); instance.ConfigureConnection(ip, port, station);
@@ -52,14 +55,14 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// Builds a serial port based Mewtocol Interface /// Builds a serial port based Mewtocol Interface
/// </summary> /// </summary>
/// <param name="portName"></param> /// <param name="portName">System port name</param>
/// <param name="baudRate"></param> /// <param name="baudRate">Baud rate of the plc toolport</param>
/// <param name="dataBits"></param> /// <param name="dataBits">DataBits of the plc toolport</param>
/// <param name="parity"></param> /// <param name="parity">Parity rate of the plc toolport</param>
/// <param name="stopBits"></param> /// <param name="stopBits">Stop bits of the plc toolport</param>
/// <param name="station"></param> /// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns> /// <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 = 1) { 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); TestPortName(portName);
@@ -75,9 +78,9 @@ namespace MewtocolNet {
/// Builds a serial mewtocol interface that finds the correct settings for the given port name automatically /// Builds a serial mewtocol interface that finds the correct settings for the given port name automatically
/// </summary> /// </summary>
/// <param name="portName"></param> /// <param name="portName"></param>
/// <param name="station"></param> /// <param name="station">Plc station number 0xEE for direct communication</param>
/// <returns></returns> /// <returns></returns>
public static PostInit<IPlcSerial> SerialAuto (string portName, int station = 1) { public static PostInit<IPlcSerial> SerialAuto (string portName, int station = 0xEE) {
TestPortName(portName); TestPortName(portName);
@@ -99,6 +102,10 @@ namespace MewtocolNet {
} }
#endregion
#region Build Order 2
public class MemoryManagerSettings { public class MemoryManagerSettings {
/// <summary> /// <summary>
@@ -125,6 +132,80 @@ namespace MewtocolNet {
/// </summary> /// </summary>
public int MaxRegistersPerGroup { get; set; } = -1; 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>();
/// <summary>
/// Sets the poll level for the given key
/// </summary>
/// <param name="level">The level to reference</param>
/// <param name="interval">Delay between poll requests</param>
public PollLevelConfigurator SetLevel (int level, TimeSpan interval) {
if(level <= 1)
throw new NotSupportedException($"The poll level {level} is not configurable");
if (!levelConfigs.ContainsKey(level)) {
levelConfigs.Add(level, new PollLevelConfig {
delay = interval,
});
} else {
throw new NotSupportedException("Can't set poll levels multiple times");
}
return this;
}
public PollLevelConfigurator SetLevel(int level, int skipNth) {
if (level <= 1)
throw new NotSupportedException($"The poll level {level} is not configurable");
if (!levelConfigs.ContainsKey(level)) {
levelConfigs.Add(level, new PollLevelConfig {
skipNth = skipNth,
});
} else {
throw new NotSupportedException("Can't set poll levels multiple times");
}
return this;
}
}
public class RegCollector {
internal List<RegisterCollection> collections = new List<RegisterCollection>();
public RegCollector AddCollection(RegisterCollection collection) {
collections.Add(collection);
return this;
}
public RegCollector AddCollection<T>() where T : RegisterCollection {
var instance = (RegisterCollection)Activator.CreateInstance(typeof(T));
collections.Add(instance);
return this;
}
} }
public class PostInit<T> { public class PostInit<T> {
@@ -162,6 +243,25 @@ namespace MewtocolNet {
imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance; imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance;
imew.memoryManager.maxRegistersPerGroup = res.MaxRegistersPerGroup; imew.memoryManager.maxRegistersPerGroup = res.MaxRegistersPerGroup;
imew.memoryManager.allowByteRegDupes = res.AllowByteRegisterDupes;
}
return this;
}
/// <summary>
/// A builder for poll custom levels
/// </summary>
public PostInit<T> WithCustomPollLevels (Action<PollLevelConfigurator> levels) {
var res = new PollLevelConfigurator();
levels.Invoke(res);
if (intf is MewtocolInterface imew) {
imew.memoryManager.pollLevelConfigs = res.levelConfigs;
} }
@@ -172,9 +272,9 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// A builder for attaching register collections /// A builder for attaching register collections
/// </summary> /// </summary>
public EndInit<T> WithRegisterCollections(Action<RegisterCollectionCollector> collector) { public EndInit<T> WithRegisterCollections(Action<RegCollector> collector) {
var res = new RegisterCollectionCollector(); var res = new RegCollector();
collector.Invoke(res); collector.Invoke(res);
if (intf is MewtocolInterface imew) { if (intf is MewtocolInterface imew) {
@@ -194,6 +294,10 @@ namespace MewtocolNet {
} }
#endregion
#region BuildLevel 3
public class EndInit<T> { public class EndInit<T> {
internal PostInit<T> postInit; internal PostInit<T> postInit;
@@ -205,6 +309,8 @@ namespace MewtocolNet {
} }
#endregion
} }
} }

View File

@@ -15,7 +15,7 @@ using MewtocolNet.UnderlyingRegisters;
namespace MewtocolNet { namespace MewtocolNet {
public abstract partial class MewtocolInterface : IPlc, INotifyPropertyChanged, IDisposable { public abstract partial class MewtocolInterface : IPlc {
#region Private fields #region Private fields
@@ -28,6 +28,7 @@ namespace MewtocolNet {
private PLCInfo plcInfo; private PLCInfo plcInfo;
private protected int stationNumber; private protected int stationNumber;
private protected int RecBufferSize = 128;
private protected int bytesTotalCountedUpstream = 0; private protected int bytesTotalCountedUpstream = 0;
private protected int bytesTotalCountedDownstream = 0; private protected int bytesTotalCountedDownstream = 0;
private protected int cycleTimeMs = 25; private protected int cycleTimeMs = 25;
@@ -35,7 +36,6 @@ namespace MewtocolNet {
private protected int bytesPerSecondDownstream = 0; private protected int bytesPerSecondDownstream = 0;
private protected AsyncQueue queue = new AsyncQueue(); private protected AsyncQueue queue = new AsyncQueue();
private protected int RecBufferSize = 128;
private protected Stopwatch speedStopwatchUpstr; private protected Stopwatch speedStopwatchUpstr;
private protected Stopwatch speedStopwatchDownstr; private protected Stopwatch speedStopwatchDownstr;
private protected Task firstPollTask = new Task(() => { }); private protected Task firstPollTask = new Task(() => { });
@@ -52,9 +52,6 @@ namespace MewtocolNet {
internal bool usePoller = false; internal bool usePoller = false;
internal MemoryAreaManager memoryManager; internal MemoryAreaManager memoryManager;
internal List<BaseRegister> RegistersUnderlying { get; private set; } = new List<BaseRegister>();
internal IEnumerable<IRegisterInternal> RegistersInternal => RegistersUnderlying.Cast<IRegisterInternal>();
#endregion #endregion
#region Public Read Only Properties / Fields #region Public Read Only Properties / Fields
@@ -95,9 +92,6 @@ namespace MewtocolNet {
} }
} }
/// <inheritdoc/>
public IEnumerable<IRegister> Registers => RegistersUnderlying.Cast<IRegister>();
/// <inheritdoc/> /// <inheritdoc/>
public int StationNumber => stationNumber; public int StationNumber => stationNumber;
@@ -173,18 +167,26 @@ namespace MewtocolNet {
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual async Task ConnectAsync() => throw new NotImplementedException(); public virtual Task ConnectAsync() => throw new NotImplementedException();
/// <inheritdoc/> /// <inheritdoc/>
public async Task AwaitFirstDataCycleAsync() => await firstPollTask; public async Task AwaitFirstDataCycleAsync() => await firstPollTask;
/// <inheritdoc/>
public async Task DisconnectAsync () {
await pollCycleTask;
Disconnect();
}
/// <inheritdoc/> /// <inheritdoc/>
public void Disconnect() { public void Disconnect() {
if (!IsConnected) return; if (!IsConnected) return;
if(pollCycleTask != null && !pollCycleTask.IsCompleted) if (!pollCycleTask.IsCompleted) pollCycleTask.Wait();
pollCycleTask.Wait();
OnMajorSocketExceptionWhileConnected(); OnMajorSocketExceptionWhileConnected();
@@ -194,7 +196,9 @@ namespace MewtocolNet {
public void Dispose() { public void Dispose() {
if (Disposed) return; if (Disposed) return;
Disconnect(); Disconnect();
//GC.SuppressFinalize(this); //GC.SuppressFinalize(this);
Disposed = true; Disposed = true;
@@ -329,7 +333,7 @@ namespace MewtocolNet {
SetDownstreamStopWatchStart(); SetDownstreamStopWatchStart();
byte[] buffer = new byte[128]; byte[] buffer = new byte[RecBufferSize];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
CalcDownstreamSpeed(bytesRead); CalcDownstreamSpeed(bytesRead);

View File

@@ -3,6 +3,7 @@ using MewtocolNet.Logging;
using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterAttributes;
using MewtocolNet.RegisterBuilding; using MewtocolNet.RegisterBuilding;
using MewtocolNet.Registers; using MewtocolNet.Registers;
using MewtocolNet.SetupClasses;
using MewtocolNet.UnderlyingRegisters; using MewtocolNet.UnderlyingRegisters;
using System; using System;
using System.Collections; using System.Collections;
@@ -24,14 +25,18 @@ namespace MewtocolNet {
internal Task pollCycleTask; internal Task pollCycleTask;
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
internal IEnumerable<BaseRegister> RegistersInternal => GetAllRegistersInternal();
public IEnumerable<IRegister> Registers => GetAllRegisters();
/// <summary> /// <summary>
/// True if the poller is actvice (can be paused) /// True if the poller is actvice (can be paused)
/// </summary> /// </summary>
public bool PollerActive => !pollerTaskStopped; public bool PollerActive => !pollerTaskStopped;
/// <summary> /// <inheritdoc/>
/// Current poller cycle duration
/// </summary>
public int PollerCycleDurationMs { public int PollerCycleDurationMs {
get => pollerCycleDurationMs; get => pollerCycleDurationMs;
private set { private set {
@@ -40,7 +45,8 @@ namespace MewtocolNet {
} }
} }
private List<RegisterCollection> registerCollections = new List<RegisterCollection>(); /// <inheritdoc/>
public RBuild Register => new RBuild(this);
#region Register Polling #region Register Polling
@@ -74,11 +80,11 @@ namespace MewtocolNet {
/// useful if you want to use a custom update frequency /// useful if you want to use a custom update frequency
/// </summary> /// </summary>
/// <returns>The number of inidvidual mewtocol commands sent</returns> /// <returns>The number of inidvidual mewtocol commands sent</returns>
public async Task<int> RunPollerCylceManual() { public async Task<int> RunPollerCylceManualAsync() {
if (!pollerTaskStopped) if (!pollerTaskStopped)
throw new NotSupportedException($"The poller is already running, " + throw new NotSupportedException($"The poller is already running, " +
$"please make sure there is no polling active before calling {nameof(RunPollerCylceManual)}"); $"please make sure there is no polling active before calling {nameof(RunPollerCylceManualAsync)}");
tcpMessagesSentThisCycle = 0; tcpMessagesSentThisCycle = 0;
@@ -139,69 +145,46 @@ namespace MewtocolNet {
private async Task UpdateRCPRegisters() { private async Task UpdateRCPRegisters() {
//build booleans //build booleans
var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister)) //var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister))
.Select(x => (BoolRegister)x) // .Select(x => (BoolRegister)x)
.ToArray(); // .ToArray();
//one frame can only read 8 registers at a time ////one frame can only read 8 registers at a time
int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8); //int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8);
int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8; //int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8;
for (int i = 0; i < rcpFrameCount; i++) { //for (int i = 0; i < rcpFrameCount; i++) {
int toReadRegistersCount = 8; // int toReadRegistersCount = 8;
if (i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder; // if (i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder;
var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}"); // var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}");
for (int j = 0; j < toReadRegistersCount; j++) { // for (int j = 0; j < toReadRegistersCount; j++) {
BoolRegister register = rcpList[i + j]; // BoolRegister register = rcpList[i + j];
rcpString.Append(register.BuildMewtocolQuery()); // rcpString.Append(register.BuildMewtocolQuery());
} // }
string rcpRequest = rcpString.ToString(); // string rcpRequest = rcpString.ToString();
var result = await SendCommandAsync(rcpRequest); // var result = await SendCommandAsync(rcpRequest);
if (!result.Success) return; // if (!result.Success) return;
var resultBitArray = result.Response.ParseRCMultiBit(); // var resultBitArray = result.Response.ParseRCMultiBit();
for (int k = 0; k < resultBitArray.Length; k++) { // for (int k = 0; k < resultBitArray.Length; k++) {
var register = rcpList[i + k]; // var register = rcpList[i + k];
if ((bool)register.Value != resultBitArray[k]) { // if ((bool)register.Value != resultBitArray[k]) {
register.SetValueFromPLC(resultBitArray[k]); // register.SetValueFromPLC(resultBitArray[k]);
} // }
} // }
} //}
}
private async Task UpdateDTRegisters() {
foreach (var reg in RegistersUnderlying) {
var type = reg.GetType();
if (reg.RegisterType.IsNumericDTDDT() || reg.RegisterType == RegisterType.DT_BYTE_RANGE) {
var lastVal = reg.Value;
var rwReg = (IRegisterInternal)reg;
var readout = await rwReg.ReadAsync();
if (readout == null) return;
if (lastVal != readout) {
rwReg.SetValueFromPLC(readout);
}
}
}
} }
@@ -217,7 +200,7 @@ namespace MewtocolNet {
if (registerCollections.Count != 0) if (registerCollections.Count != 0)
throw new NotSupportedException("Register collections can only be build once"); throw new NotSupportedException("Register collections can only be build once");
List<RegisterBuildInfo> buildInfos = new List<RegisterBuildInfo>(); var regBuild = RBuild.Factory;
foreach (var collection in collections) { foreach (var collection in collections) {
@@ -234,18 +217,24 @@ namespace MewtocolNet {
if (attr is RegisterAttribute cAttribute) { if (attr is RegisterAttribute cAttribute) {
var pollFreqAttr = (PollLevelAttribute)attributes.FirstOrDefault(x => x.GetType() == typeof(PollLevelAttribute));
if (!prop.PropertyType.IsAllowedPlcCastingType()) { if (!prop.PropertyType.IsAllowedPlcCastingType()) {
throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})"); throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})");
} }
var dotnetType = prop.PropertyType; var dotnetType = prop.PropertyType;
int pollLevel = 1;
buildInfos.Add(new RegisterBuildInfo { if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel;
mewAddress = cAttribute.MewAddress,
dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType, //add builder item
collectionTarget = collection, regBuild
boundPropTarget = prop, .Address(cAttribute.MewAddress)
}); .AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType)
.PollLevel(pollLevel)
.RegCollection(collection)
.BoundProp(prop);
} }
@@ -265,7 +254,9 @@ namespace MewtocolNet {
} }
AddRegisters(buildInfos); var assembler = new RegisterAssembler(this);
var registers = assembler.Assemble(regBuild);
AddRegisters(registers.ToArray());
} }
@@ -291,56 +282,13 @@ namespace MewtocolNet {
#region Register Adding #region Register Adding
/// <inheritdoc/> internal void AddRegisters (params BaseRegister[] registers) {
public void AddRegister (IRegister register) => AddRegister(register as BaseRegister);
/// <inheritdoc/> InsertRegistersToMemoryStack(registers.ToList());
public void AddRegister (BaseRegister register) {
if (CheckDuplicateRegister(register))
throw MewtocolException.DupeRegister(register);
if (CheckDuplicateNameRegister(register))
throw MewtocolException.DupeNameRegister(register);
if (CheckOverlappingRegister(register, out var regB))
throw MewtocolException.OverlappingRegister(register, regB);
register.attachedInterface = this;
RegistersUnderlying.Add(register);
} }
// Used for internal property based register building internal void InsertRegistersToMemoryStack (List<BaseRegister> registers) {
internal void AddRegisters (List<RegisterBuildInfo> buildInfos) {
//build all from attribute
List<BaseRegister> registers = new List<BaseRegister>();
foreach (var buildInfo in buildInfos) {
var builtRegister = buildInfo.BuildForCollectionAttribute();
int? linkLen = null;
if(builtRegister is BytesRegister bReg) {
linkLen = (int?)bReg.ReservedBytesSize ?? bReg.ReservedBitSize;
}
//attach the property and collection
builtRegister.WithBoundProperty(new RegisterPropTarget {
BoundProperty = buildInfo.boundPropTarget,
LinkLength = linkLen,
});
builtRegister.WithRegisterCollection(buildInfo.collectionTarget);
builtRegister.attachedInterface = this;
registers.Add(builtRegister);
}
//order by address //order by address
registers = registers.OrderBy(x => x.GetSpecialAddress()).ToList(); registers = registers.OrderBy(x => x.GetSpecialAddress()).ToList();
@@ -353,12 +301,7 @@ namespace MewtocolNet {
reg.name = $"auto_prop_register_{j + 1}"; reg.name = $"auto_prop_register_{j + 1}";
//link the memory area to the register //link the memory area to the register
if (memoryManager.LinkRegister(reg)) { if (memoryManager.LinkRegister(reg)) j++;
RegistersUnderlying.Add(reg);
j++;
}
} }
@@ -423,16 +366,22 @@ namespace MewtocolNet {
#region Register accessing #region Register accessing
/// <inheritdoc/>> /// <inheritdoc/>>
public IRegister GetRegister(string name) { public IRegister GetRegister (string name) {
return RegistersUnderlying.FirstOrDefault(x => x.Name == name); return RegistersInternal.FirstOrDefault(x => x.Name == name);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<IRegister> GetAllRegisters () { public IEnumerable<IRegister> GetAllRegisters () {
return RegistersUnderlying.Cast<IRegister>(); return memoryManager.GetAllRegisters().Cast<IRegister>();
}
internal IEnumerable<BaseRegister> GetAllRegistersInternal () {
return memoryManager.GetAllRegisters();
} }
@@ -442,9 +391,11 @@ namespace MewtocolNet {
private protected void ClearRegisterVals() { private protected void ClearRegisterVals() {
for (int i = 0; i < RegistersUnderlying.Count; i++) { var internals = RegistersInternal.ToList();
var reg = (IRegisterInternal)RegistersUnderlying[i]; for (int i = 0; i < internals.Count; i++) {
var reg = (IRegisterInternal)internals[i];
reg.ClearValue(); reg.ClearValue();
} }
@@ -453,7 +404,7 @@ namespace MewtocolNet {
internal void PropertyRegisterWasSet(string propName, object value) { internal void PropertyRegisterWasSet(string propName, object value) {
_ = SetRegisterAsync(GetRegister(propName), value); throw new NotImplementedException();
} }

View File

@@ -169,6 +169,7 @@ namespace MewtocolNet {
#region Raw register reading / writing #region Raw register reading / writing
[Obsolete]
internal async Task<byte[]> ReadRawRegisterAsync (IRegisterInternal _toRead) { internal async Task<byte[]> ReadRawRegisterAsync (IRegisterInternal _toRead) {
var toreadType = _toRead.GetType(); var toreadType = _toRead.GetType();
@@ -237,6 +238,7 @@ namespace MewtocolNet {
} }
[Obsolete]
internal async Task<bool> WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) { internal async Task<bool> WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) {
var toWriteType = _toWrite.GetType(); var toWriteType = _toWrite.GetType();
@@ -254,36 +256,6 @@ namespace MewtocolNet {
var result = await SendCommandAsync(requeststring); var result = await SendCommandAsync(requeststring);
return result.Success; return result.Success;
}
#endregion
#region Register reading / writing
internal async Task<bool> SetRegisterAsync (IRegister register, object value) {
var internalReg = (IRegisterInternal)register;
return await internalReg.WriteAsync(value);
}
#endregion
#region Reading / Writing Plc program
public async Task GetSystemRegister () {
//the "." means CR or \r
await SendCommandAsync("%EE#RT");
//then get plc status extended? gets polled all time
// %EE#EX00RT00
await SendCommandAsync("%EE#EX00RT00");
} }
#endregion #endregion
@@ -292,8 +264,12 @@ namespace MewtocolNet {
internal string GetStationNumber() { internal string GetStationNumber() {
return StationNumber.ToString().PadLeft(2, '0'); if (StationNumber != 0xEE && StationNumber > 99)
throw new NotSupportedException("Station number was greater 99");
if(StationNumber == 0xEE) return "EE";
return StationNumber.ToString().PadLeft(2, '0');
} }

View File

@@ -81,7 +81,7 @@ namespace MewtocolNet {
} }
/// <inheritdoc/> /// <inheritdoc/>
public void ConfigureConnection (string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 1) { public void ConfigureConnection (string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 0xEE) {
PortName = _portName; PortName = _portName;
SerialBaudRate = _baudRate; SerialBaudRate = _baudRate;
@@ -90,6 +90,9 @@ namespace MewtocolNet {
SerialStopBits = _stopBits; SerialStopBits = _stopBits;
stationNumber = _station; stationNumber = _station;
if (stationNumber != 0xEE && stationNumber > 99)
throw new NotSupportedException("Station number can't be greater than 99");
OnSerialPropsChanged(); OnSerialPropsChanged();
Disconnect(); Disconnect();

View File

@@ -32,11 +32,14 @@ namespace MewtocolNet {
#region TCP connection state handling #region TCP connection state handling
/// <inheritdoc/> /// <inheritdoc/>
public void ConfigureConnection (string ip, int port = 9094, int station = 1) { 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 MewtocolException($"The ip: {ip} is no valid ip address");
if (stationNumber != 0xEE && stationNumber > 99)
throw new NotSupportedException("Station number can't be greater than 99");
Port = port; Port = port;
stationNumber = station; stationNumber = station;
@@ -45,10 +48,14 @@ namespace MewtocolNet {
} }
/// <inheritdoc/> /// <inheritdoc/>
public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 1) { public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 0xEE) {
ipAddr = ip; ipAddr = ip;
Port = port; Port = port;
if (stationNumber != 0xEE && stationNumber > 99)
throw new NotSupportedException("Station number can't be greater than 99");
stationNumber = station; stationNumber = station;
Disconnect(); Disconnect();

View File

@@ -1,20 +0,0 @@
using System;
namespace MewtocolNet.RegisterAttributes {
/// <summary>
/// Defines the behavior of a register property
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class PollFrequencyAttribute : Attribute {
internal int skipEachCycle;
public PollFrequencyAttribute(int eachCycleN) {
skipEachCycle = eachCycleN;
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace MewtocolNet.RegisterAttributes {
/// <summary>
/// Defines the poll level of the register
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class PollLevelAttribute : Attribute {
internal int pollLevel;
public PollLevelAttribute(int level) {
pollLevel = level;
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MewtocolNet.RegisterAttributes {
public class RegisterCollectionCollector {
internal List<RegisterCollection> collections = new List<RegisterCollection>();
public RegisterCollectionCollector AddCollection (RegisterCollection collection) {
collections.Add(collection);
return this;
}
public RegisterCollectionCollector AddCollection<T> () where T : RegisterCollection {
var instance = (RegisterCollection)Activator.CreateInstance(typeof(T));
collections.Add(instance);
return this;
}
}
}

View File

@@ -1,267 +0,0 @@
using MewtocolNet.Registers;
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace MewtocolNet.RegisterBuilding {
public static class BuilderStepExtensions {
public static BuilderStep<T> AsType<T> (this BuilderStepBase baseStep) {
if (!typeof(T).IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting");
}
var castStp = new BuilderStep<T>();
if (baseStep.SpecialAddress != null) castStp.SpecialAddress = baseStep.SpecialAddress;
castStp.Name = baseStep.Name;
castStp.RegType = baseStep.RegType;
castStp.MemAddress = baseStep.MemAddress;
castStp.MemByteSize = baseStep.MemByteSize;
castStp.dotnetVarType = typeof(T);
castStp.plcVarType = null;
castStp.wasCasted = true;
return castStp;
}
public static BuilderStep AsType (this BuilderStepBase baseStep, Type type) {
if (!type.IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting");
}
var castStp = new BuilderStep();
if (baseStep.SpecialAddress != null) castStp.SpecialAddress = baseStep.SpecialAddress;
castStp.Name = baseStep.Name;
castStp.RegType = baseStep.RegType;
castStp.MemAddress = baseStep.MemAddress;
castStp.MemByteSize = baseStep.MemByteSize;
castStp.dotnetVarType = type;
castStp.plcVarType = null;
castStp.wasCasted = true;
return castStp;
}
public static IRegister Build (this BuilderStepBase step) {
//if no casting method in builder was called => autocast the type from the RegisterType
if (!step.wasCasted && step.MemByteSize == null) step.AutoType();
//fallbacks if no casting builder was given
BuilderStepBase.GetFallbackDotnetType(step);
BaseRegister builtReg;
var bInfo = new RegisterBuildInfo {
name = step.Name,
specialAddress = step.SpecialAddress,
memoryAddress = step.MemAddress,
memorySizeBytes = step.MemByteSize,
memorySizeBits = step.MemBitSize,
registerType = step.RegType,
dotnetCastType = step.dotnetVarType,
};
builtReg = bInfo.Build();
BuilderStepBase.AddToRegisterList(step, builtReg);
return builtReg;
}
public static IRegister Build<T>(this BuilderStep<T> step) {
//fallbacks if no casting builder was given
BuilderStepBase.GetFallbackDotnetType(step);
BaseRegister builtReg;
var bInfo = new RegisterBuildInfo {
name = step.Name,
specialAddress = step.SpecialAddress,
memoryAddress = step.MemAddress,
memorySizeBytes = step.MemByteSize,
memorySizeBits = step.MemBitSize,
registerType = step.RegType,
dotnetCastType = step.dotnetVarType,
};
if (step.dotnetVarType.IsEnum) {
builtReg = bInfo.Build();
} else {
builtReg = bInfo.Build();
}
BuilderStepBase.AddToRegisterList(step, builtReg);
return builtReg;
}
}
public abstract class BuilderStepBase {
internal MewtocolInterface forInterface;
internal bool wasCasted = false;
internal string OriginalInput;
internal string Name;
internal RegisterType RegType;
internal uint MemAddress;
internal uint? MemByteSize;
internal ushort? MemBitSize;
internal byte? SpecialAddress;
internal PlcVarType? plcVarType;
internal Type dotnetVarType;
internal BuilderStepBase AutoType() {
switch (RegType) {
case RegisterType.X:
case RegisterType.Y:
case RegisterType.R:
dotnetVarType = typeof(bool);
break;
case RegisterType.DT:
dotnetVarType = typeof(short);
break;
case RegisterType.DDT:
dotnetVarType = typeof(int);
break;
case RegisterType.DT_BYTE_RANGE:
dotnetVarType = typeof(string);
break;
}
plcVarType = null;
wasCasted = true;
return this;
}
internal static void GetFallbackDotnetType (BuilderStepBase step) {
bool isBoolean = step.RegType.IsBoolean();
bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null;
if (isTypeNotDefined && step.RegType == RegisterType.DT) {
step.dotnetVarType = typeof(short);
}
if (isTypeNotDefined && step.RegType == RegisterType.DDT) {
step.dotnetVarType = typeof(int);
} else if (isTypeNotDefined && isBoolean) {
step.dotnetVarType = typeof(bool);
} else if (isTypeNotDefined && step.RegType == RegisterType.DT_BYTE_RANGE) {
step.dotnetVarType = typeof(string);
}
if (step.plcVarType != null) {
step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType();
}
}
internal static void AddToRegisterList(BuilderStepBase step, BaseRegister instance) {
if (step.forInterface == null) return;
step.forInterface.AddRegister(instance);
}
}
public class BuilderStep<T> : BuilderStepBase { }
public class BuilderStep : BuilderStepBase {
public BuilderStep AsPlcType (PlcVarType varType) {
dotnetVarType = null;
plcVarType = varType;
wasCasted = true;
return this;
}
public BuilderStep AsBytes (uint byteLength) {
if (RegType != RegisterType.DT) {
throw new NotSupportedException($"Cant use the {nameof(AsBytes)} converter on a non {nameof(RegisterType.DT)} register");
}
MemByteSize = byteLength;
dotnetVarType = typeof(byte[]);
plcVarType = null;
wasCasted = true;
return this;
}
public BuilderStep AsBits(ushort bitCount = 16) {
if (RegType != RegisterType.DT) {
throw new NotSupportedException($"Cant use the {nameof(AsBits)} converter on a non {nameof(RegisterType.DT)} register");
}
MemBitSize = bitCount;
dotnetVarType = typeof(BitArray);
plcVarType = null;
wasCasted = true;
return this;
}
}
}

View File

@@ -1,13 +0,0 @@
namespace MewtocolNet.RegisterBuilding {
internal struct ParseResult {
public ParseResultState state;
public string hardFailReason;
public BuilderStep stepData;
}
}

View File

@@ -1,19 +0,0 @@
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,609 @@
using MewtocolNet.Exceptions;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using MewtocolNet.UnderlyingRegisters;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
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,
}
/// <summary>
/// Contains useful tools for register creation
/// </summary>
public class RBuild {
private MewtocolInterface attachedPLC;
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
//methods to test the input string on
private static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
(x) => TryBuildBoolean(x),
(x) => TryBuildNumericBased(x),
(x) => TryBuildByteRangeBased(x),
};
internal class SData {
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 pollLevel = 1;
internal RegisterCollection regCollection;
internal PropertyInfo boundProperty;
}
public class SBase {
public SBase() { }
internal SBase(SData data) {
Data = data;
}
internal SData Data { get; set; }
}
internal struct ParseResult {
public ParseResultState state;
public string hardFailReason;
public SData stepData;
}
//bool registers
private static ParseResult TryBuildBoolean(string plcAddrName) {
//regex to find special register values
var patternBool = new Regex(@"(?<prefix>X|Y|R)(?<area>[0-9]{0,3})(?<special>(?:[0-9]|[A-F]){1})?");
var match = patternBool.Match(plcAddrName);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
string special = match.Groups["special"].Value;
IOType regType;
uint areaAdd = 0;
byte specialAdd = 0x0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for boolean registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
//special address not given
if (string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) {
var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt);
if (isAreaInt && areaInt >= 0 && areaInt <= 9) {
//area address is actually meant as special address but 0-9
specialAdd = (byte)areaInt;
areaAdd = 0;
} else if (isAreaInt && areaInt > 9) {
//area adress is meant to be the actual area address
areaAdd = areaInt;
specialAdd = 0;
} else {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 1",
};
}
} else {
//special address parsed as hex num
if (!byte.TryParse(special, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out specialAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 2",
};
}
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new SData {
regType = (RegisterType)(int)regType,
memAddress = areaAdd,
specialAddress = specialAdd,
}
};
}
// one to two word registers
private static ParseResult TryBuildNumericBased(string plcAddrName) {
var patternByte = new Regex(@"^(?<prefix>DT|DDT)(?<area>[0-9]{1,5})$");
var match = patternByte.Match(plcAddrName);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for numeric registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new SData {
regType = regType,
memAddress = areaAdd,
}
};
}
// one to two word registers
private static ParseResult TryBuildByteRangeBased(string plcAddrName) {
var split = plcAddrName.Split('-');
if (split.Length > 2)
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'"
};
uint[] addresses = new uint[2];
for (int i = 0; i < split.Length; i++) {
string addr = split[i];
var patternByte = new Regex(@"(?<prefix>DT|DDT)(?<area>[0-9]{1,5})");
var match = patternByte.Match(addr);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType) || regType != RegisterType.DT) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
addresses[i] = areaAdd;
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new SData {
regType = RegisterType.DT_BYTE_RANGE,
dotnetVarType = typeof(byte[]),
memAddress = addresses[0],
byteSize = (addresses[1] - addresses[0] + 1) * 2
}
};
}
public SAddress Address (string plcAddrName, string name = null) {
foreach (var method in parseMethods) {
var res = method.Invoke(plcAddrName);
if(res.state == ParseResultState.Success) {
if (!string.IsNullOrEmpty(name)) res.stepData.name = name;
res.stepData.originalParseStr = plcAddrName;
unfinishedList.Add(res.stepData);
return new SAddress {
Data = res.stepData
};
} else if(res.state == ParseResultState.FailedHard) {
throw new Exception(res.hardFailReason);
}
}
throw new Exception("Wrong input format");
}
#endregion
#region Type determination stage
public class SAddress : SBase {
public TempRegister<T> AsType<T> () {
if (!typeof(T).IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting");
}
Data.dotnetVarType = typeof(T);
return new TempRegister<T>(Data);
}
public TempRegister AsType (Type type) {
if (!type.IsAllowedPlcCastingType()) {
throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting");
}
Data.dotnetVarType = type;
return new TempRegister(Data);
}
public TempRegister AsBytes (uint byteLength) {
if (Data.regType != RegisterType.DT) {
throw new NotSupportedException($"Cant use the {nameof(AsBytes)} converter on a non {nameof(RegisterType.DT)} register");
}
Data.byteSize = byteLength;
Data.dotnetVarType = typeof(byte[]);
return new TempRegister(Data);
}
public TempRegister AsBits (ushort bitCount = 16) {
if (Data.regType != RegisterType.DT) {
throw new NotSupportedException($"Cant use the {nameof(AsBits)} converter on a non {nameof(RegisterType.DT)} register");
}
Data.bitSize = bitCount;
Data.dotnetVarType = typeof(BitArray);
return new TempRegister(Data);
}
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;
}
return new TempRegister(Data);
}
}
#endregion
#region Options stage
public class TempRegister<T> : SBase {
internal TempRegister(SData data) : base(data) {}
public TempRegister<T> PollLevel (int level) {
Data.pollLevel = level;
return this;
}
public async Task WriteToAsync (T value) => throw new NotImplementedException();
}
public class TempRegister : SBase {
internal TempRegister(SData data) : base(data) { }
public TempRegister PollLevel (int level) {
Data.pollLevel = level;
return this;
}
internal TempRegister RegCollection (RegisterCollection col) {
Data.regCollection = col;
return this;
}
internal TempRegister BoundProp (PropertyInfo prop) {
Data.boundProperty = prop;
return this;
}
public async Task WriteToAsync (object value) => throw new NotImplementedException();
}
#endregion
}
internal class RegisterAssembler {
internal RegisterCollection collectionTarget;
internal MewtocolInterface onInterface;
internal RegisterAssembler (MewtocolInterface interf) {
onInterface = interf;
}
internal List<BaseRegister> Assemble (RBuild rBuildData) {
List<BaseRegister> generatedInstances = new List<BaseRegister>();
foreach (var data in rBuildData.unfinishedList) {
//parse all others where the type is known
Type registerClassType = data.dotnetVarType.GetDefaultRegisterHoldingType();
BaseRegister generatedInstance = null;
if (data.dotnetVarType.IsEnum) {
//-------------------------------------------
//as numeric register with enum target
var underlying = Enum.GetUnderlyingType(data.dotnetVarType);
var enuSize = Marshal.SizeOf(underlying);
if (enuSize > 4)
throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported");
Type myParameterizedSomeClass = typeof(NumberRegister<>).MakeGenericType(data.dotnetVarType);
ConstructorInfo constr = myParameterizedSomeClass.GetConstructor(new Type[] { typeof(uint), typeof(string) });
var parameters = new object[] { data.memAddress, data.name };
var instance = (BaseRegister)constr.Invoke(parameters);
generatedInstance = instance;
} else if (registerClassType.IsGenericType) {
//-------------------------------------------
//as numeric register
//create a new bregister instance
var flags = BindingFlags.Public | 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);
instance.pollLevel = data.pollLevel;
generatedInstance = instance;
} else if (registerClassType == typeof(BytesRegister) && data.byteSize != null) {
//-------------------------------------------
//as byte range register
BytesRegister instance = new BytesRegister(data.memAddress, (uint)data.byteSize, data.name);
generatedInstance = instance;
} else if (registerClassType == typeof(BytesRegister) && data.bitSize != null) {
//-------------------------------------------
//as bit range register
BytesRegister instance = new BytesRegister(data.memAddress, (ushort)data.bitSize, data.name);
generatedInstance = instance;
} else if (registerClassType == typeof(StringRegister)) {
//-------------------------------------------
//as byte range register
var instance = (BaseRegister)new StringRegister(data.memAddress, data.name);
generatedInstance = instance;
} else if (data.regType.IsBoolean()) {
//-------------------------------------------
//as boolean register
var io = (IOType)(int)data.regType;
var spAddr = data.specialAddress;
var areaAddr = data.memAddress;
var instance = new BoolRegister(io, spAddr, areaAddr, data.name);
generatedInstance = instance;
}
//finalize set for every
if(generatedInstance == null)
throw new MewtocolException("Failed to build register");
if (collectionTarget != null)
generatedInstance.WithRegisterCollection(collectionTarget);
generatedInstance.attachedInterface = onInterface;
generatedInstance.pollLevel = data.pollLevel;
generatedInstances.Add(generatedInstance);
}
return generatedInstances;
}
}
}

View File

@@ -1,272 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
namespace MewtocolNet.RegisterBuilding {
/// <summary>
/// Contains useful tools for register creation
/// </summary>
public class RegBuilder {
internal MewtocolInterface forInterface = null;
//methods to test the input string on
private static List<Func<string, ParseResult>> parseMethods = new List<Func<string, ParseResult>>() {
(x) => TryBuildBoolean(x),
(x) => TryBuildNumericBased(x),
(x) => TryBuildByteRangeBased(x),
};
public static RegBuilder ForInterface (IPlc interf) {
var rb = new RegBuilder();
rb.forInterface = interf as MewtocolInterface;
return rb;
}
public static RegBuilder Factory { get; private set; } = new RegBuilder();
public BuilderStep FromPlcRegName (string plcAddrName, string name = null) {
foreach (var method in parseMethods) {
var res = method.Invoke(plcAddrName);
if(res.state == ParseResultState.Success) {
if (!string.IsNullOrEmpty(name))
res.stepData.Name = name;
res.stepData.OriginalInput = plcAddrName;
res.stepData.forInterface = forInterface;
return res.stepData;
} else if(res.state == ParseResultState.FailedHard) {
throw new Exception(res.hardFailReason);
}
}
throw new Exception("Wrong input format");
}
//bool registers
private static ParseResult TryBuildBoolean (string plcAddrName) {
//regex to find special register values
var patternBool = new Regex(@"(?<prefix>X|Y|R)(?<area>[0-9]{0,3})(?<special>(?:[0-9]|[A-F]){1})?");
var match = patternBool.Match(plcAddrName);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
string special = match.Groups["special"].Value;
IOType regType;
uint areaAdd = 0;
byte specialAdd = 0x0;
//try cast the prefix
if(!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for boolean registers"
};
}
if(!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd) ) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
//special address not given
if(string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) {
var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt);
if (isAreaInt && areaInt >= 0 && areaInt <= 9) {
//area address is actually meant as special address but 0-9
specialAdd = (byte)areaInt;
areaAdd = 0;
} else if (isAreaInt && areaInt > 9) {
//area adress is meant to be the actual area address
areaAdd = areaInt;
specialAdd = 0;
} else {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 1",
};
}
} else {
//special address parsed as hex num
if (!byte.TryParse(special, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out specialAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 2",
};
}
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new BuilderStep {
RegType = (RegisterType)(int)regType,
MemAddress = areaAdd,
SpecialAddress = specialAdd,
}
};
}
// one to two word registers
private static ParseResult TryBuildNumericBased (string plcAddrName) {
var patternByte = new Regex(@"^(?<prefix>DT|DDT)(?<area>[0-9]{1,5})$");
var match = patternByte.Match(plcAddrName);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for numeric registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new BuilderStep {
RegType = regType,
MemAddress = areaAdd,
},
};
}
// one to two word registers
private static ParseResult TryBuildByteRangeBased (string plcAddrName) {
var split = plcAddrName.Split('-');
if(split.Length > 2)
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'"
};
uint[] addresses = new uint[2];
for (int i = 0; i < split.Length; i++) {
string addr = split[i];
var patternByte = new Regex(@"(?<prefix>DT|DDT)(?<area>[0-9]{1,5})");
var match = patternByte.Match(addr);
if (!match.Success)
return new ParseResult {
state = ParseResultState.FailedSoft
};
string prefix = match.Groups["prefix"].Value;
string area = match.Groups["area"].Value;
RegisterType regType;
uint areaAdd = 0;
//try cast the prefix
if (!Enum.TryParse(prefix, out regType) || regType != RegisterType.DT) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers"
};
}
if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) {
return new ParseResult {
state = ParseResultState.FailedHard,
hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong"
};
}
addresses[i] = areaAdd;
}
return new ParseResult {
state = ParseResultState.Success,
stepData = new BuilderStep {
RegType = RegisterType.DT_BYTE_RANGE,
dotnetVarType = typeof(byte[]),
MemAddress = addresses[0],
MemByteSize = (addresses[1] - addresses[0] + 1) * 2,
}
};
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
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);
var assembler = new RegisterAssembler((MewtocolInterface)plc);
var registers = assembler.Assemble(regBuilder);
var interf = (MewtocolInterface)plc;
interf.AddRegisters(registers.ToArray());
return plc;
}
}
}

View File

@@ -1,168 +0,0 @@
using MewtocolNet.Exceptions;
using MewtocolNet.RegisterAttributes;
using MewtocolNet.Registers;
using System;
using System.Collections;
using System.Data.Common;
using System.Reflection;
using System.Runtime.InteropServices;
namespace MewtocolNet.RegisterBuilding {
internal struct RegisterBuildInfo {
internal string mewAddress;
internal string name;
internal uint memoryAddress;
internal uint? memorySizeBytes;
internal ushort? memorySizeBits;
internal byte? specialAddress;
internal RegisterType? registerType;
internal Type dotnetCastType;
internal RegisterCollection collectionTarget;
internal PropertyInfo boundPropTarget;
internal BaseRegister BuildForCollectionAttribute () {
return (BaseRegister)RegBuilder.Factory.FromPlcRegName(mewAddress, name).AsType(dotnetCastType).Build();
}
internal BaseRegister Build () {
//parse enums
if (dotnetCastType.IsEnum) {
//-------------------------------------------
//as numeric register with enum target
var underlying = Enum.GetUnderlyingType(dotnetCastType);
var enuSize = Marshal.SizeOf(underlying);
if (enuSize > 4)
throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported");
Type myParameterizedSomeClass = typeof(NumberRegister<>).MakeGenericType(dotnetCastType);
ConstructorInfo constr = myParameterizedSomeClass.GetConstructor(new Type[] { typeof(uint), typeof(string) });
var parameters = new object[] { memoryAddress, name };
var instance = (BaseRegister)constr.Invoke(parameters);
if (collectionTarget != null)
instance.WithRegisterCollection(collectionTarget);
return instance;
}
//parse all others where the type is known
RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault();
Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType();
bool isBoolRegister = regType.IsBoolean();
bool isBytesArrRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister) && dotnetCastType == typeof(byte[]);
bool isBytesBitsRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister) && dotnetCastType == typeof(BitArray);
bool isStringRegister = !registerClassType.IsGenericType && registerClassType == typeof(StringRegister);
bool isNormalNumericResiter = regType.IsNumericDTDDT() && !isBytesArrRegister && !isBytesBitsRegister && !isStringRegister;
if (isNormalNumericResiter) {
//-------------------------------------------
//as numeric register
//create a new bregister instance
var flags = BindingFlags.Public | BindingFlags.Instance;
//int _adress, Type _enumType = null, string _name = null
var parameters = new object[] { memoryAddress, name };
var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null);
if(collectionTarget != null)
instance.WithRegisterCollection(collectionTarget);
return instance;
}
if(isBytesArrRegister) {
//-------------------------------------------
//as byte range register
BytesRegister instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name);
instance.ReservedBytesSize = (ushort)memorySizeBytes.Value;
if (collectionTarget != null)
instance.WithRegisterCollection(collectionTarget);
return instance;
}
if(isBytesBitsRegister) {
//-------------------------------------------
//as bit range register
BytesRegister instance;
if (memorySizeBits != null) {
instance = new BytesRegister(memoryAddress, memorySizeBits.Value, name);
} else {
instance = new BytesRegister(memoryAddress, 16, name);
}
if (collectionTarget != null)
instance.WithRegisterCollection(collectionTarget);
return instance;
}
if (isStringRegister) {
//-------------------------------------------
//as byte range register
var instance = (BaseRegister)new StringRegister(memoryAddress, name);
if (collectionTarget != null)
instance.WithRegisterCollection(collectionTarget);
return instance;
}
if (isBoolRegister) {
//-------------------------------------------
//as boolean register
var io = (IOType)(int)regType;
var spAddr = specialAddress;
var areaAddr = memoryAddress;
var instance = new BoolRegister(io, spAddr.Value, areaAddr, name);
if (collectionTarget != null)
instance.WithRegisterCollection(collectionTarget);
return instance;
}
throw new Exception("Failed to build register");
}
}
}

View File

@@ -25,6 +25,10 @@ namespace MewtocolNet.Registers {
internal object lastValue = null; internal object lastValue = null;
internal string name; internal string name;
internal uint memoryAddress; internal uint memoryAddress;
internal int pollLevel = 0;
internal uint successfulReads = 0;
internal uint successfulWrites = 0;
/// <inheritdoc/> /// <inheritdoc/>
public RegisterCollection ContainedCollection => containedCollection; public RegisterCollection ContainedCollection => containedCollection;
@@ -82,6 +86,10 @@ namespace MewtocolNet.Registers {
public virtual Task<bool> WriteAsync(object data) => throw new NotImplementedException(); public virtual Task<bool> WriteAsync(object data) => throw new NotImplementedException();
internal virtual Task<bool> WriteToAnonymousAsync (object value) => throw new NotImplementedException();
internal virtual Task<object> ReadFromAnonymousAsync () => throw new NotImplementedException();
#endregion #endregion
#region Default accessors #region Default accessors
@@ -129,6 +137,16 @@ namespace MewtocolNet.Registers {
} }
protected virtual void AddSuccessRead () {
if (successfulReads == uint.MaxValue) successfulReads = 0;
else successfulReads++;
}
protected virtual void AddSuccessWrite () {
if (successfulWrites == uint.MaxValue) successfulWrites = 0;
else successfulWrites++;
}
public override string ToString() { public override string ToString() {
var sb = new StringBuilder(); var sb = new StringBuilder();
@@ -148,9 +166,10 @@ namespace MewtocolNet.Registers {
sb.AppendLine($"MewName: {GetMewName()}"); sb.AppendLine($"MewName: {GetMewName()}");
sb.AppendLine($"Name: {Name ?? "Not named"}"); sb.AppendLine($"Name: {Name ?? "Not named"}");
sb.AppendLine($"Value: {GetValueString()}"); sb.AppendLine($"Value: {GetValueString()}");
sb.AppendLine($"Perf. Reads: {successfulReads}, Writes: {successfulWrites}");
sb.AppendLine($"Register Type: {RegisterType}"); sb.AppendLine($"Register Type: {RegisterType}");
sb.AppendLine($"Address: {GetRegisterWordRangeString()}"); sb.AppendLine($"Address: {GetRegisterWordRangeString()}");
if(GetSpecialAddress() != null) sb.AppendLine($"SPAddress: {GetSpecialAddress()}"); if(GetSpecialAddress() != null) sb.AppendLine($"SPAddress: {GetSpecialAddress():X1}");
if (GetType().IsGenericType) sb.AppendLine($"Type: NumberRegister<{GetType().GenericTypeArguments[0]}>"); if (GetType().IsGenericType) sb.AppendLine($"Type: NumberRegister<{GetType().GenericTypeArguments[0]}>");
else sb.AppendLine($"Type: {GetType()}"); else sb.AppendLine($"Type: {GetType()}");
if(containedCollection != null) sb.AppendLine($"In collection: {containedCollection.GetType()}"); if(containedCollection != null) sb.AppendLine($"In collection: {containedCollection.GetType()}");

View File

@@ -112,9 +112,6 @@ namespace MewtocolNet.Registers {
} }
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(false);
/// <inheritdoc/> /// <inheritdoc/>
public override string GetMewName() { public override string GetMewName() {

View File

@@ -117,9 +117,6 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/> /// <inheritdoc/>
public override string GetRegisterString() => "DT"; public override string GetRegisterString() => "DT";
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(null);
/// <inheritdoc/> /// <inheritdoc/>
public override uint GetRegisterAddressLen() => AddressLength; public override uint GetRegisterAddressLen() => AddressLength;
@@ -139,6 +136,8 @@ namespace MewtocolNet.Registers {
internal override object SetValueFromBytes(byte[] bytes) { internal override object SetValueFromBytes(byte[] bytes) {
AddSuccessRead();
object parsed; object parsed;
if (ReservedBitSize != null) { if (ReservedBitSize != null) {
parsed = PlcValueParser.Parse<BitArray>(this, bytes); parsed = PlcValueParser.Parse<BitArray>(this, bytes);
@@ -166,6 +165,7 @@ namespace MewtocolNet.Registers {
var res = await underlyingMemory.WriteRegisterAsync(this, encoded); var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
if (res) { if (res) {
AddSuccessWrite();
SetValueFromPLC(data); SetValueFromPLC(data);
} }

View File

@@ -7,6 +7,7 @@ using System.ComponentModel;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using static MewtocolNet.RegisterBuilding.RBuild;
namespace MewtocolNet.Registers { namespace MewtocolNet.Registers {
@@ -67,7 +68,8 @@ namespace MewtocolNet.Registers {
if (lastValue?.ToString() != val?.ToString()) { if (lastValue?.ToString() != val?.ToString()) {
lastValue = (T)val; if (val != null) lastValue = (T)val;
else lastValue = null;
TriggerNotifyChange(); TriggerNotifyChange();
attachedInterface.InvokeRegisterChanged(this); attachedInterface.InvokeRegisterChanged(this);
@@ -122,9 +124,6 @@ namespace MewtocolNet.Registers {
} }
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC(default(T));
/// <inheritdoc/> /// <inheritdoc/>
public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2); public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2);
@@ -144,6 +143,8 @@ namespace MewtocolNet.Registers {
internal override object SetValueFromBytes(byte[] bytes) { internal override object SetValueFromBytes(byte[] bytes) {
AddSuccessRead();
var parsed = PlcValueParser.Parse<T>(this, bytes); var parsed = PlcValueParser.Parse<T>(this, bytes);
SetValueFromPLC(parsed); SetValueFromPLC(parsed);
return parsed; return parsed;
@@ -159,6 +160,7 @@ namespace MewtocolNet.Registers {
var res = await underlyingMemory.WriteRegisterAsync(this, encoded); var res = await underlyingMemory.WriteRegisterAsync(this, encoded);
if (res) { if (res) {
AddSuccessWrite();
SetValueFromPLC(data); SetValueFromPLC(data);
} }
@@ -166,29 +168,25 @@ namespace MewtocolNet.Registers {
} }
/// <summary> internal override async Task<bool> WriteToAnonymousAsync (object value) {
/// Gets the register bitwise if its a 16 or 32 bit int
/// </summary>
/// <returns>A bitarray</returns>
public BitArray GetBitwise() {
if (this is NumberRegister<short> shortReg) { if (!attachedInterface.IsConnected)
throw MewtocolException.NotConnectedSend();
var bytes = BitConverter.GetBytes((short)Value); var encoded = PlcValueParser.Encode(this, (T)value);
BitArray bitAr = new BitArray(bytes); return await attachedInterface.WriteByteRange((int)MemoryAddress, encoded);
return bitAr;
} }
if (this is NumberRegister<int> intReg) { internal override async Task<object> ReadFromAnonymousAsync () {
var bytes = BitConverter.GetBytes((int)Value); if (!attachedInterface.IsConnected)
BitArray bitAr = new BitArray(bytes); throw MewtocolException.NotConnectedSend();
return bitAr;
} var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2);
if (res == null) return null;
return null; return PlcValueParser.Parse<T>(this, res);
} }

View File

@@ -69,9 +69,6 @@ namespace MewtocolNet.Registers {
/// <inheritdoc/> /// <inheritdoc/>
public override string GetRegisterString() => "DT"; public override string GetRegisterString() => "DT";
/// <inheritdoc/>
public override void ClearValue() => SetValueFromPLC("");
/// <inheritdoc/> /// <inheritdoc/>
public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize); public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize);

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace MewtocolNet.SetupClasses {
internal class PollLevelConfig {
internal TimeSpan? delay;
internal int? skipNth;
internal Stopwatch timeFromLastRead;
}
}

View File

@@ -47,6 +47,20 @@ namespace MewtocolNet.UnderlyingRegisters {
} }
public void UpdateAreaRegisterValues() {
foreach (var register in this.linkedRegisters) {
var regStart = register.MemoryAddress;
var addLen = (int)register.GetRegisterAddressLen();
var bytes = this.GetUnderlyingBytes(regStart, addLen);
register.SetValueFromBytes(bytes);
}
}
public async Task<bool> ReadRegisterAsync (BaseRegister reg) { public async Task<bool> ReadRegisterAsync (BaseRegister reg) {
return await RequestByteReadAsync(reg.MemoryAddress, reg.MemoryAddress + reg.GetRegisterAddressLen() - 1); return await RequestByteReadAsync(reg.MemoryAddress, reg.MemoryAddress + reg.GetRegisterAddressLen() - 1);
@@ -125,6 +139,8 @@ namespace MewtocolNet.UnderlyingRegisters {
int copyOffset = (int)((addStart - addressStart) * 2); int copyOffset = (int)((addStart - addressStart) * 2);
bytes.CopyTo(underlyingBytes, copyOffset); bytes.CopyTo(underlyingBytes, copyOffset);
UpdateAreaRegisterValues();
} }
private string GetMewtocolIdent () { private string GetMewtocolIdent () {

View File

@@ -11,6 +11,8 @@ namespace MewtocolNet.UnderlyingRegisters {
Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes); Task<bool> WriteRegisterAsync(BaseRegister reg, byte[] bytes);
void UpdateAreaRegisterValues();
} }
} }

View File

@@ -1,6 +1,8 @@
using MewtocolNet.Registers; using MewtocolNet.Registers;
using MewtocolNet.SetupClasses;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -11,22 +13,16 @@ namespace MewtocolNet.UnderlyingRegisters {
internal int maxOptimizationDistance = 8; internal int maxOptimizationDistance = 8;
internal int maxRegistersPerGroup = -1; internal int maxRegistersPerGroup = -1;
internal bool allowByteRegDupes;
private int wrAreaSize;
private int dtAreaSize;
internal MewtocolInterface mewInterface; internal MewtocolInterface mewInterface;
internal List<PollLevel> pollLevels;
internal Dictionary<int, PollLevelConfig> pollLevelConfigs = new Dictionary<int, PollLevelConfig>();
// WR areas are n of words, each word has 2 bytes representing the "special address component" private uint pollIteration = 0;
//X WR
internal List<WRArea> externalRelayInAreas;
//Y WR
internal List<WRArea> externalRelayOutAreas;
//R WR
internal List<WRArea> internalRelayAreas;
//DT
internal List<DTArea> dataAreas;
internal MemoryAreaManager (MewtocolInterface mewIf, int wrSize = 512, int dtSize = 32_765) { internal MemoryAreaManager (MewtocolInterface mewIf, int wrSize = 512, int dtSize = 32_765) {
@@ -38,22 +34,25 @@ namespace MewtocolNet.UnderlyingRegisters {
// Later on pass memory area sizes here // Later on pass memory area sizes here
internal void Setup (int wrSize, int dtSize) { internal void Setup (int wrSize, int dtSize) {
externalRelayInAreas = new List<WRArea>(wrSize * 16); wrAreaSize = wrSize;
externalRelayOutAreas = new List<WRArea>(wrSize * 16); dtAreaSize = dtSize;
internalRelayAreas = new List<WRArea>(wrSize * 16); pollLevels = new List<PollLevel> {
dataAreas = new List<DTArea>(dtSize); new PollLevel(wrSize, dtSize) {
level = 1,
}
};
} }
internal bool LinkRegister (BaseRegister reg) { internal bool LinkRegister (BaseRegister reg) {
TestPollLevelExistence(reg);
switch (reg.RegisterType) { switch (reg.RegisterType) {
case RegisterType.X: case RegisterType.X:
return AddWRArea(reg, externalRelayInAreas);
case RegisterType.Y: case RegisterType.Y:
return AddWRArea(reg, externalRelayOutAreas);
case RegisterType.R: case RegisterType.R:
return AddWRArea(reg, internalRelayAreas); return AddWRArea(reg);
case RegisterType.DT: case RegisterType.DT:
case RegisterType.DDT: case RegisterType.DDT:
case RegisterType.DT_BYTE_RANGE: case RegisterType.DT_BYTE_RANGE:
@@ -64,7 +63,48 @@ namespace MewtocolNet.UnderlyingRegisters {
} }
private bool AddWRArea (BaseRegister insertReg, List<WRArea> collection) { private void TestPollLevelExistence (BaseRegister reg) {
if(!pollLevelConfigs.ContainsKey(1)) {
pollLevelConfigs.Add(1, new PollLevelConfig {
skipNth = 1,
});
}
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)) {
pollLevelConfigs.Add(reg.pollLevel, new PollLevelConfig {
skipNth = reg.pollLevel,
});
}
}
}
private bool AddWRArea (BaseRegister insertReg) {
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
List<WRArea> collection = null;
switch (insertReg.RegisterType) {
case RegisterType.X:
collection = pollLevelFound.externalRelayInAreas;
break;
case RegisterType.Y:
collection = pollLevelFound.externalRelayOutAreas;
break;
case RegisterType.R:
collection = pollLevelFound.internalRelayAreas;
break;
}
WRArea area = collection.FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress); WRArea area = collection.FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress);
@@ -114,6 +154,9 @@ namespace MewtocolNet.UnderlyingRegisters {
DTArea targetArea = null; DTArea targetArea = null;
var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel);
var dataAreas = pollLevelFound.dataAreas;
foreach (var dtArea in dataAreas) { foreach (var dtArea in dataAreas) {
bool matchingAddress = regInsAddStart >= dtArea.AddressStart && bool matchingAddress = regInsAddStart >= dtArea.AddressStart &&
@@ -123,9 +166,10 @@ namespace MewtocolNet.UnderlyingRegisters {
if (matchingAddress) { if (matchingAddress) {
//check if the area has registers linked that are overlapping (not matching) //check if the area has registers linked that are overlapping (not matching)
var foundDupe = dtArea.linkedRegisters.FirstOrDefault(x => x.CompareIsDuplicateNonCast(insertReg)); var foundDupe = dtArea.linkedRegisters
.FirstOrDefault(x => x.CompareIsDuplicateNonCast(insertReg, allowByteRegDupes));
if(foundDupe != null) { if (foundDupe != null) {
throw new NotSupportedException( throw new NotSupportedException(
message: $"Can't have registers of different types at the same referenced plc address: " + message: $"Can't have registers of different types at the same referenced plc address: " +
$"{insertReg.PLCAddressName} ({insertReg.GetType()}) <=> " + $"{insertReg.PLCAddressName} ({insertReg.GetType()}) <=> " +
@@ -211,34 +255,60 @@ namespace MewtocolNet.UnderlyingRegisters {
internal async Task PollAllAreasAsync () { internal async Task PollAllAreasAsync () {
foreach (var dtArea in dataAreas) { foreach (var pollLevel in pollLevels) {
//set the whole memory area at once var sw = Stopwatch.StartNew();
var res = await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
foreach (var register in dtArea.linkedRegisters) { //determine to skip poll levels, first iteration is always polled
if(pollIteration > 0 && pollLevel.level > 1) {
var regStart = register.MemoryAddress; var lvlConfig = pollLevelConfigs[pollLevel.level];
var addLen = (int)register.GetRegisterAddressLen(); var skipIterations = lvlConfig.skipNth;
var skipDelay = lvlConfig.delay;
var bytes = dtArea.GetUnderlyingBytes(regStart, addLen); if (skipIterations != null && pollIteration % skipIterations.Value != 0) {
register.SetValueFromBytes(bytes);
//count delayed poll skips
continue;
} else if(skipDelay != null) {
//time delayed poll skips
if(lvlConfig.timeFromLastRead.Elapsed < skipDelay.Value) {
continue;
}
}
} }
//set stopwatch for levels
if(pollLevelConfigs.ContainsKey(pollLevel.level)) {
pollLevelConfigs[pollLevel.level].timeFromLastRead = Stopwatch.StartNew();
}
//update registers in poll level
foreach (var dtArea in pollLevel.dataAreas) {
//set the whole memory area at once
await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
}
pollLevel.lastReadTimeMs = (int)sw.Elapsed.TotalMilliseconds;
} }
} if(pollIteration == uint.MaxValue) {
pollIteration = uint.MinValue;
internal void Merge () { } else {
pollIteration++;
//merge gaps that the algorithm didn't catch be rerunning the register attachment }
var allDataAreaRegisters = dataAreas.SelectMany(x => x.linkedRegisters).ToList();
dataAreas = new List<DTArea>(allDataAreaRegisters.Capacity);
foreach (var reg in allDataAreaRegisters)
AddDTArea(reg);
} }
@@ -246,63 +316,77 @@ namespace MewtocolNet.UnderlyingRegisters {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("---- DT Area ----"); foreach (var pollLevel in pollLevels) {
sb.AppendLine($"Optimization distance: {maxOptimizationDistance}"); sb.AppendLine($"==== Poll lvl {pollLevel.level} ====");
foreach (var area in dataAreas) {
sb.AppendLine(); sb.AppendLine();
sb.AppendLine($"=> {area} = {area.underlyingBytes.Length} bytes"); if (pollLevelConfigs[pollLevel.level].delay != null) {
sb.AppendLine(); sb.AppendLine($"Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms");
sb.AppendLine(string.Join("\n", area.underlyingBytes.ToHexString(" ").SplitInParts(3 * 8))); } else {
sb.AppendLine($"Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations");
}
sb.AppendLine($"Level read time: {pollLevel.lastReadTimeMs}ms");
sb.AppendLine($"Optimization distance: {maxOptimizationDistance}");
sb.AppendLine(); sb.AppendLine();
foreach (var reg in area.linkedRegisters) { sb.AppendLine($"---- DT Area ----");
sb.AppendLine($"{reg.ToString(true)}"); foreach (var area in pollLevel.dataAreas) {
sb.AppendLine();
sb.AppendLine($"=> {area} = {area.underlyingBytes.Length} bytes");
sb.AppendLine();
sb.AppendLine(string.Join("\n", area.underlyingBytes.ToHexString(" ").SplitInParts(3 * 8)));
sb.AppendLine();
foreach (var reg in area.linkedRegisters) {
sb.AppendLine($"{reg.ToString(true)}");
}
} }
} sb.AppendLine($"---- WR X Area ----");
sb.AppendLine("---- WR X Area ----"); foreach (var area in pollLevel.externalRelayInAreas) {
foreach (var area in externalRelayInAreas) { sb.AppendLine(area.ToString());
sb.AppendLine(area.ToString()); foreach (var reg in area.linkedRegisters) {
foreach (var reg in area.linkedRegisters) { sb.AppendLine($"{reg.ToString(true)}");
sb.AppendLine($"{reg.ToString(true)}"); }
} }
} sb.AppendLine($"---- WR Y Area ---");
sb.AppendLine("---- WR Y Area ----"); foreach (var area in pollLevel.externalRelayOutAreas) {
foreach (var area in externalRelayOutAreas) { sb.AppendLine(area.ToString());
sb.AppendLine(area.ToString()); foreach (var reg in area.linkedRegisters) {
foreach (var reg in area.linkedRegisters) { sb.AppendLine($"{reg.ToString(true)}");
sb.AppendLine($"{reg.ToString(true)}"); }
} }
} sb.AppendLine($"---- WR R Area ----");
sb.AppendLine("---- WR R Area ----"); foreach (var area in pollLevel.internalRelayAreas) {
foreach (var area in internalRelayAreas) { sb.AppendLine(area.ToString());
sb.AppendLine(area.ToString()); foreach (var reg in area.linkedRegisters) {
foreach (var reg in area.linkedRegisters) { sb.AppendLine($"{reg.ToString(true)}");
sb.AppendLine($"{reg.ToString(true)}"); }
} }
@@ -312,6 +396,22 @@ namespace MewtocolNet.UnderlyingRegisters {
} }
internal IEnumerable<BaseRegister> GetAllRegisters () {
List<BaseRegister> registers = new List<BaseRegister>();
foreach (var lvl in pollLevels) {
registers.AddRange(lvl.dataAreas.SelectMany(x => x.linkedRegisters));
registers.AddRange(lvl.internalRelayAreas.SelectMany(x => x.linkedRegisters));
registers.AddRange(lvl.externalRelayInAreas.SelectMany(x => x.linkedRegisters));
registers.AddRange(lvl.externalRelayOutAreas.SelectMany(x => x.linkedRegisters));
}
return registers;
}
} }

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
namespace MewtocolNet.UnderlyingRegisters {
internal class PollLevel {
internal int lastReadTimeMs = 0;
internal PollLevel (int wrSize, int dtSize) {
externalRelayInAreas = new List<WRArea>(wrSize * 16);
externalRelayOutAreas = new List<WRArea>(wrSize * 16);
internalRelayAreas = new List<WRArea>(wrSize * 16);
dataAreas = new List<DTArea>(dtSize);
}
internal int level;
// WR areas are n of words, each word has 2 bytes representing the "special address component"
//X WR
internal List<WRArea> externalRelayInAreas;
//Y WR
internal List<WRArea> externalRelayOutAreas;
//R WR
internal List<WRArea> internalRelayAreas;
//DT
internal List<DTArea> dataAreas;
}
}

View File

@@ -23,6 +23,12 @@ namespace MewtocolNet.UnderlyingRegisters {
mewInterface = mewIf; mewInterface = mewIf;
}
public void UpdateAreaRegisterValues () {
} }
public byte[] GetUnderlyingBytes(BaseRegister reg) { public byte[] GetUnderlyingBytes(BaseRegister reg) {