diff --git a/Examples.WPF/App.xaml b/Examples.WPF/App.xaml index fe9a706..4b030de 100644 --- a/Examples.WPF/App.xaml +++ b/Examples.WPF/App.xaml @@ -6,5 +6,6 @@ StartupUri="MainWindow.xaml"> + diff --git a/Examples.WPF/Converters/ColorHashConverter.cs b/Examples.WPF/Converters/ColorHashConverter.cs new file mode 100644 index 0000000..8692d54 --- /dev/null +++ b/Examples.WPF/Converters/ColorHashConverter.cs @@ -0,0 +1,62 @@ +using System; +using System.Drawing; +using System.Globalization; +using System.Windows.Data; + + +namespace Examples.WPF.Converters; + +[ValueConversion(typeof(bool), typeof(bool))] +public class ColorHashConverter : IValueConverter { + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { + + var hashCode = value.GetHashCode(); + var randColor = GenerateRandomVibrantColor(new Random(hashCode)); + + System.Windows.Media.Brush outBrush = new System.Windows.Media.SolidColorBrush(new System.Windows.Media.Color { + R = randColor.R, + G = randColor.G, + B = randColor.B, + A = 255, + }); + + return outBrush; + + } + + private Color GenerateRandomVibrantColor(Random random) { + + byte red = (byte)random.Next(256); + byte green = (byte)random.Next(256); + byte blue = (byte)random.Next(256); + + Color color = Color.FromArgb(255, red, green, blue); + + // Ensure the color is vibrant and colorful + while (!IsVibrantColor(color)) { + red = (byte)random.Next(256); + green = (byte)random.Next(256); + blue = (byte)random.Next(256); + color = Color.FromArgb(255,red, green, blue); + } + + return color; + } + + private bool IsVibrantColor(Color color) { + + int minBrightness = 100; + int maxBrightness = 200; + int minSaturation = 150; + + int brightness = (int)(color.GetBrightness() * 255); + int saturation = (int)(color.GetSaturation() * 255); + + return brightness >= minBrightness && brightness <= maxBrightness && saturation >= minSaturation; + + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); + +} diff --git a/Examples.WPF/ViewModels/PlcDataViewViewModel.cs b/Examples.WPF/ViewModels/PlcDataViewViewModel.cs index 174aabf..7cacd07 100644 --- a/Examples.WPF/ViewModels/PlcDataViewViewModel.cs +++ b/Examples.WPF/ViewModels/PlcDataViewViewModel.cs @@ -1,4 +1,5 @@ using MewtocolNet; +using MewtocolNet.Events; using System; using System.Collections.Generic; using System.Linq; @@ -9,6 +10,24 @@ namespace Examples.WPF.ViewModels; public class PlcDataViewViewModel : ViewModelBase { + private ReconnectArgs plcCurrentReconnectArgs = null!; + public IPlc Plc => App.ViewModel.Plc!; + public ReconnectArgs PlcCurrentReconnectArgs { + get => plcCurrentReconnectArgs; + set { + plcCurrentReconnectArgs = value; + OnPropChange(); + } + } + + public PlcDataViewViewModel () { + + Plc.ReconnectTryStarted += (s, e) => PlcCurrentReconnectArgs = e; + Plc.Reconnected += (s, e) => PlcCurrentReconnectArgs = null!; + Plc.Disconnected += (s, e) => PlcCurrentReconnectArgs = null!; + + } + } \ No newline at end of file diff --git a/Examples.WPF/Views/ConnectView.xaml.cs b/Examples.WPF/Views/ConnectView.xaml.cs index 6cc631a..adc4f35 100644 --- a/Examples.WPF/Views/ConnectView.xaml.cs +++ b/Examples.WPF/Views/ConnectView.xaml.cs @@ -59,9 +59,12 @@ public partial class ConnectView : UserControl { App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt) .WithPoller() .WithInterfaceSettings(setting => { - setting.TryReconnectAttempts = 30; + setting.TryReconnectAttempts = 0; setting.TryReconnectDelayMs = 2000; + setting.SendReceiveTimeoutMs = 1000; setting.HeartbeatIntervalMs = 3000; + setting.MaxDataBlocksPerWrite = 12; + setting.MaxOptimizationDistance = 10; }) .WithCustomPollLevels(lvl => { lvl.SetLevel(2, 3); @@ -73,6 +76,8 @@ public partial class ConnectView : UserControl { //b.Struct("DT0").Build(); //b.Struct("DT0").AsArray(30).Build(); + b.Bool("R10A").Build(); + b.Struct("DT1000").Build(out heartbeatSetter); b.Struct("DT1000").Build(); diff --git a/Examples.WPF/Views/PlcDataView.xaml b/Examples.WPF/Views/PlcDataView.xaml index c82fa9a..d0ca91c 100644 --- a/Examples.WPF/Views/PlcDataView.xaml +++ b/Examples.WPF/Views/PlcDataView.xaml @@ -33,16 +33,56 @@ - + + + + + + + + + + + + + + + + + + + + + + + + in + + + + + + + + + + + + + + + diff --git a/MewtocolNet/Events/ReconnectArgs.cs b/MewtocolNet/Events/ReconnectArgs.cs new file mode 100644 index 0000000..969d559 --- /dev/null +++ b/MewtocolNet/Events/ReconnectArgs.cs @@ -0,0 +1,84 @@ +using MewtocolNet.Registers; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text; + +namespace MewtocolNet.Events { + + public delegate void PlcReconnectEventHandler(object sender, ReconnectArgs e); + + public class ReconnectArgs : EventArgs, INotifyPropertyChanged { + + private TimeSpan retryCountDownRemaining; + + public event PropertyChangedEventHandler PropertyChanged; + + public int ReconnectTry { get; internal set; } + + public int MaxAttempts { get; internal set; } + + public TimeSpan RetryCountDownTime { get; internal set; } + + public TimeSpan RetryCountDownRemaining { + get => retryCountDownRemaining; + private set { + retryCountDownRemaining = value; + OnPropChange(); + } + } + + private System.Timers.Timer countDownTimer; + + internal ReconnectArgs(int currentAttempt, int totalAttempts, TimeSpan delayBetween) { + + ReconnectTry = currentAttempt; + MaxAttempts = totalAttempts; + RetryCountDownTime = delayBetween; + + //start countdown timer + RetryCountDownRemaining = RetryCountDownTime; + + var interval = 100; + var intervalTS = TimeSpan.FromMilliseconds(interval); + + countDownTimer = new System.Timers.Timer(100); + + countDownTimer.Elapsed += (s, e) => { + + if (RetryCountDownRemaining <= TimeSpan.Zero) { + StopTimer(); + return; + } + + RetryCountDownRemaining -= intervalTS; + + }; + + countDownTimer.Start(); + + } + + internal void ConnectionSuccess () { + + StopTimer(); + + } + + private void StopTimer () { + + countDownTimer?.Stop(); + RetryCountDownRemaining = TimeSpan.Zero; + + } + + private void OnPropChange([CallerMemberName] string propertyName = null) { + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + } + + } + +} diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index c63bc36..c0878fd 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -20,6 +20,23 @@ namespace MewtocolNet { #region Byte and string operation helpers + public static T SetFlag(this Enum value, T flag, bool set) { + + Type underlyingType = Enum.GetUnderlyingType(value.GetType()); + + dynamic valueAsInt = Convert.ChangeType(value, underlyingType); + dynamic flagAsInt = Convert.ChangeType(flag, underlyingType); + + if (set) { + valueAsInt |= flagAsInt; + } else { + valueAsInt &= ~flagAsInt; + } + + return (T)valueAsInt; + + } + public static int DetermineTypeByteIntialSize(this Type type) { //enums can only be of numeric types diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index 590bc9d..e0b430d 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -1,4 +1,5 @@ -using MewtocolNet.ProgramParsing; +using MewtocolNet.Events; +using MewtocolNet.ProgramParsing; using MewtocolNet.RegisterBuilding; using MewtocolNet.Registers; using System; @@ -12,6 +13,31 @@ namespace MewtocolNet { /// Provides a interface for Panasonic PLCs /// public interface IPlc : IDisposable, INotifyPropertyChanged { + + /// + /// Fires when the interface is fully connected to a PLC + /// + event PlcConnectionEventHandler Connected; + + /// + /// Fires when a reconnect attempt was successfull + /// + event PlcConnectionEventHandler Reconnected; + + /// + /// Fires when the interfaces makes a reconnect try to the PLC + /// + event PlcReconnectEventHandler ReconnectTryStarted; + + /// + /// Fires when the plc/interface connection was fully closed + /// + event PlcConnectionEventHandler Disconnected; + + /// + /// Fires when the value of a register changes + /// + event RegisterChangedEventHandler RegisterChanged; /// /// The current connection state of the interface diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index d534b37..45de15e 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -29,6 +29,9 @@ namespace MewtocolNet { /// public event PlcConnectionEventHandler Reconnected; + /// + public event PlcReconnectEventHandler ReconnectTryStarted; + /// public event PlcConnectionEventHandler Disconnected; @@ -139,13 +142,7 @@ namespace MewtocolNet { } /// - public int BytesPerSecondUpstream { - get { return bytesPerSecondUpstream; } - private protected set { - bytesPerSecondUpstream = value; - OnPropChange(); - } - } + public int BytesPerSecondUpstream => bytesPerSecondUpstream; /// public bool IsReceiving { @@ -157,13 +154,7 @@ namespace MewtocolNet { } /// - public int BytesPerSecondDownstream { - get { return bytesPerSecondDownstream; } - private protected set { - bytesPerSecondDownstream = value; - OnPropChange(); - } - } + public int BytesPerSecondDownstream => bytesPerSecondDownstream; /// public MewtocolVersion MewtocolVersion { @@ -302,7 +293,15 @@ namespace MewtocolNet { if (pollCycleTask != null && !pollCycleTask.IsCompleted) pollCycleTask.Wait(); - OnMajorSocketExceptionWhileConnected(); + if (IsConnected) { + + tSource.Cancel(); + isMessageLocked = false; + + Logger.Log("The PLC connection was closed manually", LogLevel.Error, this); + OnDisconnect(); + + } } @@ -348,8 +347,16 @@ namespace MewtocolNet { //stop the heartbeat timer for the time of retries StopHeartBeat(); + var eArgs = new ReconnectArgs(retryCount, tryReconnectAttempts, TimeSpan.FromMilliseconds(tryReconnectDelayMs)); + ReconnectTryStarted?.Invoke(this, eArgs); + + Reconnected += (s, e) => eArgs.ConnectionSuccess(); + await ReconnectAsync(tryReconnectDelayMs); - await Task.Delay(2000); + + if (IsConnected) return; + + await Task.Delay(tryReconnectDelayMs); retryCount++; @@ -378,17 +385,7 @@ namespace MewtocolNet { if (regularSendTask != null && !regularSendTask.IsCompleted) { //queue self - - var t = new Task(() => SendCommandInternalAsync(_msg, onReceiveProgress).Result); - userInputSendTasks.Enqueue(t); - - OnPropChange(nameof(QueuedMessages)); - - await t; - - OnPropChange(nameof(QueuedMessages)); - - return t.Result; + return await EnqueueMessage(_msg, onReceiveProgress); } @@ -399,17 +396,12 @@ namespace MewtocolNet { isMessageLocked = true; - //wait for the last send task to complete - //if (regularSendTask != null && !regularSendTask.IsCompleted) await regularSendTask; - //send request regularSendTask = SendTwoDirectionalFrameAsync(_msg, onReceiveProgress); - //if (regularSendTask == null) return MewtocolFrameResponse.Canceled; - try { - var timeoutAwaiter = await Task.WhenAny(regularSendTask, Task.Delay(2000, tSource.Token)); + var timeoutAwaiter = await Task.WhenAny(regularSendTask, Task.Delay(sendReceiveTimeoutMs, tSource.Token)); if (timeoutAwaiter != regularSendTask) { @@ -444,10 +436,52 @@ namespace MewtocolNet { isMessageLocked = false; regularSendTask = null; + //run the remaining tasks if no poller is used + if(!PollerActive && !tSource.Token.IsCancellationRequested) { + + while(userInputSendTasks.Count > 1) { + + if (PollerActive || tSource.Token.IsCancellationRequested) break; + + await RunOneOpenQueuedTask(); + + } + + } + return responseData; } + protected async Task RunOneOpenQueuedTask() { + + if (userInputSendTasks != null && userInputSendTasks.Count > 0) { + + var t = userInputSendTasks.Dequeue(); + + t.Start(); + + await t; + + } + + } + + protected async Task EnqueueMessage(string _msg, Action onReceiveProgress = null) { + + var t = new Task(() => SendCommandInternalAsync(_msg, onReceiveProgress).Result); + userInputSendTasks.Enqueue(t); + + OnPropChange(nameof(QueuedMessages)); + + await t; + + OnPropChange(nameof(QueuedMessages)); + + return t.Result; + + } + private protected async Task SendOneDirectionalFrameAsync (string frame) { try { @@ -763,17 +797,17 @@ namespace MewtocolNet { } - private protected void OnMajorSocketExceptionWhileConnected() { + private protected void OnSocketExceptionWhileConnected() { - if (IsConnected) { + tSource.Cancel(); - tSource.Cancel(); - isMessageLocked = false; + bytesPerSecondDownstream = 0; + bytesPerSecondUpstream = 0; - Logger.Log("The PLC connection was closed", LogLevel.Error, this); - OnDisconnect(); + isMessageLocked = false; + IsConnected = false; - } + if (reconnectTask == null) StartReconnectTask(); } @@ -785,16 +819,6 @@ namespace MewtocolNet { } - private protected void OnSocketExceptionWhileConnected () { - - tSource.Cancel(); - isMessageLocked = false; - IsConnected = false; - - if (reconnectTask == null) StartReconnectTask(); - - } - private protected virtual void OnConnected(PLCInfo plcinf) { Logger.Log("Connected to PLC", LogLevel.Info, this); @@ -807,18 +831,23 @@ namespace MewtocolNet { //notify the registers GetAllRegisters().Cast().ToList().ForEach(x => x.OnPlcConnected()); + reconnectTask = null; IsConnected = true; - - Connected?.Invoke(this, new PlcConnectionArgs()); + isReconnectingStage = false; + isConnectingStage = false; if (!usePoller) { firstPollTask.RunSynchronously(); } + Connected?.Invoke(this, new PlcConnectionArgs()); + PolledCycle += OnPollCycleDone; void OnPollCycleDone() { - firstPollTask.RunSynchronously(); + if(firstPollTask != null && !firstPollTask.IsCompleted) + firstPollTask.RunSynchronously(); + PolledCycle -= OnPollCycleDone; } @@ -829,8 +858,8 @@ namespace MewtocolNet { IsReceiving = false; IsSending = false; - BytesPerSecondDownstream = 0; - BytesPerSecondUpstream = 0; + bytesPerSecondDownstream = 0; + bytesPerSecondUpstream = 0; PollerCycleDurationMs = 0; isMessageLocked = false; @@ -856,8 +885,8 @@ namespace MewtocolNet { IsReceiving = false; IsSending = false; - BytesPerSecondDownstream = 0; - BytesPerSecondUpstream = 0; + bytesPerSecondDownstream = 0; + bytesPerSecondUpstream = 0; PollerCycleDurationMs = 0; PlcInfo = null; @@ -887,6 +916,9 @@ namespace MewtocolNet { GetAllRegisters().Cast() .ToList().ForEach(x => x.OnInterfaceCyclicTimerUpdate((int)cyclicGenericUpdateCounter.Interval)); + OnPropChange(nameof(BytesPerSecondUpstream)); + OnPropChange(nameof(BytesPerSecondDownstream)); + } private void SetUpstreamStopWatchStart() { @@ -921,7 +953,7 @@ namespace MewtocolNet { var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000); if (perSecUpstream <= 10000) - BytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero); + bytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero); } @@ -932,7 +964,7 @@ namespace MewtocolNet { var perSecDownstream = (double)((bytesTotalCountedDownstream / speedStopwatchDownstr.Elapsed.TotalMilliseconds) * 1000); if (perSecDownstream <= 10000) - BytesPerSecondDownstream = (int)Math.Round(perSecDownstream, MidpointRounding.AwayFromZero); + bytesPerSecondDownstream = (int)Math.Round(perSecDownstream, MidpointRounding.AwayFromZero); } diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index 1cd0e57..0fb4d56 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -17,6 +17,8 @@ namespace MewtocolNet { /// public abstract partial class MewtocolInterface { + private bool heartbeatNeedsRun = false; + internal Task heartbeatTask = Task.CompletedTask; internal Func heartbeatCallbackTask; @@ -127,9 +129,13 @@ namespace MewtocolNet { heartbeatNeedsRun = true; - } + if(!PollerActive) { - private bool heartbeatNeedsRun = false; + Task.Run(HeartbeatTickTask); + + } + + } private async Task HeartbeatTickTask () { @@ -141,8 +147,7 @@ namespace MewtocolNet { Logger.LogError("Heartbeat timed out", this); - //OnSocketExceptionWhileConnected(); - //StartReconnectTask(); + OnSocketExceptionWhileConnected(); return; @@ -224,15 +229,7 @@ namespace MewtocolNet { await memoryManager.PollAllAreasAsync(async () => { - if (userInputSendTasks != null && userInputSendTasks.Count > 0) { - - var t = userInputSendTasks.Dequeue(); - - t.Start(); - - await t; - - } + await RunOneOpenQueuedTask(); }); diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 94a0760..3c9bde9 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -138,7 +138,12 @@ namespace MewtocolNet { var result = await SendCommandInternalAsync(requeststring); if (result.Success) { + Logger.Log($"Operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this); + + //directily update the op mode + PlcInfo.OperationMode = PlcInfo.OperationMode.SetFlag(OPMode.RunMode, setRun); + } else { Logger.Log("Operation mode change failed", LogLevel.Error, this); } diff --git a/MewtocolNet/MewtocolInterfaceTcp.cs b/MewtocolNet/MewtocolInterfaceTcp.cs index 4e10e69..1f65115 100644 --- a/MewtocolNet/MewtocolInterfaceTcp.cs +++ b/MewtocolNet/MewtocolInterfaceTcp.cs @@ -211,30 +211,12 @@ namespace MewtocolNet { regularSendTask = null; reconnectTask = Task.CompletedTask; - //try to abort any non read message - //await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB"); + if (await SendCommandInternalAsync($"%{GetStationNumber()}#RT") != null) { - //get plc info 2 times to clear old stuff from the buffer + Logger.Log("Reconnect successfull"); + OnReconnected(); - OnReconnected(); - - //var plcinf = await SendCommandAsync($"%{GetStationNumber()}#RT"); - - //if (plcinf != null) { - - // Logger.Log("Reconnect successfull"); - - // OnReconnected(); - - // //await base.ConnectAsync(); - // //OnConnected(plcinf); - - //} else { - - // Logger.Log("Initial connection failed", LogLevel.Error, this); - // OnDisconnect(); - - //} + } await Task.CompletedTask; diff --git a/MewtocolNet/MewtocolNet.csproj b/MewtocolNet/MewtocolNet.csproj index 7318b21..d87d056 100644 --- a/MewtocolNet/MewtocolNet.csproj +++ b/MewtocolNet/MewtocolNet.csproj @@ -41,6 +41,7 @@ + diff --git a/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs index 444404d..f4da17f 100644 --- a/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs +++ b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs @@ -32,7 +32,21 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { internal Register Assemble(StepBase stp) => assembler.Assemble(stp.Data); - //struct constructor + //bool constructor + + public StructStp Bool(string fpAddr, string name = null) { + + var data = AddressTools.ParseAddress(fpAddr, name); + + data.dotnetVarType = typeof(bool); + + return new StructStp(data) { + builder = this, + }; + + } + + //struct constructor public StructStp Struct(string fpAddr, string name = null) where T : struct { diff --git a/MewtocolNet/Registers/Base/IRegister.cs b/MewtocolNet/Registers/Base/IRegister.cs index 44380db..fe589e6 100644 --- a/MewtocolNet/Registers/Base/IRegister.cs +++ b/MewtocolNet/Registers/Base/IRegister.cs @@ -29,6 +29,11 @@ namespace MewtocolNet.Registers { /// int PollLevel { get; } + /// + /// Info string about the memory area + /// + string MemoryAreaInfo { get; } + /// /// The update frequency of the register in Hz /// @@ -59,6 +64,8 @@ namespace MewtocolNet.Registers { /// uint MemoryAddress { get; } + string MemoryAreaHash { get; } + /// /// Gets the value of the register as the plc representation string /// diff --git a/MewtocolNet/Registers/Base/Register.cs b/MewtocolNet/Registers/Base/Register.cs index f448195..f1598a3 100644 --- a/MewtocolNet/Registers/Base/Register.cs +++ b/MewtocolNet/Registers/Base/Register.cs @@ -27,7 +27,7 @@ namespace MewtocolNet.Registers { internal List boundProperties = new List(); internal Type underlyingSystemType; - internal IMemoryArea underlyingMemory; + internal AreaBase underlyingMemory; internal bool autoGenerated; internal bool isAnonymous; @@ -94,6 +94,9 @@ namespace MewtocolNet.Registers { } } + public string MemoryAreaInfo => underlyingMemory.GetName(); + + public string MemoryAreaHash => underlyingMemory.GetHashCode().ToString(); internal Register() { } diff --git a/MewtocolNet/Registers/Classes/BoolRegister.cs b/MewtocolNet/Registers/Classes/BoolRegister.cs index 59fa41c..0665066 100644 --- a/MewtocolNet/Registers/Classes/BoolRegister.cs +++ b/MewtocolNet/Registers/Classes/BoolRegister.cs @@ -74,6 +74,17 @@ namespace MewtocolNet.Registers { /// public override uint GetRegisterAddressLen() => 1; + internal override object SetValueFromBytes(byte[] bytes) { + + AddSuccessRead(); + + var parsed = PlcValueParser.Parse(this, bytes); + + UpdateHoldingValue(parsed); + return parsed; + + } + } } diff --git a/MewtocolNet/Registers/Classes/StringRegister.cs b/MewtocolNet/Registers/Classes/StringRegister.cs index 40b7cbf..81dd5fe 100644 --- a/MewtocolNet/Registers/Classes/StringRegister.cs +++ b/MewtocolNet/Registers/Classes/StringRegister.cs @@ -123,9 +123,7 @@ namespace MewtocolNet.Registers { //if string correct the sizing of the byte hint was wrong var reservedSize = BitConverter.ToInt16(bytes, 0); - if (reservedStringLength != reservedSize && - attachedInterface.PlcInfo.IsRunMode && - (attachedInterface.pollerTaskStopped || attachedInterface.pollerFirstCycleCompleted)) + if (reservedStringLength != reservedSize && attachedInterface.PlcInfo.IsRunMode) throw new NotSupportedException( $"The STRING register at {GetMewName()} is not correctly sized, " + $"the size should be STRING[{reservedSize}] instead of STRING[{reservedStringLength}]" diff --git a/MewtocolNet/UnderlyingRegisters/DTArea.cs b/MewtocolNet/UnderlyingRegisters/Areas/AreaBase.cs similarity index 81% rename from MewtocolNet/UnderlyingRegisters/DTArea.cs rename to MewtocolNet/UnderlyingRegisters/Areas/AreaBase.cs index d745547..eb9215a 100644 --- a/MewtocolNet/UnderlyingRegisters/DTArea.cs +++ b/MewtocolNet/UnderlyingRegisters/Areas/AreaBase.cs @@ -5,7 +5,8 @@ using System.Text; using System.Threading.Tasks; namespace MewtocolNet.UnderlyingRegisters { - public class DTArea : IMemoryArea { + + public abstract class AreaBase { private MewtocolInterface mewInterface; @@ -23,7 +24,7 @@ namespace MewtocolNet.UnderlyingRegisters { public ulong AddressStart => addressStart; public ulong AddressEnd => addressEnd; - internal DTArea(MewtocolInterface mewIf) { + internal AreaBase(MewtocolInterface mewIf) { mewInterface = mewIf; @@ -116,26 +117,12 @@ namespace MewtocolNet.UnderlyingRegisters { } - private string GetMewtocolIdent() { - - StringBuilder asciistring = new StringBuilder("D"); - asciistring.Append(AddressStart.ToString().PadLeft(5, '0')); - asciistring.Append(AddressEnd.ToString().PadLeft(5, '0')); - return asciistring.ToString(); - - } - - private string GetMewtocolIdent(ulong addStart, ulong addEnd) { - - StringBuilder asciistring = new StringBuilder("D"); - asciistring.Append(addStart.ToString().PadLeft(5, '0')); - asciistring.Append(addEnd.ToString().PadLeft(5, '0')); - return asciistring.ToString(); - - } - public override string ToString() => $"DT{AddressStart}-{AddressEnd}"; + public virtual string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)"; + + public string GetHash() => GetHashCode().ToString(); + } } diff --git a/MewtocolNet/UnderlyingRegisters/Areas/DTArea.cs b/MewtocolNet/UnderlyingRegisters/Areas/DTArea.cs new file mode 100644 index 0000000..e6a567e --- /dev/null +++ b/MewtocolNet/UnderlyingRegisters/Areas/DTArea.cs @@ -0,0 +1,19 @@ +using MewtocolNet.Registers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MewtocolNet.UnderlyingRegisters { + + public class DTArea : AreaBase, IMemoryArea { + + internal DTArea(MewtocolInterface mewIf) : base(mewIf) { } + + public override string ToString() => $"DT{AddressStart}-{AddressEnd}"; + + public override string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)"; + + } + +} diff --git a/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs b/MewtocolNet/UnderlyingRegisters/Areas/IMemoryArea.cs similarity index 91% rename from MewtocolNet/UnderlyingRegisters/IMemoryArea.cs rename to MewtocolNet/UnderlyingRegisters/Areas/IMemoryArea.cs index e593fa4..3bc41a2 100644 --- a/MewtocolNet/UnderlyingRegisters/IMemoryArea.cs +++ b/MewtocolNet/UnderlyingRegisters/Areas/IMemoryArea.cs @@ -4,6 +4,8 @@ namespace MewtocolNet.UnderlyingRegisters { internal interface IMemoryArea { + string GetName(); + byte[] GetUnderlyingBytes(Register reg); void SetUnderlyingBytes(Register reg, byte[] bytes); diff --git a/MewtocolNet/UnderlyingRegisters/Areas/WRArea.cs b/MewtocolNet/UnderlyingRegisters/Areas/WRArea.cs new file mode 100644 index 0000000..d980672 --- /dev/null +++ b/MewtocolNet/UnderlyingRegisters/Areas/WRArea.cs @@ -0,0 +1,19 @@ +using MewtocolNet.Registers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MewtocolNet.UnderlyingRegisters { + + public class WRArea : AreaBase, IMemoryArea { + + internal WRArea(MewtocolInterface mewIf) : base(mewIf) { } + + public override string ToString() => $"DT{AddressStart}-{AddressEnd}"; + + public override string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)"; + + } + +} diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index 5d63054..a1f5e44 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -92,17 +92,7 @@ namespace MewtocolNet.UnderlyingRegisters { TestPollLevelExistence(reg); - switch (reg.RegisterType) { - case RegisterPrefix.X: - case RegisterPrefix.Y: - case RegisterPrefix.R: - AddToWRArea(reg); - break; - case RegisterPrefix.DT: - case RegisterPrefix.DDT: - AddToDTArea(reg); - break; - } + AddToArea(reg, reg.RegisterType); } @@ -155,73 +145,33 @@ namespace MewtocolNet.UnderlyingRegisters { } - private bool AddToWRArea(Register insertReg) { - - var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel); - - List collection = null; - - switch (insertReg.RegisterType) { - case RegisterPrefix.X: - collection = pollLevelFound.externalRelayInAreas; - break; - case RegisterPrefix.Y: - collection = pollLevelFound.externalRelayOutAreas; - break; - case RegisterPrefix.R: - collection = pollLevelFound.internalRelayAreas; - break; - } - - WRArea area = collection.FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress); - - if (area != null) { - - var existingLinkedRegister = area.linkedRegisters - .FirstOrDefault(x => x.CompareIsDuplicate(insertReg)); - - if (existingLinkedRegister != null) { - - return false; - - } else { - - insertReg.underlyingMemory = area; - area.linkedRegisters.Add(insertReg); - return true; - - } - - } else { - - area = new WRArea(mewInterface) { - registerType = insertReg.RegisterType, - addressStart = insertReg.MemoryAddress, - }; - - insertReg.underlyingMemory = area; - area.linkedRegisters.Add(insertReg); - - collection.Add(area); - collection = collection.OrderBy(x => x.AddressStart).ToList(); - - return true; - - } - - } - - private void AddToDTArea(Register insertReg) { + private void AddToArea(Register insertReg, RegisterPrefix prefix) { uint regInsAddStart = insertReg.MemoryAddress; uint regInsAddEnd = insertReg.MemoryAddress + insertReg.GetRegisterAddressLen() - 1; - DTArea targetArea = null; + AreaBase targetArea = null; var pollLevelFound = pollLevels.FirstOrDefault(x => x.level == insertReg.pollLevel); - var dataAreas = pollLevelFound.dataAreas; + List pollLevelAreas = null; - foreach (var dtArea in dataAreas) { + switch (prefix) { + case RegisterPrefix.X: + pollLevelAreas = pollLevelFound.externalRelayInAreas; + break; + case RegisterPrefix.Y: + pollLevelAreas = pollLevelFound.externalRelayOutAreas; + break; + case RegisterPrefix.R: + pollLevelAreas = pollLevelFound.internalRelayAreas; + break; + case RegisterPrefix.DT: + case RegisterPrefix.DDT: + pollLevelAreas = pollLevelFound.dataAreas; + break; + } + + foreach (var dtArea in pollLevelAreas) { bool addressInsideArea = regInsAddStart >= dtArea.AddressStart && regInsAddEnd <= dtArea.AddressEnd; @@ -278,7 +228,7 @@ namespace MewtocolNet.UnderlyingRegisters { }; targetArea.BoundaryUdpdate(); - dataAreas.Add(targetArea); + pollLevelAreas.Add(targetArea); } @@ -358,7 +308,7 @@ namespace MewtocolNet.UnderlyingRegisters { } //update registers in poll level - foreach (var dtArea in pollLevel.dataAreas.ToArray()) { + foreach (var dtArea in pollLevel.GetAllAreas().ToArray()) { //set the whole memory area at once await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd); @@ -493,10 +443,9 @@ namespace MewtocolNet.UnderlyingRegisters { foreach (var lvl in pollLevels) { registers.AddRange(lvl.dataAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked)); - - registers.AddRange(lvl.internalRelayAreas.SelectMany(x => x.linkedRegisters)); - registers.AddRange(lvl.externalRelayInAreas.SelectMany(x => x.linkedRegisters)); - registers.AddRange(lvl.externalRelayOutAreas.SelectMany(x => x.linkedRegisters)); + registers.AddRange(lvl.internalRelayAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked)); + registers.AddRange(lvl.externalRelayInAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked)); + registers.AddRange(lvl.externalRelayOutAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked)); } diff --git a/MewtocolNet/UnderlyingRegisters/PollLevel.cs b/MewtocolNet/UnderlyingRegisters/PollLevel.cs index 25fe5c5..d70186d 100644 --- a/MewtocolNet/UnderlyingRegisters/PollLevel.cs +++ b/MewtocolNet/UnderlyingRegisters/PollLevel.cs @@ -6,30 +6,43 @@ namespace MewtocolNet.UnderlyingRegisters { 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; + internal List externalRelayInAreas; //Y WR - internal List externalRelayOutAreas; + internal List externalRelayOutAreas; //R WR - internal List internalRelayAreas; + internal List internalRelayAreas; //DT - internal List dataAreas; + internal List dataAreas; + + 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 IEnumerable GetAllAreas () { + + List combined = new List(); + + combined.AddRange(internalRelayAreas); + combined.AddRange(externalRelayInAreas); + combined.AddRange(externalRelayOutAreas); + combined.AddRange(dataAreas); + + return combined; + + } } diff --git a/MewtocolNet/UnderlyingRegisters/WRArea.cs b/MewtocolNet/UnderlyingRegisters/WRArea.cs deleted file mode 100644 index a1ebfb5..0000000 --- a/MewtocolNet/UnderlyingRegisters/WRArea.cs +++ /dev/null @@ -1,92 +0,0 @@ -using MewtocolNet.Registers; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace MewtocolNet.UnderlyingRegisters { - - public class WRArea : IMemoryArea { - - private MewtocolInterface mewInterface; - - internal RegisterPrefix registerType; - internal ulong addressStart; - - internal byte[] wordData = new byte[2]; - - internal List linkedRegisters = new List(); - - public ulong AddressStart => addressStart; - - internal WRArea(MewtocolInterface mewIf) { - - mewInterface = mewIf; - - } - - public void UpdateAreaRegisterValues() { - - - - } - public void SetUnderlyingBytes(Register reg, byte[] bytes) { - - - } - - public byte[] GetUnderlyingBytes(Register reg) { - - return null; - - } - - public async Task ReadRegisterAsync(Register reg) { - - return true; - - } - - public async Task WriteRegisterAsync(Register reg, byte[] bytes) { - - return true; - - } - - public string GetMewtocolIdent() => GetMewtocolIdentsAllBits(); - - public string GetMewtocolIdentsAllBits() { - - StringBuilder asciistring = new StringBuilder(); - - for (byte i = 0; i < 16; i++) { - - asciistring.Append(GetMewtocolIdentSingleBit(i)); - - } - - return asciistring.ToString(); - - } - - public string GetMewtocolIdentSingleBit(byte specialAddress) { - - //(R|X|Y)(area add [3] + special add [1]) - StringBuilder asciistring = new StringBuilder(); - - string prefix = registerType.ToString(); - string mem = AddressStart.ToString(); - string sp = specialAddress.ToString("X1"); - - asciistring.Append(prefix); - asciistring.Append(mem.PadLeft(3, '0')); - asciistring.Append(sp); - - return asciistring.ToString(); - - } - - public override string ToString() => $"{registerType}{AddressStart} 0-F"; - - } - -}