diff --git a/MewtocolNet/Exceptions/MewtocolException.cs b/MewtocolNet/Exceptions/MewtocolException.cs index 445fd78..8709822 100644 --- a/MewtocolNet/Exceptions/MewtocolException.cs +++ b/MewtocolNet/Exceptions/MewtocolException.cs @@ -17,6 +17,12 @@ namespace MewtocolNet.Exceptions { 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 (IRegisterInternal register) { 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) { - throw new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " + + return new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " + $"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}"); } diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index f27f1ff..2191b05 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -59,7 +59,7 @@ namespace MewtocolNet { if (_onString == 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) { string val = res.Groups[2].Value; return val; @@ -75,7 +75,7 @@ namespace MewtocolNet { _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) { string val = res.Groups[2].Value; return val == "1"; @@ -91,7 +91,7 @@ namespace MewtocolNet { _onString = _onString.Replace("\r", ""); - var res = new Regex(@"\%([0-9]{2})\$RC(?(?:0|1){0,8})(..)").Match(_onString); + var res = new Regex(@"\%([0-9a-fA-F]{2})\$RC(?(?:0|1){0,8})(..)").Match(_onString); if (res.Success) { string val = res.Groups["bits"].Value; @@ -121,7 +121,7 @@ namespace MewtocolNet { _onString = _onString.Replace("\r", ""); - var res = new Regex(@"\%([0-9]{2})\$RD(?.*)(?..)").Match(_onString); + var res = new Regex(@"\%([0-9a-fA-F]{2})\$RD(?.*)(?..)").Match(_onString); if (res.Success) { string val = res.Groups["data"].Value; @@ -245,16 +245,20 @@ namespace MewtocolNet { bool valCompare = reg1.RegisterType == compare.RegisterType && reg1.MemoryAddress == compare.MemoryAddress && + reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() && reg1.GetSpecialAddress() == compare.GetSpecialAddress(); 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() && reg1.MemoryAddress == compare.MemoryAddress && + reg1.GetRegisterAddressLen() == compare.GetRegisterAddressLen() && reg1.GetSpecialAddress() == compare.GetSpecialAddress(); return valCompare; diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index cfa3fcb..1582f32 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -1,6 +1,8 @@ -using MewtocolNet.Registers; +using MewtocolNet.RegisterBuilding; +using MewtocolNet.Registers; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; namespace MewtocolNet { @@ -8,7 +10,7 @@ namespace MewtocolNet { /// /// Provides a interface for Panasonic PLCs /// - public interface IPlc : IDisposable { + public interface IPlc : IDisposable, INotifyPropertyChanged { /// /// The current connection state of the interface @@ -35,11 +37,6 @@ namespace MewtocolNet { /// int QueuedMessages { get; } - /// - /// The registered data registers of the PLC - /// - IEnumerable Registers { get; } - /// /// Generic information about the connected PLC /// @@ -60,13 +57,24 @@ namespace MewtocolNet { /// int ConnectTimeout { get; set; } + /// + /// Provides an anonymous interface for register reading and writing without memory management + /// + RBuild Register { get; } + /// /// Tries to establish a connection with the device asynchronously /// Task ConnectAsync(); /// - /// Disconnects the devive from its current connection + /// Disconnects the device from its current plc connection + /// and awaits the end of all asociated tasks + /// + Task DisconnectAsync(); + + /// + /// Disconnects the device from its current plc connection /// void Disconnect(); @@ -97,23 +105,13 @@ namespace MewtocolNet { /// useful if you want to use a custom update frequency /// /// The number of inidvidual mewtocol commands sent - Task RunPollerCylceManual(); + Task RunPollerCylceManualAsync(); /// /// Gets the connection info string /// string GetConnectionInfo(); - /// - /// Adds a register to the plc - /// - void AddRegister(BaseRegister register); - - /// - /// Adds a register to the plc - /// - void AddRegister(IRegister register); - /// /// Gets a register from the plc by name /// diff --git a/MewtocolNet/Mewtocol.cs b/MewtocolNet/Mewtocol.cs index f9d34c1..ada7bc7 100644 --- a/MewtocolNet/Mewtocol.cs +++ b/MewtocolNet/Mewtocol.cs @@ -1,5 +1,6 @@ using MewtocolNet.Exceptions; using MewtocolNet.RegisterAttributes; +using MewtocolNet.SetupClasses; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -14,15 +15,17 @@ namespace MewtocolNet { /// Builder helper for mewtocol interfaces /// public static class Mewtocol { - + + #region Build Order 1 + /// /// Builds a ethernet based Mewtocol Interface /// /// /// - /// Plc station number + /// Plc station number 0xEE for direct communication /// - public static PostInit Ethernet (string ip, int port = 9094, int station = 1) { + public static PostInit Ethernet (string ip, int port = 9094, int station = 0xEE) { var instance = new MewtocolInterfaceTcp(); instance.ConfigureConnection(ip, port, station); @@ -37,9 +40,9 @@ namespace MewtocolNet { /// /// /// - /// Plc station number + /// Plc station number 0xEE for direct communication /// - public static PostInit Ethernet(IPAddress ip, int port = 9094, int station = 1) { + public static PostInit Ethernet(IPAddress ip, int port = 9094, int station = 0xEE) { var instance = new MewtocolInterfaceTcp(); instance.ConfigureConnection(ip, port, station); @@ -52,14 +55,14 @@ namespace MewtocolNet { /// /// Builds a serial port based Mewtocol Interface /// - /// - /// - /// - /// - /// - /// + /// System port name + /// Baud rate of the plc toolport + /// DataBits of the plc toolport + /// Parity rate of the plc toolport + /// Stop bits of the plc toolport + /// Plc station number 0xEE for direct communication /// - public static PostInit Serial (string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 1) { + public static PostInit Serial (string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 0xEE) { TestPortName(portName); @@ -75,9 +78,9 @@ namespace MewtocolNet { /// Builds a serial mewtocol interface that finds the correct settings for the given port name automatically /// /// - /// + /// Plc station number 0xEE for direct communication /// - public static PostInit SerialAuto (string portName, int station = 1) { + public static PostInit SerialAuto (string portName, int station = 0xEE) { TestPortName(portName); @@ -99,6 +102,10 @@ namespace MewtocolNet { } + #endregion + + #region Build Order 2 + public class MemoryManagerSettings { /// @@ -125,6 +132,80 @@ namespace MewtocolNet { /// public int MaxRegistersPerGroup { get; set; } = -1; + /// + /// Wether or not to throw an exception when a byte array overlap or duplicate is detected + /// + public bool AllowByteRegisterDupes { get; set; } = false; + + } + + public class PollLevelConfigurator { + + internal Dictionary levelConfigs = new Dictionary(); + + /// + /// Sets the poll level for the given key + /// + /// The level to reference + /// Delay between poll requests + 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 collections = new List(); + + public RegCollector AddCollection(RegisterCollection collection) { + + collections.Add(collection); + + return this; + + } + + public RegCollector AddCollection() where T : RegisterCollection { + + var instance = (RegisterCollection)Activator.CreateInstance(typeof(T)); + + collections.Add(instance); + + return this; + + } + } public class PostInit { @@ -162,6 +243,25 @@ namespace MewtocolNet { imew.memoryManager.maxOptimizationDistance = res.MaxOptimizationDistance; imew.memoryManager.maxRegistersPerGroup = res.MaxRegistersPerGroup; + imew.memoryManager.allowByteRegDupes = res.AllowByteRegisterDupes; + + } + + return this; + + } + + /// + /// A builder for poll custom levels + /// + public PostInit WithCustomPollLevels (Action levels) { + + var res = new PollLevelConfigurator(); + levels.Invoke(res); + + if (intf is MewtocolInterface imew) { + + imew.memoryManager.pollLevelConfigs = res.levelConfigs; } @@ -172,9 +272,9 @@ namespace MewtocolNet { /// /// A builder for attaching register collections /// - public EndInit WithRegisterCollections(Action collector) { + public EndInit WithRegisterCollections(Action collector) { - var res = new RegisterCollectionCollector(); + var res = new RegCollector(); collector.Invoke(res); if (intf is MewtocolInterface imew) { @@ -194,6 +294,10 @@ namespace MewtocolNet { } + #endregion + + #region BuildLevel 3 + public class EndInit { internal PostInit postInit; @@ -205,6 +309,8 @@ namespace MewtocolNet { } + #endregion + } } diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 0aa006f..be48263 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -15,7 +15,7 @@ using MewtocolNet.UnderlyingRegisters; namespace MewtocolNet { - public abstract partial class MewtocolInterface : IPlc, INotifyPropertyChanged, IDisposable { + public abstract partial class MewtocolInterface : IPlc { #region Private fields @@ -28,6 +28,7 @@ namespace MewtocolNet { private PLCInfo plcInfo; private protected int stationNumber; + private protected int RecBufferSize = 128; private protected int bytesTotalCountedUpstream = 0; private protected int bytesTotalCountedDownstream = 0; private protected int cycleTimeMs = 25; @@ -35,7 +36,6 @@ namespace MewtocolNet { private protected int bytesPerSecondDownstream = 0; private protected AsyncQueue queue = new AsyncQueue(); - private protected int RecBufferSize = 128; private protected Stopwatch speedStopwatchUpstr; private protected Stopwatch speedStopwatchDownstr; private protected Task firstPollTask = new Task(() => { }); @@ -52,9 +52,6 @@ namespace MewtocolNet { internal bool usePoller = false; internal MemoryAreaManager memoryManager; - internal List RegistersUnderlying { get; private set; } = new List(); - internal IEnumerable RegistersInternal => RegistersUnderlying.Cast(); - #endregion #region Public Read Only Properties / Fields @@ -95,9 +92,6 @@ namespace MewtocolNet { } } - /// - public IEnumerable Registers => RegistersUnderlying.Cast(); - /// public int StationNumber => stationNumber; @@ -173,18 +167,26 @@ namespace MewtocolNet { } /// - public virtual async Task ConnectAsync() => throw new NotImplementedException(); + public virtual Task ConnectAsync() => throw new NotImplementedException(); /// public async Task AwaitFirstDataCycleAsync() => await firstPollTask; + /// + public async Task DisconnectAsync () { + + await pollCycleTask; + + Disconnect(); + + } + /// public void Disconnect() { if (!IsConnected) return; - if(pollCycleTask != null && !pollCycleTask.IsCompleted) - pollCycleTask.Wait(); + if (!pollCycleTask.IsCompleted) pollCycleTask.Wait(); OnMajorSocketExceptionWhileConnected(); @@ -194,7 +196,9 @@ namespace MewtocolNet { public void Dispose() { if (Disposed) return; + Disconnect(); + //GC.SuppressFinalize(this); Disposed = true; @@ -329,7 +333,7 @@ namespace MewtocolNet { SetDownstreamStopWatchStart(); - byte[] buffer = new byte[128]; + byte[] buffer = new byte[RecBufferSize]; int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); CalcDownstreamSpeed(bytesRead); diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index fa19a1b..3a37c65 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -3,6 +3,7 @@ using MewtocolNet.Logging; using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterBuilding; using MewtocolNet.Registers; +using MewtocolNet.SetupClasses; using MewtocolNet.UnderlyingRegisters; using System; using System.Collections; @@ -24,14 +25,18 @@ namespace MewtocolNet { internal Task pollCycleTask; + private List registerCollections = new List(); + + internal IEnumerable RegistersInternal => GetAllRegistersInternal(); + + public IEnumerable Registers => GetAllRegisters(); + /// /// True if the poller is actvice (can be paused) /// public bool PollerActive => !pollerTaskStopped; - /// - /// Current poller cycle duration - /// + /// public int PollerCycleDurationMs { get => pollerCycleDurationMs; private set { @@ -40,7 +45,8 @@ namespace MewtocolNet { } } - private List registerCollections = new List(); + /// + public RBuild Register => new RBuild(this); #region Register Polling @@ -74,11 +80,11 @@ namespace MewtocolNet { /// useful if you want to use a custom update frequency /// /// The number of inidvidual mewtocol commands sent - public async Task RunPollerCylceManual() { + public async Task RunPollerCylceManualAsync() { if (!pollerTaskStopped) 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; @@ -139,69 +145,46 @@ namespace MewtocolNet { private async Task UpdateRCPRegisters() { //build booleans - var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister)) - .Select(x => (BoolRegister)x) - .ToArray(); + //var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister)) + // .Select(x => (BoolRegister)x) + // .ToArray(); - //one frame can only read 8 registers at a time - int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8); - int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8; + ////one frame can only read 8 registers at a time + //int rcpFrameCount = (int)Math.Ceiling((double)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]; - rcpString.Append(register.BuildMewtocolQuery()); + // BoolRegister register = rcpList[i + j]; + // rcpString.Append(register.BuildMewtocolQuery()); - } + // } - string rcpRequest = rcpString.ToString(); - var result = await SendCommandAsync(rcpRequest); - if (!result.Success) return; + // string rcpRequest = rcpString.ToString(); + // var result = await SendCommandAsync(rcpRequest); + // 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]) { - register.SetValueFromPLC(resultBitArray[k]); - } + // if ((bool)register.Value != 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) throw new NotSupportedException("Register collections can only be build once"); - List buildInfos = new List(); + var regBuild = RBuild.Factory; foreach (var collection in collections) { @@ -234,18 +217,24 @@ namespace MewtocolNet { if (attr is RegisterAttribute cAttribute) { + var pollFreqAttr = (PollLevelAttribute)attributes.FirstOrDefault(x => x.GetType() == typeof(PollLevelAttribute)); + if (!prop.PropertyType.IsAllowedPlcCastingType()) { throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})"); } var dotnetType = prop.PropertyType; + int pollLevel = 1; - buildInfos.Add(new RegisterBuildInfo { - mewAddress = cAttribute.MewAddress, - dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType, - collectionTarget = collection, - boundPropTarget = prop, - }); + if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel; + + //add builder item + regBuild + .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 - /// - public void AddRegister (IRegister register) => AddRegister(register as BaseRegister); + internal void AddRegisters (params BaseRegister[] registers) { - /// - 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); + InsertRegistersToMemoryStack(registers.ToList()); } - // Used for internal property based register building - internal void AddRegisters (List buildInfos) { - - //build all from attribute - List registers = new List(); - - 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); - - } + internal void InsertRegistersToMemoryStack (List registers) { //order by address registers = registers.OrderBy(x => x.GetSpecialAddress()).ToList(); @@ -353,12 +301,7 @@ namespace MewtocolNet { reg.name = $"auto_prop_register_{j + 1}"; //link the memory area to the register - if (memoryManager.LinkRegister(reg)) { - - RegistersUnderlying.Add(reg); - j++; - - } + if (memoryManager.LinkRegister(reg)) j++; } @@ -423,16 +366,22 @@ namespace MewtocolNet { #region Register accessing /// > - public IRegister GetRegister(string name) { + public IRegister GetRegister (string name) { - return RegistersUnderlying.FirstOrDefault(x => x.Name == name); + return RegistersInternal.FirstOrDefault(x => x.Name == name); } /// public IEnumerable GetAllRegisters () { - return RegistersUnderlying.Cast(); + return memoryManager.GetAllRegisters().Cast(); + + } + + internal IEnumerable GetAllRegistersInternal () { + + return memoryManager.GetAllRegisters(); } @@ -442,9 +391,11 @@ namespace MewtocolNet { 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(); } @@ -453,7 +404,7 @@ namespace MewtocolNet { internal void PropertyRegisterWasSet(string propName, object value) { - _ = SetRegisterAsync(GetRegister(propName), value); + throw new NotImplementedException(); } diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 7dadf6a..4d562a8 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -169,6 +169,7 @@ namespace MewtocolNet { #region Raw register reading / writing + [Obsolete] internal async Task ReadRawRegisterAsync (IRegisterInternal _toRead) { var toreadType = _toRead.GetType(); @@ -237,6 +238,7 @@ namespace MewtocolNet { } + [Obsolete] internal async Task WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) { var toWriteType = _toWrite.GetType(); @@ -254,36 +256,6 @@ namespace MewtocolNet { var result = await SendCommandAsync(requeststring); return result.Success; - } - - #endregion - - #region Register reading / writing - - internal async Task 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 @@ -292,8 +264,12 @@ namespace MewtocolNet { 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'); } diff --git a/MewtocolNet/MewtocolInterfaceSerial.cs b/MewtocolNet/MewtocolInterfaceSerial.cs index 008d385..35684a8 100644 --- a/MewtocolNet/MewtocolInterfaceSerial.cs +++ b/MewtocolNet/MewtocolInterfaceSerial.cs @@ -81,7 +81,7 @@ namespace MewtocolNet { } /// - 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; SerialBaudRate = _baudRate; @@ -90,6 +90,9 @@ namespace MewtocolNet { SerialStopBits = _stopBits; stationNumber = _station; + if (stationNumber != 0xEE && stationNumber > 99) + throw new NotSupportedException("Station number can't be greater than 99"); + OnSerialPropsChanged(); Disconnect(); diff --git a/MewtocolNet/MewtocolInterfaceTcp.cs b/MewtocolNet/MewtocolInterfaceTcp.cs index c047f11..46f467d 100644 --- a/MewtocolNet/MewtocolInterfaceTcp.cs +++ b/MewtocolNet/MewtocolInterfaceTcp.cs @@ -32,11 +32,14 @@ namespace MewtocolNet { #region TCP connection state handling /// - 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)) 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; stationNumber = station; @@ -45,10 +48,14 @@ namespace MewtocolNet { } /// - public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 1) { + public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 0xEE) { ipAddr = ip; Port = port; + + if (stationNumber != 0xEE && stationNumber > 99) + throw new NotSupportedException("Station number can't be greater than 99"); + stationNumber = station; Disconnect(); diff --git a/MewtocolNet/RegisterAttributes/PollFrequencyAttribute.cs b/MewtocolNet/RegisterAttributes/PollFrequencyAttribute.cs deleted file mode 100644 index 981e3ee..0000000 --- a/MewtocolNet/RegisterAttributes/PollFrequencyAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace MewtocolNet.RegisterAttributes { - /// - /// Defines the behavior of a register property - /// - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] - public class PollFrequencyAttribute : Attribute { - - internal int skipEachCycle; - - public PollFrequencyAttribute(int eachCycleN) { - - skipEachCycle = eachCycleN; - - } - - } - -} diff --git a/MewtocolNet/RegisterAttributes/PollLevelAttribute.cs b/MewtocolNet/RegisterAttributes/PollLevelAttribute.cs new file mode 100644 index 0000000..9203657 --- /dev/null +++ b/MewtocolNet/RegisterAttributes/PollLevelAttribute.cs @@ -0,0 +1,21 @@ +using System; + +namespace MewtocolNet.RegisterAttributes { + + /// + /// Defines the poll level of the register + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public class PollLevelAttribute : Attribute { + + internal int pollLevel; + + public PollLevelAttribute(int level) { + + pollLevel = level; + + } + + } + +} diff --git a/MewtocolNet/RegisterAttributes/RegisterCollectionCollector.cs b/MewtocolNet/RegisterAttributes/RegisterCollectionCollector.cs deleted file mode 100644 index e7ce46e..0000000 --- a/MewtocolNet/RegisterAttributes/RegisterCollectionCollector.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace MewtocolNet.RegisterAttributes { - - public class RegisterCollectionCollector { - - internal List collections = new List(); - - public RegisterCollectionCollector AddCollection (RegisterCollection collection) { - - collections.Add(collection); - - return this; - - } - - public RegisterCollectionCollector AddCollection () where T : RegisterCollection { - - var instance = (RegisterCollection)Activator.CreateInstance(typeof(T)); - - collections.Add(instance); - - return this; - - } - - } - -} diff --git a/MewtocolNet/RegisterBuilding/BuilderStep.cs b/MewtocolNet/RegisterBuilding/BuilderStep.cs deleted file mode 100644 index 3c08159..0000000 --- a/MewtocolNet/RegisterBuilding/BuilderStep.cs +++ /dev/null @@ -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 AsType (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(); - - 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(this BuilderStep 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 : 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; - - } - - } - -} diff --git a/MewtocolNet/RegisterBuilding/ParseResult.cs b/MewtocolNet/RegisterBuilding/ParseResult.cs deleted file mode 100644 index 909d9af..0000000 --- a/MewtocolNet/RegisterBuilding/ParseResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MewtocolNet.RegisterBuilding { - - internal struct ParseResult { - - public ParseResultState state; - - public string hardFailReason; - - public BuilderStep stepData; - - } - -} diff --git a/MewtocolNet/RegisterBuilding/ParseResultState.cs b/MewtocolNet/RegisterBuilding/ParseResultState.cs deleted file mode 100644 index 142ac3c..0000000 --- a/MewtocolNet/RegisterBuilding/ParseResultState.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MewtocolNet.RegisterBuilding { - internal enum ParseResultState { - - /// - /// The parse try failed at the intial regex match - /// - FailedSoft, - /// - /// The parse try failed at the afer- regex match - /// - FailedHard, - /// - /// The parse try did work - /// - Success, - - } - -} diff --git a/MewtocolNet/RegisterBuilding/RBuild.cs b/MewtocolNet/RegisterBuilding/RBuild.cs new file mode 100644 index 0000000..3bd82b7 --- /dev/null +++ b/MewtocolNet/RegisterBuilding/RBuild.cs @@ -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 { + + /// + /// The parse try failed at the intial regex match + /// + FailedSoft, + /// + /// The parse try failed at the afer- regex match + /// + FailedHard, + /// + /// The parse try did work + /// + Success, + + } + + /// + /// Contains useful tools for register creation + /// + public class RBuild { + + private MewtocolInterface attachedPLC; + + public RBuild () { } + + internal RBuild (MewtocolInterface plc) { + + attachedPLC = plc; + + } + + public static RBuild Factory => new RBuild(); + + internal List unfinishedList = new List(); + + #region String parse stage + + //methods to test the input string on + private static List> parseMethods = new List>() { + + (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(@"(?X|Y|R)(?[0-9]{0,3})(?(?:[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(@"^(?DT|DDT)(?[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(@"(?DT|DDT)(?[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 AsType () { + + 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(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 : SBase { + + internal TempRegister(SData data) : base(data) {} + + public TempRegister 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 Assemble (RBuild rBuildData) { + + List generatedInstances = new List(); + + 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; + + } + + } + +} diff --git a/MewtocolNet/RegisterBuilding/RegBuilder.cs b/MewtocolNet/RegisterBuilding/RegBuilder.cs deleted file mode 100644 index 580a7ae..0000000 --- a/MewtocolNet/RegisterBuilding/RegBuilder.cs +++ /dev/null @@ -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 { - - /// - /// Contains useful tools for register creation - /// - public class RegBuilder { - - internal MewtocolInterface forInterface = null; - - //methods to test the input string on - private static List> parseMethods = new List>() { - - (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(@"(?X|Y|R)(?[0-9]{0,3})(?(?:[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(@"^(?DT|DDT)(?[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(@"(?DT|DDT)(?[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, - } - }; - - } - - } - -} diff --git a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs new file mode 100644 index 0000000..03267e0 --- /dev/null +++ b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs @@ -0,0 +1,28 @@ +using System; + +namespace MewtocolNet.RegisterBuilding { + public static class RegBuilderExtensions { + + public static IPlc AddTrackedRegisters(this IPlc plc, Action builder) { + + if (plc.IsConnected) + throw new Exception("Can't add registers if the PLC is connected"); + + var regBuilder = new RBuild(); + builder.Invoke(regBuilder); + + var assembler = new RegisterAssembler((MewtocolInterface)plc); + var registers = assembler.Assemble(regBuilder); + + var interf = (MewtocolInterface)plc; + + interf.AddRegisters(registers.ToArray()); + + return plc; + + } + + + } + +} diff --git a/MewtocolNet/RegisterBuilding/RegisterBuildInfo.cs b/MewtocolNet/RegisterBuilding/RegisterBuildInfo.cs deleted file mode 100644 index bfd9ad5..0000000 --- a/MewtocolNet/RegisterBuilding/RegisterBuildInfo.cs +++ /dev/null @@ -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"); - - } - - } - -} diff --git a/MewtocolNet/Registers/BaseRegister.cs b/MewtocolNet/Registers/BaseRegister.cs index 8388d95..edd649c 100644 --- a/MewtocolNet/Registers/BaseRegister.cs +++ b/MewtocolNet/Registers/BaseRegister.cs @@ -25,6 +25,10 @@ namespace MewtocolNet.Registers { internal object lastValue = null; internal string name; internal uint memoryAddress; + internal int pollLevel = 0; + + internal uint successfulReads = 0; + internal uint successfulWrites = 0; /// public RegisterCollection ContainedCollection => containedCollection; @@ -82,6 +86,10 @@ namespace MewtocolNet.Registers { public virtual Task WriteAsync(object data) => throw new NotImplementedException(); + internal virtual Task WriteToAnonymousAsync (object value) => throw new NotImplementedException(); + + internal virtual Task ReadFromAnonymousAsync () => throw new NotImplementedException(); + #endregion #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() { var sb = new StringBuilder(); @@ -148,9 +166,10 @@ namespace MewtocolNet.Registers { sb.AppendLine($"MewName: {GetMewName()}"); sb.AppendLine($"Name: {Name ?? "Not named"}"); sb.AppendLine($"Value: {GetValueString()}"); + sb.AppendLine($"Perf. Reads: {successfulReads}, Writes: {successfulWrites}"); sb.AppendLine($"Register Type: {RegisterType}"); 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]}>"); else sb.AppendLine($"Type: {GetType()}"); if(containedCollection != null) sb.AppendLine($"In collection: {containedCollection.GetType()}"); diff --git a/MewtocolNet/Registers/BoolRegister.cs b/MewtocolNet/Registers/BoolRegister.cs index b17b071..f54b62b 100644 --- a/MewtocolNet/Registers/BoolRegister.cs +++ b/MewtocolNet/Registers/BoolRegister.cs @@ -112,9 +112,6 @@ namespace MewtocolNet.Registers { } - /// - public override void ClearValue() => SetValueFromPLC(false); - /// public override string GetMewName() { diff --git a/MewtocolNet/Registers/BytesRegister.cs b/MewtocolNet/Registers/BytesRegister.cs index 813bb92..36fa4bd 100644 --- a/MewtocolNet/Registers/BytesRegister.cs +++ b/MewtocolNet/Registers/BytesRegister.cs @@ -117,9 +117,6 @@ namespace MewtocolNet.Registers { /// public override string GetRegisterString() => "DT"; - /// - public override void ClearValue() => SetValueFromPLC(null); - /// public override uint GetRegisterAddressLen() => AddressLength; @@ -139,6 +136,8 @@ namespace MewtocolNet.Registers { internal override object SetValueFromBytes(byte[] bytes) { + AddSuccessRead(); + object parsed; if (ReservedBitSize != null) { parsed = PlcValueParser.Parse(this, bytes); @@ -166,6 +165,7 @@ namespace MewtocolNet.Registers { var res = await underlyingMemory.WriteRegisterAsync(this, encoded); if (res) { + AddSuccessWrite(); SetValueFromPLC(data); } diff --git a/MewtocolNet/Registers/NumberRegister.cs b/MewtocolNet/Registers/NumberRegister.cs index 66c9838..08e10c1 100644 --- a/MewtocolNet/Registers/NumberRegister.cs +++ b/MewtocolNet/Registers/NumberRegister.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using static MewtocolNet.RegisterBuilding.RBuild; namespace MewtocolNet.Registers { @@ -67,7 +68,8 @@ namespace MewtocolNet.Registers { if (lastValue?.ToString() != val?.ToString()) { - lastValue = (T)val; + if (val != null) lastValue = (T)val; + else lastValue = null; TriggerNotifyChange(); attachedInterface.InvokeRegisterChanged(this); @@ -122,9 +124,6 @@ namespace MewtocolNet.Registers { } - /// - public override void ClearValue() => SetValueFromPLC(default(T)); - /// public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2); @@ -144,6 +143,8 @@ namespace MewtocolNet.Registers { internal override object SetValueFromBytes(byte[] bytes) { + AddSuccessRead(); + var parsed = PlcValueParser.Parse(this, bytes); SetValueFromPLC(parsed); return parsed; @@ -159,6 +160,7 @@ namespace MewtocolNet.Registers { var res = await underlyingMemory.WriteRegisterAsync(this, encoded); if (res) { + AddSuccessWrite(); SetValueFromPLC(data); } @@ -166,29 +168,25 @@ namespace MewtocolNet.Registers { } - /// - /// Gets the register bitwise if its a 16 or 32 bit int - /// - /// A bitarray - public BitArray GetBitwise() { + internal override async Task WriteToAnonymousAsync (object value) { - if (this is NumberRegister shortReg) { + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); - var bytes = BitConverter.GetBytes((short)Value); - BitArray bitAr = new BitArray(bytes); - return bitAr; + var encoded = PlcValueParser.Encode(this, (T)value); + return await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); - } + } - if (this is NumberRegister intReg) { + internal override async Task ReadFromAnonymousAsync () { - var bytes = BitConverter.GetBytes((int)Value); - BitArray bitAr = new BitArray(bytes); - return bitAr; + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); - } + var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); + if (res == null) return null; - return null; + return PlcValueParser.Parse(this, res); } diff --git a/MewtocolNet/Registers/StringRegister.cs b/MewtocolNet/Registers/StringRegister.cs index 7d5f013..58a5d1a 100644 --- a/MewtocolNet/Registers/StringRegister.cs +++ b/MewtocolNet/Registers/StringRegister.cs @@ -69,9 +69,6 @@ namespace MewtocolNet.Registers { /// public override string GetRegisterString() => "DT"; - /// - public override void ClearValue() => SetValueFromPLC(""); - /// public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize); diff --git a/MewtocolNet/SetupClasses/PollLevelConfig.cs b/MewtocolNet/SetupClasses/PollLevelConfig.cs new file mode 100644 index 0000000..c0fb00c --- /dev/null +++ b/MewtocolNet/SetupClasses/PollLevelConfig.cs @@ -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; + + } + +} diff --git a/MewtocolNet/UnderlyingRegisters/DTArea.cs b/MewtocolNet/UnderlyingRegisters/DTArea.cs index 47136a8..dda1e80 100644 --- a/MewtocolNet/UnderlyingRegisters/DTArea.cs +++ b/MewtocolNet/UnderlyingRegisters/DTArea.cs @@ -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 ReadRegisterAsync (BaseRegister reg) { return await RequestByteReadAsync(reg.MemoryAddress, reg.MemoryAddress + reg.GetRegisterAddressLen() - 1); @@ -125,6 +139,8 @@ namespace MewtocolNet.UnderlyingRegisters { int copyOffset = (int)((addStart - addressStart) * 2); bytes.CopyTo(underlyingBytes, copyOffset); + UpdateAreaRegisterValues(); + } private string GetMewtocolIdent () { diff --git a/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs b/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs index 3310f3c..9deb19b 100644 --- a/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs +++ b/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs @@ -11,6 +11,8 @@ namespace MewtocolNet.UnderlyingRegisters { Task WriteRegisterAsync(BaseRegister reg, byte[] bytes); + void UpdateAreaRegisterValues(); + } } diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index 9f2a25c..983aab2 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -1,6 +1,8 @@ using MewtocolNet.Registers; +using MewtocolNet.SetupClasses; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -11,22 +13,16 @@ namespace MewtocolNet.UnderlyingRegisters { internal int maxOptimizationDistance = 8; internal int maxRegistersPerGroup = -1; + internal bool allowByteRegDupes; + + private int wrAreaSize; + private int dtAreaSize; internal MewtocolInterface mewInterface; + internal List pollLevels; + internal Dictionary pollLevelConfigs = new Dictionary(); - // WR areas are n of words, each word has 2 bytes representing the "special address component" - - //X WR - internal List externalRelayInAreas; - - //Y WR - internal List externalRelayOutAreas; - - //R WR - internal List internalRelayAreas; - - //DT - internal List dataAreas; + private uint pollIteration = 0; 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 internal void Setup (int wrSize, int dtSize) { - externalRelayInAreas = new List(wrSize * 16); - externalRelayOutAreas = new List(wrSize * 16); - internalRelayAreas = new List(wrSize * 16); - dataAreas = new List(dtSize); + wrAreaSize = wrSize; + dtAreaSize = dtSize; + pollLevels = new List { + new PollLevel(wrSize, dtSize) { + level = 1, + } + }; } internal bool LinkRegister (BaseRegister reg) { + TestPollLevelExistence(reg); + switch (reg.RegisterType) { case RegisterType.X: - return AddWRArea(reg, externalRelayInAreas); case RegisterType.Y: - return AddWRArea(reg, externalRelayOutAreas); case RegisterType.R: - return AddWRArea(reg, internalRelayAreas); + return AddWRArea(reg); case RegisterType.DT: case RegisterType.DDT: case RegisterType.DT_BYTE_RANGE: @@ -64,7 +63,48 @@ namespace MewtocolNet.UnderlyingRegisters { } - private bool AddWRArea (BaseRegister insertReg, List 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 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); @@ -114,6 +154,9 @@ namespace MewtocolNet.UnderlyingRegisters { DTArea targetArea = null; + var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel); + var dataAreas = pollLevelFound.dataAreas; + foreach (var dtArea in dataAreas) { bool matchingAddress = regInsAddStart >= dtArea.AddressStart && @@ -123,9 +166,10 @@ namespace MewtocolNet.UnderlyingRegisters { if (matchingAddress) { //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( message: $"Can't have registers of different types at the same referenced plc address: " + $"{insertReg.PLCAddressName} ({insertReg.GetType()}) <=> " + @@ -211,34 +255,60 @@ namespace MewtocolNet.UnderlyingRegisters { internal async Task PollAllAreasAsync () { - foreach (var dtArea in dataAreas) { + foreach (var pollLevel in pollLevels) { - //set the whole memory area at once - var res = await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd); + var sw = Stopwatch.StartNew(); - 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 addLen = (int)register.GetRegisterAddressLen(); + var lvlConfig = pollLevelConfigs[pollLevel.level]; + var skipIterations = lvlConfig.skipNth; + var skipDelay = lvlConfig.delay; - var bytes = dtArea.GetUnderlyingBytes(regStart, addLen); - register.SetValueFromBytes(bytes); + if (skipIterations != null && pollIteration % skipIterations.Value != 0) { + + //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; + } - } - - internal void Merge () { - - //merge gaps that the algorithm didn't catch be rerunning the register attachment - - var allDataAreaRegisters = dataAreas.SelectMany(x => x.linkedRegisters).ToList(); - dataAreas = new List(allDataAreaRegisters.Capacity); - - foreach (var reg in allDataAreaRegisters) - AddDTArea(reg); + if(pollIteration == uint.MaxValue) { + pollIteration = uint.MinValue; + } else { + pollIteration++; + } } @@ -246,63 +316,77 @@ namespace MewtocolNet.UnderlyingRegisters { var sb = new StringBuilder(); - sb.AppendLine("---- DT Area ----"); + foreach (var pollLevel in pollLevels) { - sb.AppendLine($"Optimization distance: {maxOptimizationDistance}"); - - foreach (var area in dataAreas) { + sb.AppendLine($"==== Poll lvl {pollLevel.level} ===="); sb.AppendLine(); - sb.AppendLine($"=> {area} = {area.underlyingBytes.Length} bytes"); - sb.AppendLine(); - sb.AppendLine(string.Join("\n", area.underlyingBytes.ToHexString(" ").SplitInParts(3 * 8))); + if (pollLevelConfigs[pollLevel.level].delay != null) { + sb.AppendLine($"Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms"); + } 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(); - 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 GetAllRegisters () { + + List registers = new List(); + + 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; + + } } diff --git a/MewtocolNet/UnderlyingRegisters/PollLevel.cs b/MewtocolNet/UnderlyingRegisters/PollLevel.cs new file mode 100644 index 0000000..2d99b69 --- /dev/null +++ b/MewtocolNet/UnderlyingRegisters/PollLevel.cs @@ -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(wrSize * 16); + externalRelayOutAreas = new List(wrSize * 16); + internalRelayAreas = new List(wrSize * 16); + dataAreas = new List(dtSize); + + } + + internal int level; + + // WR areas are n of words, each word has 2 bytes representing the "special address component" + + //X WR + internal List externalRelayInAreas; + + //Y WR + internal List externalRelayOutAreas; + + //R WR + internal List internalRelayAreas; + + //DT + internal List dataAreas; + + } + +} diff --git a/MewtocolNet/UnderlyingRegisters/WRArea.cs b/MewtocolNet/UnderlyingRegisters/WRArea.cs index d040cd6..c2f7a9b 100644 --- a/MewtocolNet/UnderlyingRegisters/WRArea.cs +++ b/MewtocolNet/UnderlyingRegisters/WRArea.cs @@ -23,6 +23,12 @@ namespace MewtocolNet.UnderlyingRegisters { mewInterface = mewIf; + } + + public void UpdateAreaRegisterValues () { + + + } public byte[] GetUnderlyingBytes(BaseRegister reg) {