diff --git a/Examples/ExampleScenarios.cs b/Examples/ExampleScenarios.cs index d4c82d6..24dcd95 100644 --- a/Examples/ExampleScenarios.cs +++ b/Examples/ExampleScenarios.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using MewtocolNet.Registers; using System.Diagnostics; using System.Text; +using Microsoft.Win32; namespace Examples; @@ -17,7 +18,7 @@ public class ExampleScenarios { public void SetupLogger () { //attaching the logger - Logger.LogLevel = LogLevel.Info; + Logger.LogLevel = LogLevel.Error; Logger.OnNewLogMessage((date, level, msg) => { if (level == LogLevel.Error) Console.ForegroundColor = ConsoleColor.Red; @@ -46,6 +47,7 @@ public class ExampleScenarios { interf.WithRegisterCollection(registers).WithPoller(); await interf.ConnectAsync(); + await interf.AwaitFirstDataAsync(); _ = Task.Factory.StartNew(async () => { @@ -54,22 +56,18 @@ public class ExampleScenarios { //flip the bool register each tick and wait for it to be registered //await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1); - Console.Title = $"Polling Paused: {interf.PollingPaused}, " + - $"Poller active: {interf.PollerActive}, " + + Console.Title = $"Speed UP: {interf.BytesPerSecondUpstream} B/s, " + $"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " + - $"Poll delay: {interf.PollerDelayMs} ms, " + + $"Poll cycle: {interf.PollerCycleDurationMs} ms, " + $"Queued MSGs: {interf.QueuedMessages}"; Console.Clear(); Console.WriteLine("Underlying registers on tick: \n"); - foreach (var register in interf.Registers) { - + foreach (var register in interf.Registers) Console.WriteLine($"{register.ToString(true)}"); - } - Console.WriteLine($"{registers.TestBool1}"); Console.WriteLine($"{registers.TestDuplicate}"); @@ -192,12 +190,12 @@ public class ExampleScenarios { public async Task RunReadTest () { //setting up a new PLC interface and register collection - MewtocolInterface interf = new MewtocolInterface("192.168.115.210"); + MewtocolInterface interf = new MewtocolInterface("192.168.115.210").WithPoller(); //auto add all built registers to the interface var builder = RegBuilder.ForInterface(interf); var r0reg = builder.FromPlcRegName("R0").Build(); - builder.FromPlcRegName("R1").Build(); + builder.FromPlcRegName("R1", "Testname").Build(); builder.FromPlcRegName("R1F").Build(); builder.FromPlcRegName("R101A").Build(); @@ -205,35 +203,57 @@ public class ExampleScenarios { builder.FromPlcRegName("DDT36").AsPlcType(PlcVarType.DINT).Build(); builder.FromPlcRegName("DT200").AsBytes(30).Build(); - builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build(); - //builder.FromPlcRegName("DT40").AsPlcType(PlcVarType.STRING).Build(); + var timeReg = builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build(); + var stringReg = builder.FromPlcRegName("DT40").AsPlcType(PlcVarType.STRING).Build(); //connect await interf.ConnectAsync(); - //var res = await interf.SendCommandAsync("%01#RCSR000F"); + //await first register data + await interf.AwaitFirstDataAsync(); - while(true) { + _ = Task.Factory.StartNew(async () => { - await interf.SetRegisterAsync(r0reg, !(bool)r0reg.Value); - await interf.SetRegisterAsync(shortReg, (short)new Random().Next(0, 100)); + void setTitle () { - var sw = Stopwatch.StartNew(); - - foreach (var reg in interf.Registers) { - - await reg.ReadAsync(); - - Console.WriteLine($"Register {reg.ToString()}"); + Console.Title = + $"Speed UP: {interf.BytesPerSecondUpstream} B/s, " + + $"Speed DOWN: {interf.BytesPerSecondDownstream} B/s, " + + $"Poll cycle: {interf.PollerCycleDurationMs} ms, " + + $"Queued MSGs: {interf.QueuedMessages}"; } + while (interf.IsConnected) { + setTitle(); + await Task.Delay(1000); + } + + setTitle(); + + }); + + while (interf.IsConnected) { + + var sw = Stopwatch.StartNew(); + + //set bool + await r0reg.WriteAsync(!(bool)r0reg.Value); + + //set random num + await shortReg.WriteAsync((short)new Random().Next(0, 100)); + await stringReg.WriteAsync($"_{DateTime.Now.Second}s_"); + sw.Stop(); - Console.WriteLine($"Total read time for registers: {sw.Elapsed.TotalMilliseconds:N0}ms"); + foreach (var reg in interf.Registers) + Console.WriteLine(reg.ToString()); + + Console.WriteLine($"Total write time for registers: {sw.Elapsed.TotalMilliseconds:N0}ms"); Console.WriteLine(); + //await Task.Delay(new Random().Next(0, 10000)); await Task.Delay(1000); } @@ -244,28 +264,24 @@ public class ExampleScenarios { public async Task MultiFrameTest() { var preLogLevel = Logger.LogLevel; - Logger.LogLevel = LogLevel.Error; + Logger.LogLevel = LogLevel.Critical; //setting up a new PLC interface and register collection - MewtocolInterface interf = new MewtocolInterface("192.168.115.210"); + MewtocolInterface interf = new MewtocolInterface("192.168.115.210") { + ConnectTimeout = 3000, + }; //auto add all built registers to the interface var builder = RegBuilder.ForInterface(interf); var r0reg = builder.FromPlcRegName("R0").Build(); builder.FromPlcRegName("R1").Build(); - builder.FromPlcRegName("R1F").Build(); - builder.FromPlcRegName("R60A").Build(); - builder.FromPlcRegName("R61A").Build(); - builder.FromPlcRegName("R62A").Build(); - builder.FromPlcRegName("R63A").Build(); - builder.FromPlcRegName("R64A").Build(); - builder.FromPlcRegName("R65A").Build(); - builder.FromPlcRegName("R66A").Build(); - builder.FromPlcRegName("R67A").Build(); - builder.FromPlcRegName("R68A").Build(); - builder.FromPlcRegName("R69A").Build(); - builder.FromPlcRegName("R70A").Build(); - builder.FromPlcRegName("R71A").Build(); + builder.FromPlcRegName("DT0").AsBytes(100).Build(); + + for (int i = 1; i < 100; i++) { + + builder.FromPlcRegName($"R{i}A").Build(); + + } //connect await interf.ConnectAsync(); @@ -273,36 +289,18 @@ public class ExampleScenarios { Console.WriteLine("Poller cycle started"); var sw = Stopwatch.StartNew(); - await interf.RunPollerCylceManual(false); + int cmdCount = await interf.RunPollerCylceManual(); sw.Stop(); Console.WriteLine("Poller cycle finished"); - Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms"); + Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms for {cmdCount} commands"); interf.Disconnect(); await Task.Delay(1000); - await interf.ConnectAsync(); - - sw = Stopwatch.StartNew(); - - Console.WriteLine("Poller cycle started"); - - await interf.RunPollerCylceManual(true); - - sw.Stop(); - - Console.WriteLine("Poller cycle finished"); - - Console.WriteLine($"Multi frame excec time: {sw.ElapsedMilliseconds:N0}ms"); - - Logger.LogLevel = preLogLevel; - - await Task.Delay(10000); - } } diff --git a/MewtocolNet/DynamicInterface.cs b/MewtocolNet/DynamicInterface.cs index 357bd52..2344bba 100644 --- a/MewtocolNet/DynamicInterface.cs +++ b/MewtocolNet/DynamicInterface.cs @@ -5,6 +5,7 @@ using MewtocolNet.Registers; using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; @@ -19,25 +20,32 @@ namespace MewtocolNet /// public partial class MewtocolInterface { - /// - /// True if the auto poller is currently paused - /// - public bool PollingPaused => pollerIsPaused; + internal event Action PolledCycle; + + internal volatile bool pollerTaskStopped = true; + internal volatile bool pollerFirstCycle; + + internal bool usePoller = false; + + private int tcpMessagesSentThisCycle = 0; + + private int pollerCycleDurationMs; /// /// True if the poller is actvice (can be paused) /// public bool PollerActive => !pollerTaskStopped; - internal event Action PolledCycle; - - internal volatile bool pollerTaskRunning; - internal volatile bool pollerTaskStopped; - internal volatile bool pollerIsPaused; - internal volatile bool pollerFirstCycle = false; - - internal bool usePoller = false; - internal bool pollerUseMultiFrame = false; + /// + /// Current poller cycle duration + /// + public int PollerCycleDurationMs { + get => pollerCycleDurationMs; + private set { + pollerCycleDurationMs = value; + OnPropChange(); + } + } #region Register Polling @@ -46,54 +54,20 @@ namespace MewtocolNet /// internal void KillPoller() { - pollerTaskRunning = false; pollerTaskStopped = true; - ClearRegisterVals(); } - /// - /// Pauses the polling and waits for the last message to be sent - /// - /// - public async Task PausePollingAsync() { - - if (!pollerTaskRunning) - return; - - pollerTaskRunning = false; - - while (!pollerIsPaused) { - - if (pollerIsPaused) - break; - - await Task.Delay(10); - - } - - pollerTaskRunning = false; - - } - - /// - /// Resumes the polling - /// - public void ResumePolling() { - - pollerTaskRunning = true; - - } - /// /// Attaches a continous reader that reads back the Registers and Contacts /// internal void AttachPoller() { - if (pollerTaskRunning) + if (!pollerTaskStopped) return; + PollerCycleDurationMs = 0; pollerFirstCycle = true; Task.Run(Poll); @@ -104,14 +78,18 @@ namespace MewtocolNet /// Runs a single poller cycle manually, /// useful if you want to use a custom update frequency /// - /// - public async Task RunPollerCylceManual (bool useMultiFrame = false) { + /// The number of inidvidual mewtocol commands sent + public async Task RunPollerCylceManual () { - if (useMultiFrame) { - await OnMultiFrameCycle(); - } else { - await OnSingleFrameCycle(); - } + if (!pollerTaskStopped) + throw new NotSupportedException($"The poller is already running, " + + $"please make sure there is no polling active before calling {nameof(RunPollerCylceManual)}"); + + tcpMessagesSentThisCycle = 0; + + await OnMultiFrameCycle(); + + return tcpMessagesSentThisCycle; } @@ -120,78 +98,49 @@ namespace MewtocolNet Logger.Log("Poller is attaching", LogLevel.Info, this); - int iteration = 0; - pollerTaskStopped = false; - pollerTaskRunning = true; - pollerIsPaused = false; while (!pollerTaskStopped) { - if (iteration >= Registers.Count + 1) { - iteration = 0; - //invoke cycle polled event - InvokePolledCycleDone(); - continue; - } + tcpMessagesSentThisCycle = 0; - if(pollerUseMultiFrame) { - await OnMultiFrameCycle(); - } else { - await OnSingleFrameCycle(); + await OnMultiFrameCycle(); + + if(!IsConnected) { + pollerTaskStopped = true; + return; } pollerFirstCycle = false; - - iteration++; - - pollerIsPaused = !pollerTaskRunning; + InvokePolledCycleDone(); } - pollerIsPaused = false; - - } - - private async Task OnSingleFrameCycle () { - - foreach (var reg in Registers) { - - if (reg.IsAllowedRegisterGenericType()) { - - var lastVal = reg.Value; - - var rwReg = (IRegisterInternal)reg; - - var readout = await rwReg.ReadAsync(); - - if (lastVal != readout) { - - rwReg.SetValueFromPLC(readout); - InvokeRegisterChanged(reg); - - } - - } - - } - - await GetPLCInfoAsync(); - } private async Task OnMultiFrameCycle () { + var sw = Stopwatch.StartNew(); + await UpdateRCPRegisters(); + await UpdateDTRegisters(); + await GetPLCInfoAsync(); + sw.Stop(); + PollerCycleDurationMs = (int)sw.ElapsedMilliseconds; + } + #endregion + + #region Smart register polling methods + private async Task UpdateRCPRegisters () { //build booleans - var rcpList = Registers.Where(x => x.GetType() == typeof(BoolRegister)) + var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister)) .Select(x => (BoolRegister)x) .ToArray(); @@ -216,6 +165,8 @@ namespace MewtocolNet string rcpRequest = rcpString.ToString(); var result = await SendCommandAsync(rcpRequest); + if (!result.Success) return; + var resultBitArray = result.Response.ParseRCMultiBit(); for (int k = 0; k < resultBitArray.Length; k++) { @@ -233,9 +184,27 @@ namespace MewtocolNet } - internal void PropertyRegisterWasSet(string propName, object value) { + private async Task UpdateDTRegisters () { - _ = SetRegisterAsync(GetRegister(propName), value); + 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); + InvokeRegisterChanged(reg); + } + + } + + } } @@ -409,7 +378,7 @@ namespace MewtocolNet throw MewtocolException.DupeNameRegister(builtRegister); builtRegister.attachedInterface = this; - Registers.Add(builtRegister); + RegistersUnderlying.Add(builtRegister); } @@ -422,7 +391,7 @@ namespace MewtocolNet throw MewtocolException.DupeNameRegister(register); register.attachedInterface = this; - Registers.Add(register); + RegistersUnderlying.Add(register); } @@ -458,7 +427,7 @@ namespace MewtocolNet /// public IRegister GetRegister(string name) { - return Registers.FirstOrDefault(x => x.Name == name); + return RegistersUnderlying.FirstOrDefault(x => x.Name == name); } @@ -471,7 +440,7 @@ namespace MewtocolNet /// public IEnumerable GetAllRegisters() { - return Registers.Cast(); + return RegistersUnderlying.Cast(); } @@ -479,6 +448,12 @@ namespace MewtocolNet #region Event Invoking + internal void PropertyRegisterWasSet(string propName, object value) { + + _ = SetRegisterAsync(GetRegister(propName), value); + + } + internal void InvokeRegisterChanged(IRegister reg) { RegisterChanged?.Invoke(reg); diff --git a/MewtocolNet/IRegister.cs b/MewtocolNet/IRegister.cs index 6bd6bca..18aa8d8 100644 --- a/MewtocolNet/IRegister.cs +++ b/MewtocolNet/IRegister.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Threading.Tasks; namespace MewtocolNet { @@ -38,6 +39,12 @@ namespace MewtocolNet { /// int MemoryAddress { get; } + /// + /// Gets the value of the register as the plc representation string + /// + /// + string GetAsPLC(); + /// /// Builds a readable string with all important register informations /// @@ -49,16 +56,17 @@ namespace MewtocolNet { string ToString(bool detailed); /// - /// Sets the register value in the plc async + /// Reads the register value async from the plc /// - /// True if successful - Task SetValueAsync(); + /// The register value + Task ReadAsync(); /// - /// Gets the register value from the plc async + /// Writes the register content async to the plc /// - /// The value or null if failed - Task GetValueAsync(); + /// True if successfully set + Task WriteAsync(object data); + } diff --git a/MewtocolNet/MewtocolFrameResponse.cs b/MewtocolNet/MewtocolFrameResponse.cs new file mode 100644 index 0000000..a381336 --- /dev/null +++ b/MewtocolNet/MewtocolFrameResponse.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MewtocolNet { + + public struct MewtocolFrameResponse { + + public bool Success { get; private set; } + + public string Response { get; private set; } + + public int ErrorCode { get; private set; } + + public string Error { get; private set; } + + public MewtocolFrameResponse (string response) { + + Success = true; + ErrorCode = 0; + Response = response; + Error = null; + + } + + public MewtocolFrameResponse(int errorCode) { + + Success = false; + Response = null; + ErrorCode = errorCode; + Error = CodeDescriptions.Error[errorCode]; + + } + + public MewtocolFrameResponse(int errorCode, string exceptionMsg) { + + Success = false; + Response = null; + ErrorCode = errorCode; + Error = exceptionMsg; + + } + + } + +} diff --git a/MewtocolNet/MewtocolHelpers.cs b/MewtocolNet/MewtocolHelpers.cs index 38c0285..99be553 100644 --- a/MewtocolNet/MewtocolHelpers.cs +++ b/MewtocolNet/MewtocolHelpers.cs @@ -13,6 +13,37 @@ namespace MewtocolNet { /// public static class MewtocolHelpers { + #region Value PLC Humanizers + + /// + /// Gets the TimeSpan as a PLC representation string fe. + /// + /// T#1h10m30s20ms + /// + /// + /// + /// + public static string AsPLCTime (this TimeSpan timespan) { + + if (timespan == null || timespan == TimeSpan.Zero) + return $"T#0s"; + + StringBuilder sb = new StringBuilder("T#"); + + int millis = timespan.Milliseconds; + int seconds = timespan.Seconds; + int minutes = timespan.Minutes; + int hours = timespan.Hours; + + if (hours > 0) sb.Append($"{hours}h"); + if (minutes > 0) sb.Append($"{minutes}m"); + if (seconds > 0) sb.Append($"{seconds}s"); + if (millis > 0) sb.Append($"{millis}ms"); + + return sb.ToString(); + + } + /// /// Turns a bit array into a 0 and 1 string /// @@ -24,6 +55,10 @@ namespace MewtocolNet { } + #endregion + + #region Byte and string operation helpers + /// /// Converts a string (after converting to upper case) to ascii bytes /// @@ -35,7 +70,12 @@ namespace MewtocolNet { } - internal static string BuildBCCFrame(this string asciiArr) { + /// + /// Builds the BCC / Checksum for the mewtocol command + /// + /// The mewtocol command (%01#RCS0001) + /// The mewtocol command with the appended checksum + public static string BuildBCCFrame(this string asciiArr) { Encoding ae = Encoding.ASCII; byte[] b = ae.GetBytes(asciiArr); @@ -63,8 +103,13 @@ namespace MewtocolNet { } + /// + /// Parses a return message as RCS single bit + /// internal static bool? ParseRCSingleBit(this string _onString) { + _onString = _onString.Replace("\r", ""); + var res = new Regex(@"\%([0-9]{2})\$RC(.)").Match(_onString); if (res.Success) { string val = res.Groups[2].Value; @@ -74,8 +119,13 @@ namespace MewtocolNet { } + /// + /// Parses a return message as RCS multiple bits + /// internal static BitArray ParseRCMultiBit(this string _onString) { + _onString = _onString.Replace("\r", ""); + var res = new Regex(@"\%([0-9]{2})\$RC(?(?:0|1){0,8})(..)").Match(_onString); if (res.Success) { @@ -124,7 +174,7 @@ namespace MewtocolNet { } /// - /// Splits a string in even parts + /// Splits a string into even parts /// internal static IEnumerable SplitInParts(this string s, int partLength) { @@ -138,9 +188,11 @@ namespace MewtocolNet { } + /// + /// Converts a hex string (AB01C1) to a byte array + /// internal static byte[] HexStringToByteArray (this string hex) { - if (hex == null) - return null; + if (hex == null) return null; return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) @@ -166,24 +218,9 @@ namespace MewtocolNet { } - internal static string AsPLC (this TimeSpan timespan) { - - StringBuilder sb = new StringBuilder("T#"); - - int millis = timespan.Milliseconds; - int seconds = timespan.Seconds; - int minutes = timespan.Minutes; - int hours = timespan.Hours; - - if (hours > 0) sb.Append($"{hours}h"); - if (minutes > 0) sb.Append($"{minutes}m"); - if (seconds > 0) sb.Append($"{seconds}s"); - if (millis > 0) sb.Append($"{millis}ms"); - - return sb.ToString(); - - } - + /// + /// Switches byte order from mixed to big endian + /// internal static byte[] BigToMixedEndian(this byte[] arr) { List oldBL = new List(arr); @@ -206,6 +243,10 @@ namespace MewtocolNet { } + #endregion + + #region Comparerers + /// /// Checks if the register type is boolean /// @@ -249,6 +290,8 @@ namespace MewtocolNet { } + #endregion + } } \ No newline at end of file diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 98a3aa4..cb48916 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -7,11 +7,13 @@ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.ComponentModel.Design; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; +using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -52,18 +54,6 @@ namespace MewtocolNet { set { connectTimeout = value; } } - private volatile int pollerDelayMs = 0; - /// - /// Delay for each poller cycle in milliseconds, default = 0 - /// - public int PollerDelayMs { - get => pollerDelayMs; - set { - pollerDelayMs = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PollerDelayMs))); - } - } - private volatile int queuedMessages; /// /// Currently queued Messages @@ -114,9 +104,11 @@ namespace MewtocolNet { /// /// The registered data registers of the PLC /// - public List Registers { get; private set; } = new List(); + internal List RegistersUnderlying { get; private set; } = new List(); - internal IEnumerable RegistersInternal => Registers.Cast(); + public IEnumerable Registers => RegistersUnderlying.Cast(); + + internal IEnumerable RegistersInternal => RegistersUnderlying.Cast(); private string ip; private int port; @@ -184,6 +176,8 @@ namespace MewtocolNet { private Stopwatch speedStopwatchUpstr; private Stopwatch speedStopwatchDownstr; + private Task firstPollTask = new Task(() => { }); + #region Initialization /// @@ -238,6 +232,8 @@ namespace MewtocolNet { /// public async Task ConnectAsync(Action OnConnected = null, Action OnFailed = null) { + firstPollTask = new Task(() => { }); + Logger.Log("Connecting to PLC...", LogLevel.Info, this); var plcinf = await GetPLCInfoAsync(); @@ -249,18 +245,19 @@ namespace MewtocolNet { Connected?.Invoke(plcinf); - if (OnConnected != null) { + if (!usePoller) { + if (OnConnected != null) OnConnected(plcinf); + firstPollTask.RunSynchronously(); + return this; + } - if (!usePoller) { - OnConnected(plcinf); - return this; - } + PolledCycle += OnPollCycleDone; + void OnPollCycleDone() { + + if (OnConnected != null) OnConnected(plcinf); + firstPollTask.RunSynchronously(); + PolledCycle -= OnPollCycleDone; - PolledCycle += OnPollCycleDone; - void OnPollCycleDone() { - OnConnected(plcinf); - PolledCycle -= OnPollCycleDone; - } } } else { @@ -268,6 +265,7 @@ namespace MewtocolNet { if (OnFailed != null) { OnFailed(); Disconnected?.Invoke(); + firstPollTask.RunSynchronously(); Logger.Log("Initial connection failed", LogLevel.Info, this); } @@ -277,6 +275,12 @@ namespace MewtocolNet { } + /// + /// Use this to await the first poll iteration after connecting, + /// This also completes if the initial connection fails + /// + public async Task AwaitFirstDataAsync () => await firstPollTask; + /// /// Changes the connections parameters of the PLC, only applyable when the connection is offline /// @@ -310,10 +314,9 @@ namespace MewtocolNet { /// Attaches a poller to the interface that continously /// polls the registered data registers and writes the values to them /// - public MewtocolInterface WithPoller(bool useMultiFrame = false) { + public MewtocolInterface WithPoller() { usePoller = true; - pollerUseMultiFrame = useMultiFrame; return this; } @@ -375,41 +378,6 @@ namespace MewtocolNet { } - private void OnMajorSocketExceptionWhileConnecting() { - - Logger.Log("The PLC connection timed out", LogLevel.Error, this); - CycleTimeMs = 0; - IsConnected = false; - KillPoller(); - - } - - private void OnMajorSocketExceptionWhileConnected() { - - if (IsConnected) { - - Logger.Log("The PLC connection was closed", LogLevel.Error, this); - CycleTimeMs = 0; - IsConnected = false; - Disconnected?.Invoke(); - KillPoller(); - client.Close(); - - } - - } - - private void ClearRegisterVals() { - - for (int i = 0; i < Registers.Count; i++) { - - var reg = (IRegisterInternal)Registers[i]; - reg.ClearValue(); - - } - - } - #endregion #region Low level command handling @@ -418,186 +386,221 @@ namespace MewtocolNet { /// Calculates the checksum automatically and sends a command to the PLC then awaits results /// /// MEWTOCOL Formatted request string ex: %01#RT + /// Append the checksum and bcc automatically /// Returns the result - public async Task SendCommandAsync(string _msg) { - - _msg = _msg.BuildBCCFrame(); - _msg += "\r"; + public async Task SendCommandAsync(string _msg, bool withTerminator = true) { //send request - try { + queuedMessages++; + var tempResponse = await queue.Enqueue(() => SendFrameAsync(_msg, withTerminator, withTerminator)); - queuedMessages++; + tcpMessagesSentThisCycle++; + queuedMessages--; - TCPMessageResult tcpResult = TCPMessageResult.Waiting; - string response = ""; - - int lineFeedFails = 0; - - //recursively try to get a response on failed line feeds - while (tcpResult == TCPMessageResult.Waiting || tcpResult == TCPMessageResult.FailedLineFeed) { - - if (lineFeedFails >= 5) - throw new MewtocolException($"The message ${_msg} had {lineFeedFails} linefeed fails"); - - var tempResponse = await queue.Enqueue(() => SendSingleBlock(_msg)); - tcpResult = tempResponse.Item1; - response = tempResponse.Item2; - - if(tcpResult == TCPMessageResult.FailedLineFeed) { - lineFeedFails++; - Logger.Log($"Linefeed fail, retrying...", LogLevel.Error); - } - - if (queuedMessages > 0) - queuedMessages--; - - if (tcpResult == TCPMessageResult.FailedWithException) - throw new MewtocolException("The connection to the device was terminated"); - - } - - //error catching - Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase); - Match m = errorcheck.Match(response); - - if (m.Success) { - string eCode = m.Groups[1].Value; - string eDes = CodeDescriptions.Error[Convert.ToInt32(eCode)]; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Response is: {response}"); - Logger.Log($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}", LogLevel.Error); - Console.ResetColor(); - return new CommandResult { - Success = false, - Error = eCode, - ErrorDescription = eDes - }; - } - - return new CommandResult { - Success = true, - Error = "0000", - Response = response, - }; - - } catch { - return new CommandResult { - Success = false, - Error = "0000", - ErrorDescription = "null result" - }; - } + return tempResponse; } - private async Task<(TCPMessageResult, string)> SendSingleBlock(string _blockString) { + private async Task SendFrameAsync (string frame, bool useBcc = true, bool useCr = true) { - if (client == null || !client.Connected) { - await ConnectTCP(); - } - - if (client == null || !client.Connected) - return (TCPMessageResult.NotConnected, null); - - var message = _blockString.BytesFromHexASCIIString(); - - //time measuring - if (speedStopwatchUpstr == null) { - speedStopwatchUpstr = Stopwatch.StartNew(); - } - - if (speedStopwatchUpstr.Elapsed.TotalSeconds >= 1) { - speedStopwatchUpstr.Restart(); - bytesTotalCountedUpstream = 0; - } - - //send request - using (var sendStream = new MemoryStream(message)) { - await sendStream.CopyToAsync(stream); - Logger.Log($"[--------------------------------]", LogLevel.Critical, this); - Logger.Log($"--> OUT MSG: {_blockString}", LogLevel.Critical, this); - } - - //calc upstream speed - bytesTotalCountedUpstream += message.Length; - - var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000); - if (perSecUpstream <= 10000) - BytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero); - - //await result - StringBuilder response = new StringBuilder(); try { - byte[] responseBuffer = new byte[128 * 16]; + //stop time + if (speedStopwatchUpstr == null) { + speedStopwatchUpstr = Stopwatch.StartNew(); + } - bool endLineCode = false; - bool startMsgCode = false; + if (speedStopwatchUpstr.Elapsed.TotalSeconds >= 1) { + speedStopwatchUpstr.Restart(); + bytesTotalCountedUpstream = 0; + } - while (!endLineCode && !startMsgCode) { + const char CR = '\r'; + const char DELIMITER = '&'; - do { + if (client == null || !client.Connected) await ConnectTCP(); - //time measuring - if (speedStopwatchDownstr == null) { - speedStopwatchDownstr = Stopwatch.StartNew(); - } + if (useBcc) + frame = $"{frame.BuildBCCFrame()}"; - if (speedStopwatchDownstr.Elapsed.TotalSeconds >= 1) { - speedStopwatchDownstr.Restart(); - bytesTotalCountedDownstream = 0; - } + if (useCr) + frame = $"{frame}\r"; - int bytes = await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length); + //write inital command + byte[] writeBuffer = Encoding.UTF8.GetBytes(frame); + await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length); - endLineCode = responseBuffer.Any(x => x == 0x0D); - startMsgCode = responseBuffer.Count(x => x == 0x25) > 1; + //calc upstream speed + bytesTotalCountedUpstream += writeBuffer.Length; - if (!endLineCode && !startMsgCode) break; + var perSecUpstream = (double)((bytesTotalCountedUpstream / speedStopwatchUpstr.Elapsed.TotalMilliseconds) * 1000); + if (perSecUpstream <= 10000) + BytesPerSecondUpstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero); + + + Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this); + Logger.Log($"--> OUT MSG: {frame.Replace("\r", "(CR)")}", LogLevel.Critical, this); + + //read + List totalResponse = new List(); + byte[] responseBuffer = new byte[512]; + + bool wasMultiFramedResponse = false; + CommandState cmdState = CommandState.Intial; + + //read until command complete + while (cmdState != CommandState.Complete) { + + //time measuring + if (speedStopwatchDownstr == null) { + speedStopwatchDownstr = Stopwatch.StartNew(); + } + + if (speedStopwatchDownstr.Elapsed.TotalSeconds >= 1) { + speedStopwatchDownstr.Restart(); + bytesTotalCountedDownstream = 0; + } + + responseBuffer = new byte[128]; + + await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length); + + bool terminatorReceived = responseBuffer.Any(x => x == (byte)CR); + var delimiterTerminatorIdx = SearchBytePattern(responseBuffer, new byte[] { (byte)DELIMITER, (byte)CR }); + + if (terminatorReceived && delimiterTerminatorIdx == -1) { + cmdState = CommandState.Complete; + } else if (delimiterTerminatorIdx != -1) { + cmdState = CommandState.RequestedNextFrame; + } else { + cmdState = CommandState.LineFeed; + } + + //log message parts + var tempMsg = Encoding.UTF8.GetString(responseBuffer).Replace("\r", "(CR)"); + Logger.Log($">> IN PART: {tempMsg}, Command state: {cmdState}", LogLevel.Critical, this); + + //error response + int errorCode = CheckForErrorMsg(tempMsg); + if (errorCode != 0) return new MewtocolFrameResponse(errorCode); + + //add complete response to collector without empty bytes + totalResponse.AddRange(responseBuffer.Where(x => x != (byte)0x0)); + + //request next part of the command if the delimiter was received + if (cmdState == CommandState.RequestedNextFrame) { + + Logger.Log($"Requesting next frame...", LogLevel.Critical, this); + + wasMultiFramedResponse = true; + writeBuffer = Encoding.UTF8.GetBytes("%01**&\r"); + await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length); - response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes)); } - while (stream.DataAvailable); } - } catch (IOException) { - OnMajorSocketExceptionWhileConnected(); - return (TCPMessageResult.FailedWithException, null); - } catch (SocketException) { - OnMajorSocketExceptionWhileConnected(); - return (TCPMessageResult.FailedWithException, null); - } + //build final result + string resString = Encoding.UTF8.GetString(totalResponse.ToArray()); + if (wasMultiFramedResponse) { - string resString = response.ToString(); + var split = resString.Split('&'); - if (!string.IsNullOrEmpty(resString) && resString != "\r" ) { + for (int j = 0; j < split.Length; j++) { - Logger.Log($"<-- IN MSG: {response}", LogLevel.Critical, this); + split[j] = split[j].Replace("\r", ""); + split[j] = split[j].Substring(0, split[j].Length - 2); + if (j > 0) split[j] = split[j].Replace($"%{GetStationNumber()}", ""); - bytesTotalCountedDownstream += Encoding.ASCII.GetByteCount(response.ToString()); + } + + resString = string.Join("", split); + + } + + bytesTotalCountedDownstream += Encoding.ASCII.GetByteCount(resString); var perSecDownstream = (double)((bytesTotalCountedDownstream / speedStopwatchDownstr.Elapsed.TotalMilliseconds) * 1000); if (perSecUpstream <= 10000) BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero); - return (TCPMessageResult.Success, resString); + Logger.Log($"<-- IN MSG: {resString.Replace("\r", "(CR)")}", LogLevel.Critical, this); + Logger.Log($"Total bytes parsed: {resString.Length}", LogLevel.Critical, this); + Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this); - } else { + return new MewtocolFrameResponse(resString); - return (TCPMessageResult.FailedLineFeed, null); + } catch (Exception ex) { + + return new MewtocolFrameResponse(400, ex.Message.ToString(System.Globalization.CultureInfo.InvariantCulture)); } } + private int CheckForErrorMsg (string msg) { + + //error catching + Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase); + Match m = errorcheck.Match(msg); + + if (m.Success) { + + string eCode = m.Groups[1].Value; + return Convert.ToInt32(eCode); + + } + + return 0; + + } + + private int SearchBytePattern (byte[] src, byte[] pattern) { + + int maxFirstCharSlot = src.Length - pattern.Length + 1; + for (int i = 0; i < maxFirstCharSlot; i++) { + if (src[i] != pattern[0]) // compare only first byte + continue; + + // found a match on first byte, now try to match rest of the pattern + for (int j = pattern.Length - 1; j >= 1; j--) { + if (src[i + j] != pattern[j]) break; + if (j == 1) return i; + } + } + return -1; + + } + #endregion #region Disposing + private void OnMajorSocketExceptionWhileConnecting() { + + if (IsConnected) { + + Logger.Log("The PLC connection timed out", LogLevel.Error, this); + OnDisconnect(); + + } + + } + + private void OnMajorSocketExceptionWhileConnected() { + + if (IsConnected) { + + Logger.Log("The PLC connection was closed", LogLevel.Error, this); + OnDisconnect(); + + } + + } + + /// /// Disposes the current interface and clears all its members /// @@ -607,12 +610,43 @@ namespace MewtocolNet { Disconnect(); - GC.SuppressFinalize(this); + //GC.SuppressFinalize(this); Disposed = true; } + private void OnDisconnect () { + + if (IsConnected) { + + BytesPerSecondDownstream = 0; + BytesPerSecondUpstream = 0; + CycleTimeMs = 0; + + IsConnected = false; + ClearRegisterVals(); + + Disconnected?.Invoke(); + KillPoller(); + client.Close(); + + } + + } + + + private void ClearRegisterVals() { + + for (int i = 0; i < RegistersUnderlying.Count; i++) { + + var reg = (IRegisterInternal)RegistersUnderlying[i]; + reg.ClearValue(); + + } + + } + #endregion #region Accessing Info @@ -628,6 +662,20 @@ namespace MewtocolNet { #endregion + #region Property change evnts + + /// + /// Triggers a property changed event + /// + /// Name of the property to trigger for + private void OnPropChange ([CallerMemberName]string propertyName = null) { + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + } + + #endregion + } } \ No newline at end of file diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 3b0f4f9..54137bd 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -48,7 +48,9 @@ namespace MewtocolNet { return retInfo; } + return null; + } #endregion @@ -89,7 +91,7 @@ namespace MewtocolNet { /// /// start address of the array /// /// - public async Task WriteByteRange(int start, byte[] byteArr) { + public async Task WriteByteRange (int start, byte[] byteArr) { string byteString = byteArr.BigToMixedEndian().ToHexString(); var wordLength = byteArr.Length / 2; @@ -111,9 +113,10 @@ namespace MewtocolNet { /// /// Start adress /// Number of bytes to get + /// Flips bytes from big to mixed endian /// Gets invoked when the progress changes, contains the progress as a double /// A byte array or null of there was an error - public async Task ReadByteRange(int start, int count, Action onProgress = null) { + public async Task ReadByteRange(int start, int count, bool flipBytes = true, Action onProgress = null) { var byteList = new List(); @@ -138,11 +141,13 @@ namespace MewtocolNet { var bytes = result.Response.ParseDTByteString(8 * 4).HexStringToByteArray(); - if (bytes == null) { - return null; - } + if (bytes == null) return null; - byteList.AddRange(bytes.BigToMixedEndian().Take(count).ToArray()); + if (flipBytes) { + byteList.AddRange(bytes.BigToMixedEndian().Take(count).ToArray()); + } else { + byteList.AddRange(bytes.Take(count).ToArray()); + } } @@ -168,6 +173,7 @@ namespace MewtocolNet { string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}"; var result = await SendCommandAsync(requeststring); + if (!result.Success) return null; var resultBool = result.Response.ParseRCSingleBit(); if (resultBool != null) { @@ -183,9 +189,7 @@ namespace MewtocolNet { string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; var result = await SendCommandAsync(requeststring); - - if (!result.Success) - throw new Exception($"Failed to load the byte data for: {_toRead}"); + if (!result.Success) return null; if(_toRead.RegisterType == RegisterType.DT) { @@ -204,9 +208,19 @@ namespace MewtocolNet { string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; var result = await SendCommandAsync(requeststring); + if (!result.Success) return null; - if (!result.Success) - throw new Exception($"Failed to load the byte data for: {_toRead}"); + var resBytes = result.Response.ParseDTRawStringAsBytes(); + + return resBytes; + + } + + if (toreadType == typeof(StringRegister)) { + + string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; + var result = await SendCommandAsync(requeststring); + if (!result.Success) return null; var resBytes = result.Response.ParseDTRawStringAsBytes(); @@ -220,8 +234,10 @@ namespace MewtocolNet { internal async Task WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) { + var toWriteType = _toWrite.GetType(); + //returns a byte array 1 long and with the byte beeing 0 or 1 - if (_toWrite.GetType() == typeof(BoolRegister)) { + if (toWriteType == typeof(BoolRegister)) { string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}"; var result = await SendCommandAsync(requeststring); @@ -229,8 +245,8 @@ namespace MewtocolNet { } - //returns a byte array 2 bytes or 4 bytes long depending on the data size - if (_toWrite.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) { + //writes a byte array 2 bytes or 4 bytes long depending on the data size + if (toWriteType.IsGenericType && toWriteType.GetGenericTypeDefinition() == typeof(NumberRegister<>)) { string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}"; var result = await SendCommandAsync(requeststring); @@ -239,15 +255,18 @@ namespace MewtocolNet { } //returns a byte array with variable size - if (_toWrite.GetType() == typeof(BytesRegister)) { + if (toWriteType == typeof(BytesRegister)) { - //string stationNum = GetStationNumber(); - //string dataString = gotBytes.BuildDTString(_toWrite.ReservedSize); - //string dataArea = _toWrite.BuildCustomIdent(dataString.Length / 4); + throw new NotImplementedException("Not imp"); - //string requeststring = $"%{stationNum}#WD{dataArea}{dataString}"; + } - //var result = await SendCommandAsync(requeststring); + //writes to the string area + if (toWriteType == typeof(StringRegister)) { + + string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}"; + var result = await SendCommandAsync(requeststring); + return result.Success; } @@ -259,7 +278,7 @@ namespace MewtocolNet { #region Register reading / writing - public async Task SetRegisterAsync (IRegister register, object value) { + internal async Task SetRegisterAsync (IRegister register, object value) { var internalReg = (IRegisterInternal)register; diff --git a/MewtocolNet/MewtocolNet.csproj b/MewtocolNet/MewtocolNet.csproj index c296329..8c6901f 100644 --- a/MewtocolNet/MewtocolNet.csproj +++ b/MewtocolNet/MewtocolNet.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;net6.0; Mewtocol.NET 0.7.1 Felix Weiss diff --git a/MewtocolNet/RegisterBuildInfo.cs b/MewtocolNet/RegisterBuildInfo.cs index 4ad24f6..4eda284 100644 --- a/MewtocolNet/RegisterBuildInfo.cs +++ b/MewtocolNet/RegisterBuildInfo.cs @@ -3,8 +3,7 @@ using System; using System.Collections; using System.Reflection; -namespace MewtocolNet -{ +namespace MewtocolNet { internal struct RegisterBuildInfo { @@ -24,24 +23,21 @@ namespace MewtocolNet Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType(); bool isBytesRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister); + bool isStringRegister = !registerClassType.IsGenericType && registerClassType == typeof(StringRegister); if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool))) { //------------------------------------------- //as numeric register with boolean bit target //create a new bregister instance - var flags = BindingFlags.Public | BindingFlags.Instance; - - //int _adress, int _reservedByteSize, string _name = null - var parameters = new object[] { memoryAddress, memorySizeBytes, name }; - var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null); + var instance = new BytesRegister(memoryAddress, memorySizeBytes, name); if (collectionType != null) instance.WithCollectionType(collectionType); return instance; - } else if (regType.IsNumericDTDDT() && !isBytesRegister) { + } else if (regType.IsNumericDTDDT() && !isBytesRegister && !isStringRegister) { //------------------------------------------- //as numeric register @@ -66,12 +62,20 @@ namespace MewtocolNet //------------------------------------------- //as byte range register + var instance = new BytesRegister(memoryAddress, memorySizeBytes, name); - //create a new bregister instance - var flags = BindingFlags.Public | BindingFlags.Instance; - //int _adress, int _reservedSize, string _name = null - var parameters = new object[] { memoryAddress, memorySizeBytes, name }; - var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null); + if (collectionType != null) + instance.WithCollectionType(collectionType); + + return instance; + + } + + if (isStringRegister) { + + //------------------------------------------- + //as byte range register + var instance = (BaseRegister)new StringRegister(memoryAddress, name); if (collectionType != null) instance.WithCollectionType(collectionType); @@ -89,10 +93,7 @@ namespace MewtocolNet var spAddr = specialAddress; var areaAddr = memoryAddress; - //create a new bregister instance - var flags = BindingFlags.Public | BindingFlags.Instance; - var parameters = new object[] { io, spAddr.Value, areaAddr, name }; - var instance = (BoolRegister)Activator.CreateInstance(typeof(BoolRegister), flags, null, parameters, null); + var instance = new BoolRegister(io, spAddr.Value, areaAddr, name); if (collectionType != null) ((IRegisterInternal)instance).WithCollectionType(collectionType); diff --git a/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs b/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs index 4772469..683232c 100644 --- a/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs +++ b/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs @@ -3,12 +3,11 @@ using System; using System.Linq; using System.Reflection; -namespace MewtocolNet.RegisterBuilding -{ +namespace MewtocolNet.RegisterBuilding { public static class FinalizerExtensions { - public static IRegister Build (this RegisterBuilderStep step) { + public static IRegister Build(this RegisterBuilderStep step) { //if no casting method in builder was called => autocast the type from the RegisterType if (!step.wasCasted) @@ -34,7 +33,7 @@ namespace MewtocolNet.RegisterBuilding } - private static void GetFallbackDotnetType (this RegisterBuilderStep step) { + private static void GetFallbackDotnetType(this RegisterBuilderStep step) { bool isBoolean = step.RegType.IsBoolean(); bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null; @@ -66,7 +65,7 @@ namespace MewtocolNet.RegisterBuilding } - private static void AddToRegisterList (this RegisterBuilderStep step, BaseRegister instance) { + private static void AddToRegisterList(this RegisterBuilderStep step, BaseRegister instance) { if (step.forInterface == null) return; diff --git a/MewtocolNet/RegisterBuilding/ParseResult.cs b/MewtocolNet/RegisterBuilding/ParseResult.cs index d548d2c..b851848 100644 --- a/MewtocolNet/RegisterBuilding/ParseResult.cs +++ b/MewtocolNet/RegisterBuilding/ParseResult.cs @@ -1,4 +1,5 @@ namespace MewtocolNet.RegisterBuilding { + internal struct ParseResult { public ParseResultState state; diff --git a/MewtocolNet/Registers/BaseRegister.cs b/MewtocolNet/Registers/BaseRegister.cs index 13b0289..5a74bad 100644 --- a/MewtocolNet/Registers/BaseRegister.cs +++ b/MewtocolNet/Registers/BaseRegister.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Threading.Tasks; @@ -83,6 +82,8 @@ namespace MewtocolNet.Registers { public virtual string GetValueString() => Value?.ToString() ?? "null"; + public virtual string GetAsPLC () => Value?.ToString() ?? "null"; + public virtual string GetRegisterString() => RegisterType.ToString(); public virtual string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}"; @@ -99,13 +100,9 @@ namespace MewtocolNet.Registers { public virtual async Task WriteAsync(object data) => throw new NotImplementedException(); - public virtual Task SetValueAsync() => throw new NotImplementedException(); - - public virtual Task GetValueAsync() => throw new NotImplementedException(); - #endregion - public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}"; + public override string ToString() => $"{GetRegisterPLCName()}{(Name != null ? $" ({Name})" : "")} - Value: {GetValueString()}"; public virtual string ToString(bool additional) { diff --git a/MewtocolNet/Registers/BoolRegister.cs b/MewtocolNet/Registers/BoolRegister.cs index 3930814..a8f14aa 100644 --- a/MewtocolNet/Registers/BoolRegister.cs +++ b/MewtocolNet/Registers/BoolRegister.cs @@ -62,8 +62,11 @@ namespace MewtocolNet.Registers { /// public override async Task ReadAsync() { + if (!attachedInterface.IsConnected) return null; + var read = await attachedInterface.ReadRawRegisterAsync(this); - var parsed = PlcValueParser.Parse(read); + if(read == null) return null; + var parsed = PlcValueParser.Parse(this, read); SetValueFromPLC(parsed); return parsed; @@ -73,7 +76,9 @@ namespace MewtocolNet.Registers { /// public override async Task WriteAsync(object data) { - return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode((bool)data)); + if (!attachedInterface.IsConnected) return false; + + return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (bool)data)); } diff --git a/MewtocolNet/Registers/BytesRegister.cs b/MewtocolNet/Registers/BytesRegister.cs index d247800..c6c102e 100644 --- a/MewtocolNet/Registers/BytesRegister.cs +++ b/MewtocolNet/Registers/BytesRegister.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace MewtocolNet.Registers { @@ -71,8 +72,12 @@ namespace MewtocolNet.Registers { /// public override async Task ReadAsync() { + if (!attachedInterface.IsConnected) return null; + var read = await attachedInterface.ReadRawRegisterAsync(this); - var parsed = PlcValueParser.Parse(read); + if (read == null) return null; + + var parsed = PlcValueParser.Parse(this, read); SetValueFromPLC(parsed); return parsed; @@ -82,6 +87,8 @@ namespace MewtocolNet.Registers { /// public override async Task WriteAsync(object data) { + if (!attachedInterface.IsConnected) return false; + return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data); } diff --git a/MewtocolNet/Registers/NumberRegister.cs b/MewtocolNet/Registers/NumberRegister.cs index 0b16135..207d7de 100644 --- a/MewtocolNet/Registers/NumberRegister.cs +++ b/MewtocolNet/Registers/NumberRegister.cs @@ -92,12 +92,15 @@ namespace MewtocolNet.Registers { } + /// + public override string GetAsPLC() => ((TimeSpan)Value).AsPLCTime(); + /// public override string GetValueString() { if(typeof(T) == typeof(TimeSpan)) { - return $"{Value} [{((TimeSpan)Value).AsPLC()}]"; + return $"{Value} [{((TimeSpan)Value).AsPLCTime()}]"; } @@ -158,8 +161,12 @@ namespace MewtocolNet.Registers { /// public override async Task ReadAsync() { + if (!attachedInterface.IsConnected) return null; + var read = await attachedInterface.ReadRawRegisterAsync(this); - var parsed = PlcValueParser.Parse(read); + if (read == null) return null; + + var parsed = PlcValueParser.Parse(this, read); SetValueFromPLC(parsed); return parsed; @@ -169,7 +176,9 @@ namespace MewtocolNet.Registers { /// public override async Task WriteAsync(object data) { - return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode((T)data)); + if (!attachedInterface.IsConnected) return false; + + return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (T)data)); } diff --git a/MewtocolNet/Registers/StringRegister.cs b/MewtocolNet/Registers/StringRegister.cs index 14248a3..44fb5ed 100644 --- a/MewtocolNet/Registers/StringRegister.cs +++ b/MewtocolNet/Registers/StringRegister.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Net; using System.Text; using System.Threading.Tasks; @@ -11,32 +12,23 @@ namespace MewtocolNet.Registers { /// public class StringRegister : BaseRegister { - internal int addressLength; - /// - /// The rgisters memory length - /// - public int AddressLength => addressLength; - internal short ReservedSize { get; set; } + internal short UsedSize { get; set; } + + internal int WordsSize { get; set; } + + private bool isCalibrated = false; + /// /// Defines a register containing a string /// - public StringRegister (int _adress, int _reservedByteSize, string _name = null) { + public StringRegister (int _address, string _name = null) { - if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); + if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); name = _name; - memoryAddress = _adress; - ReservedSize = (short)_reservedByteSize; - - //calc mem length - var wordsize = (double)_reservedByteSize / 2; - if (wordsize % 2 != 0) { - wordsize++; - } - + memoryAddress = _address; RegisterType = RegisterType.DT_BYTE_RANGE; - addressLength = (int)Math.Round(wordsize + 1); } @@ -46,15 +38,18 @@ namespace MewtocolNet.Registers { StringBuilder asciistring = new StringBuilder("D"); asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append((MemoryAddress + AddressLength).ToString().PadLeft(5, '0')); + asciistring.Append((MemoryAddress + WordsSize - 1).ToString().PadLeft(5, '0')); return asciistring.ToString(); } + /// + public override string GetValueString() => $"'{Value}'"; + /// public override void SetValueFromPLC (object val) { - lastValue = (byte[])val; + lastValue = (string)val; TriggerChangedEvnt(this); TriggerNotifyChange(); @@ -65,20 +60,47 @@ namespace MewtocolNet.Registers { public override string GetRegisterString() => "DT"; /// - public override void ClearValue() => SetValueFromPLC(null); + public override void ClearValue() => SetValueFromPLC(""); /// public override async Task ReadAsync() { + if (!attachedInterface.IsConnected) return null; + + //get the string params first + + if(!isCalibrated) await Calibrate(); + var read = await attachedInterface.ReadRawRegisterAsync(this); - return PlcValueParser.Parse(read); + if (read == null) return null; + + return PlcValueParser.Parse(this, read); + + } + + private async Task Calibrate () { + + //get the string describer bytes + var bytes = await attachedInterface.ReadByteRange(MemoryAddress, 4, false); + + ReservedSize = BitConverter.ToInt16(bytes, 0); + UsedSize = BitConverter.ToInt16(bytes, 2); + WordsSize = 2 + (ReservedSize + 1) / 2; + + isCalibrated = true; } /// public override async Task WriteAsync(object data) { - return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data); + if (!attachedInterface.IsConnected) return false; + + var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (string)data)); + + if (res) UsedSize = (short)((string)Value).Length; + + return res; } diff --git a/MewtocolNet/TCPMessageResult.cs b/MewtocolNet/TCPMessageResult.cs index d3e23eb..0d51d14 100644 --- a/MewtocolNet/TCPMessageResult.cs +++ b/MewtocolNet/TCPMessageResult.cs @@ -9,4 +9,13 @@ } + internal enum CommandState { + + Intial, + LineFeed, + RequestedNextFrame, + Complete + + } + } \ No newline at end of file diff --git a/MewtocolNet/TypeConversion/Conversions.cs b/MewtocolNet/TypeConversion/Conversions.cs index 41543e8..53d62e4 100644 --- a/MewtocolNet/TypeConversion/Conversions.cs +++ b/MewtocolNet/TypeConversion/Conversions.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; namespace MewtocolNet.TypeConversion { @@ -32,11 +33,11 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.R) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { return (bool)(bytes[0] == 1); }, - ToRaw = value => { + ToRaw = (reg, value) => { return new byte[] { (byte)(value ? 1 : 0) }; @@ -45,11 +46,11 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.X) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { return bytes[0] == 1; }, - ToRaw = value => { + ToRaw = (reg, value) => { return new byte[] { (byte)(value ? 1 : 0) }; @@ -58,11 +59,11 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.Y) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { return bytes[0] == 1; }, - ToRaw = value => { + ToRaw = (reg, value) => { return new byte[] { (byte)(value ? 1 : 0) }; @@ -71,11 +72,11 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.DT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.INT, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { return BitConverter.ToInt16(bytes, 0); }, - ToRaw = value => { + ToRaw = (reg, value) => { return BitConverter.GetBytes(value); @@ -84,11 +85,11 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.DT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.UINT, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { return BitConverter.ToUInt16(bytes, 0); }, - ToRaw = value => { + ToRaw = (reg, value) => { return BitConverter.GetBytes(value); @@ -97,11 +98,11 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.DINT, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { return BitConverter.ToInt32(bytes, 0); }, - ToRaw = value => { + ToRaw = (reg, value) => { return BitConverter.GetBytes(value); @@ -110,11 +111,11 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.UDINT, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { return BitConverter.ToUInt32(bytes, 0); }, - ToRaw = value => { + ToRaw = (reg, value) => { return BitConverter.GetBytes(value); @@ -123,7 +124,7 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.REAL, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { var val = BitConverter.ToUInt32(bytes, 0); byte[] floatVals = BitConverter.GetBytes(val); @@ -132,7 +133,7 @@ namespace MewtocolNet.TypeConversion { return finalFloat; }, - ToRaw = value => { + ToRaw = (reg, value) => { return BitConverter.GetBytes(value); @@ -141,7 +142,7 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.TIME, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { var vallong = BitConverter.ToUInt32(bytes, 0); var valMillis = vallong * 10; @@ -149,7 +150,7 @@ namespace MewtocolNet.TypeConversion { return ts; }, - ToRaw = value => { + ToRaw = (reg, value) => { var tLong = (uint)(value.TotalMilliseconds / 10); return BitConverter.GetBytes(tLong); @@ -158,19 +159,52 @@ namespace MewtocolNet.TypeConversion { }, new PlcTypeConversion(RegisterType.DT) { HoldingRegisterType = typeof(BytesRegister), - FromRaw = bytes => bytes, - ToRaw = value => value, + FromRaw = (reg, bytes) => bytes, + ToRaw = (reg, value) => value, + }, + new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { + HoldingRegisterType = typeof(StringRegister), + PlcVarType = PlcVarType.STRING, + FromRaw = (reg, bytes) => { + + //get actual showed size + short actualLen = BitConverter.ToInt16(bytes, 2); + + //skip 4 bytes because they only describe the length + return Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray()); + + }, + ToRaw = (reg, value) => { + + var sReg = (StringRegister)reg; + + if(value.Length > sReg.ReservedSize) + value = value.Substring(0, sReg.ReservedSize); + + int padLen = sReg.ReservedSize; + if(sReg.ReservedSize % 2 != 0) padLen++; + + var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0')); + + List finalBytes = new List(); + finalBytes.AddRange(BitConverter.GetBytes(sReg.ReservedSize)); + finalBytes.AddRange(BitConverter.GetBytes((short)value.Length)); + finalBytes.AddRange(strBytes); + + return finalBytes.ToArray(); + + }, }, new PlcTypeConversion(RegisterType.DT) { HoldingRegisterType = typeof(BytesRegister), PlcVarType = PlcVarType.WORD, - FromRaw = bytes => { + FromRaw = (reg, bytes) => { BitArray bitAr = new BitArray(bytes); return bitAr; }, - ToRaw = value => { + ToRaw = (reg, value) => { byte[] ret = new byte[(value.Length - 1) / 8 + 1]; value.CopyTo(ret, 0); diff --git a/MewtocolNet/TypeConversion/IPlcTypeConverter.cs b/MewtocolNet/TypeConversion/IPlcTypeConverter.cs index 0b381f8..cdbffb5 100644 --- a/MewtocolNet/TypeConversion/IPlcTypeConverter.cs +++ b/MewtocolNet/TypeConversion/IPlcTypeConverter.cs @@ -4,9 +4,9 @@ namespace MewtocolNet { internal interface IPlcTypeConverter { - object FromRawData(byte[] data); + object FromRawData(IRegister register, byte[] data); - byte[] ToRawData(object value); + byte[] ToRawData(IRegister register, object value); Type GetDotnetType(); diff --git a/MewtocolNet/TypeConversion/PlcTypeConversion.cs b/MewtocolNet/TypeConversion/PlcTypeConversion.cs index f50d0b5..b65f961 100644 --- a/MewtocolNet/TypeConversion/PlcTypeConversion.cs +++ b/MewtocolNet/TypeConversion/PlcTypeConversion.cs @@ -13,9 +13,9 @@ namespace MewtocolNet { public Type HoldingRegisterType { get; set; } - public Func FromRaw { get; set; } + public Func FromRaw { get; set; } - public Func ToRaw { get; set; } + public Func ToRaw { get; set; } public PlcTypeConversion(RegisterType plcType) { @@ -32,9 +32,9 @@ namespace MewtocolNet { public PlcVarType GetPlcVarType() => PlcVarType; - public object FromRawData(byte[] data) => FromRaw.Invoke(data); + public object FromRawData(IRegister register, byte[] data) => FromRaw.Invoke(register, data); - public byte[] ToRawData(object value) => ToRaw.Invoke((T)value); + public byte[] ToRawData(IRegister register, object value) => ToRaw.Invoke(register, (T)value); } diff --git a/MewtocolNet/TypeConversion/PlcValueParser.cs b/MewtocolNet/TypeConversion/PlcValueParser.cs index c6c5497..ab873d8 100644 --- a/MewtocolNet/TypeConversion/PlcValueParser.cs +++ b/MewtocolNet/TypeConversion/PlcValueParser.cs @@ -12,25 +12,25 @@ namespace MewtocolNet { private static List conversions => Conversions.items; - public static T Parse(byte[] bytes) { + public static T Parse(IRegister register, byte[] bytes) { var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T)); if (converter == null) throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist"); - return (T)converter.FromRawData(bytes); + return (T)converter.FromRawData(register, bytes); } - public static byte[] Encode (T value) { + public static byte[] Encode (IRegister register, T value) { var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T)); if (converter == null) throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist"); - return converter.ToRawData(value); + return converter.ToRawData(register, value); } diff --git a/MewtocolTests/TestRegisterInterface.cs b/MewtocolTests/TestRegisterInterface.cs index 1b0e3ab..b2b8b50 100644 --- a/MewtocolTests/TestRegisterInterface.cs +++ b/MewtocolTests/TestRegisterInterface.cs @@ -34,8 +34,8 @@ namespace MewtocolTests { "D0005000051", //double word register "D0005000051", //double word register "D0005000051", //double word register - "D0005000065", //variable len register even bytes - "D0005000066", //variable len register odd bytes + "D0005000064", //variable len register even bytes + "D0005000065", //variable len register odd bytes }; //test mewtocol idents diff --git a/PLC_Test/test_c30_fpx_h.pro b/PLC_Test/test_c30_fpx_h.pro index 564b2a6..098bb1c 100644 Binary files a/PLC_Test/test_c30_fpx_h.pro and b/PLC_Test/test_c30_fpx_h.pro differ diff --git a/PLC_Test/test_c30_fpx_h.xml b/PLC_Test/test_c30_fpx_h.xml index 52b5a75..8929a5a 100644 --- a/PLC_Test/test_c30_fpx_h.xml +++ b/PLC_Test/test_c30_fpx_h.xml @@ -2,17 +2,17 @@ - + - + - +