Fix queue and dc issues

This commit is contained in:
Felix Weiß
2023-08-07 18:05:02 +02:00
parent 82d9aa8b16
commit c41b0cef16
21 changed files with 564 additions and 213 deletions

View File

@@ -26,7 +26,6 @@ internal class Program {
//connect async to the plc //connect async to the plc
await plc.ConnectAsync(); await plc.ConnectAsync();
await plc.SendCommandAsync($"%EE#RP0000000004");
//check if the connection was established //check if the connection was established
if (!plc.IsConnected) { if (!plc.IsConnected) {

View File

@@ -8,7 +8,7 @@
mc:Ignorable="d" mc:Ignorable="d"
MinWidth="500" MinWidth="500"
MinHeight="400" MinHeight="400"
Height="600" Height="850"
Width="800" Width="800"
Title="MewtocolNet WPF Demo"> Title="MewtocolNet WPF Demo">
<Grid> <Grid>
@@ -100,7 +100,14 @@
<TextBlock Text="{Binding AppViewModel.Plc.BytesPerSecondUpstream, Mode=OneWay}" <TextBlock Text="{Binding AppViewModel.Plc.BytesPerSecondUpstream, Mode=OneWay}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<Border Width="1"
Margin="5"
Background="Gray"/>
<TextBlock Text="{Binding AppViewModel.Plc.QueuedMessages, StringFormat='{}Q: {0}', Mode=OneWay}"
VerticalAlignment="Center"/>
<StackPanel.Style> <StackPanel.Style>
<Style TargetType="StackPanel"> <Style TargetType="StackPanel">
<Style.Triggers> <Style.Triggers>

View File

@@ -1,6 +1,7 @@
using Examples.WPF.ViewModels; using Examples.WPF.ViewModels;
using MewtocolNet; using MewtocolNet;
using MewtocolNet.ComCassette; using MewtocolNet.ComCassette;
using MewtocolNet.Registers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -53,24 +54,28 @@ public partial class ConnectView : UserControl {
var parsedInt = int.Parse(viewModel.SelectedPort); var parsedInt = int.Parse(viewModel.SelectedPort);
IRegister<short> heartbeatSetter = null!;
App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt) App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt)
.WithPoller() .WithPoller()
.WithInterfaceSettings(s => { .WithInterfaceSettings(setting => {
s.TryReconnectAttempts = 30; setting.TryReconnectAttempts = 30;
s.TryReconnectDelayMs = 2000; setting.TryReconnectDelayMs = 2000;
setting.HeartbeatIntervalMs = 3000;
}) })
.WithCustomPollLevels(l => { .WithCustomPollLevels(lvl => {
l.SetLevel(2, 3); lvl.SetLevel(2, 3);
l.SetLevel(3, TimeSpan.FromSeconds(5)); lvl.SetLevel(3, TimeSpan.FromSeconds(5));
l.SetLevel(4, TimeSpan.FromSeconds(10)); lvl.SetLevel(4, TimeSpan.FromSeconds(10));
}) })
.WithRegisters(b => { .WithRegisters(b => {
//b.Struct<short>("DT0").Build(); //b.Struct<short>("DT0").Build();
//b.Struct<short>("DT0").AsArray(30).Build(); //b.Struct<short>("DT0").AsArray(30).Build();
b.Struct<short>("DT1000").Build(); b.Struct<short>("DT1000").Build(out heartbeatSetter);
//b.Struct<Word>("DT1000").Build(); b.Struct<Word>("DT1000").Build();
b.Struct<ushort>("DT1001").PollLevel(2).Build(); b.Struct<ushort>("DT1001").PollLevel(2).Build();
b.Struct<Word>("DT1002").PollLevel(2).Build(); b.Struct<Word>("DT1002").PollLevel(2).Build();
@@ -83,6 +88,11 @@ public partial class ConnectView : UserControl {
b.String("DT1024", 32).PollLevel(3).Build(); b.String("DT1024", 32).PollLevel(3).Build();
b.String("DT1042", 5).PollLevel(4).Build(); b.String("DT1042", 5).PollLevel(4).Build();
})
.WithHeartbeatTask(async () => {
await heartbeatSetter.WriteAsync((short)new Random().Next(short.MinValue, short.MaxValue));
}) })
.Build(); .Build();
@@ -92,12 +102,6 @@ public partial class ConnectView : UserControl {
App.MainWindow.mainContent.Content = new PlcDataView(); App.MainWindow.mainContent.Content = new PlcDataView();
//for (int i = 0; i < 300000; i++) {
// _ = Task.Run(async () => await App.ViewModel.Plc.SendCommandAsync("%EE#RT"));
//}
} }
viewModel.IsConnecting = false; viewModel.IsConnecting = false;

View File

@@ -23,6 +23,10 @@
Click="ClickedDisconnect"/> Click="ClickedDisconnect"/>
<MenuItem Header="Connect" IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}" <MenuItem Header="Connect" IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}"
Click="ClickedConnect"/> Click="ClickedConnect"/>
<MenuItem Header="Set Random DT1000" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedSetRandom"/>
<MenuItem Header="Toggle OP mode" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedToggleRunMode"/>
</MenuItem> </MenuItem>
</Menu> </Menu>

View File

@@ -1,4 +1,5 @@
using Examples.WPF.ViewModels; using Examples.WPF.ViewModels;
using MewtocolNet.Registers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -41,4 +42,22 @@ public partial class PlcDataView : UserControl {
} }
private async void ClickedSetRandom(object sender, RoutedEventArgs e) {
var reg = (IRegister<ushort>?)viewModel.Plc.GetAllRegisters()?.FirstOrDefault(x => x.PLCAddressName == "DT1001");
if(reg != null) {
await reg.WriteAsync((ushort)new Random().Next(ushort.MinValue, ushort.MaxValue));
}
}
private async void ClickedToggleRunMode(object sender, RoutedEventArgs e) {
await viewModel.Plc.ToggleOperationModeAsync();
}
} }

View File

@@ -3,6 +3,7 @@ using MewtocolNet.Registers;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MewtocolNet.Helpers {
internal static class PlcBitConverter {
internal static string ToVersionNumber(IEnumerable<byte> inBytes, int startIndex = 0) {
return string.Join(".", inBytes.Skip(startIndex).Take(4).Reverse().Select(x => x.ToString()));
}
internal static DateTime ToDateTime(IEnumerable<byte> inBytes, int startIndex = 0) {
var offDate = new DateTime(2001, 01, 01);
var secondsOff = BitConverter.ToUInt32(inBytes.ToArray(), startIndex);
return offDate + TimeSpan.FromSeconds(secondsOff);
}
}
}

View File

@@ -94,24 +94,21 @@ namespace MewtocolNet {
/// </summary> /// </summary>
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param> /// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
/// <returns>Returns the result</returns> /// <returns>Returns the result</returns>
Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null); //Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null);
/// <summary>
/// Sends a command to the PLC and awaits its arrival<br/>
/// The checksum and BCC are appended automatically.<br/>
/// <b>Warning!</b> this command is only sent one directional, no error checking or other features included
/// </summary>
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
/// <returns>Returns true if the message was received</returns>
Task<bool> SendNoResponseCommandAsync(string _msg);
/// <summary> /// <summary>
/// Changes the PLCs operation mode to the given one /// Changes the PLCs operation mode to the given one
/// </summary> /// </summary>
/// <param name="setRun">True for run mode, false for prog mode</param> /// <param name="setRun">True for RUN mode, false for PROG mode</param>
/// <returns>The success state of the write operation</returns> /// <returns>The success state of the write operation</returns>
Task<bool> SetOperationModeAsync(bool setRun); Task<bool> SetOperationModeAsync(bool setRun);
/// <summary>
/// Toggles between RUN and PROG mode
/// </summary>
/// <returns>The success state of the write operation</returns>
Task<bool> ToggleOperationModeAsync();
/// <summary> /// <summary>
/// Restarts the plc program /// Restarts the plc program
/// </summary> /// </summary>

View File

@@ -321,10 +321,12 @@ namespace MewtocolNet
imew.memoryManager.pollLevelOrMode = res.PollLevelOverwriteMode; imew.memoryManager.pollLevelOrMode = res.PollLevelOverwriteMode;
imew.maxDataBlocksPerWrite = res.MaxDataBlocksPerWrite; imew.maxDataBlocksPerWrite = res.MaxDataBlocksPerWrite;
imew.sendReceiveTimeoutMs = res.SendReceiveTimeoutMs; imew.heartbeatIntervalMs = res.HeartbeatIntervalMs;
imew.tryReconnectAttempts = res.TryReconnectAttempts; imew.tryReconnectAttempts = res.TryReconnectAttempts;
imew.tryReconnectDelayMs = res.TryReconnectDelayMs; imew.tryReconnectDelayMs = res.TryReconnectDelayMs;
imew.alwaysGetMetadata = res.AlwaysGetMetadata;
} }
return this; return this;
@@ -352,7 +354,7 @@ namespace MewtocolNet
/// <summary> /// <summary>
/// A builder for attaching register collections /// A builder for attaching register collections
/// </summary> /// </summary>
public EndInit<T> WithRegisterCollections(Action<RegCollector> collector) { public PostRegisterSetup<T> WithRegisterCollections(Action<RegCollector> collector) {
try { try {
@@ -363,7 +365,7 @@ namespace MewtocolNet
imew.WithRegisterCollections(res.collections); imew.WithRegisterCollections(res.collections);
} }
return new EndInit<T> { return new PostRegisterSetup<T> {
postInit = this postInit = this
}; };
@@ -378,7 +380,7 @@ namespace MewtocolNet
/// <summary> /// <summary>
/// A builder for attaching register collections /// A builder for attaching register collections
/// </summary> /// </summary>
public PostInit<T> WithRegisters(Action<RBuild> builder) { public PostRegisterSetup<T> WithRegisters(Action<RBuild> builder) {
try { try {
@@ -389,7 +391,9 @@ namespace MewtocolNet
plc.AddRegisters(regBuilder.assembler.assembled.ToArray()); plc.AddRegisters(regBuilder.assembler.assembled.ToArray());
return this; return new PostRegisterSetup<T> {
postInit = this,
};
} catch { } catch {
@@ -408,12 +412,53 @@ namespace MewtocolNet
#endregion #endregion
#region Interface building step 3 public class PostRegisterSetup<T> {
public class EndInit<T> {
internal PostInit<T> postInit; internal PostInit<T> postInit;
/// <summary>
/// Repeats the passed method each time the hearbeat is triggered,
/// use
/// </summary>
/// <param name="heartBeatAsync"></param>
/// <returns></returns>
public EndInitSetup<T> WithHeartbeatTask(Func<Task> heartBeatAsync, bool executeInProg = false) {
try {
var plc = (MewtocolInterface)(object)postInit.intf;
plc.heartbeatCallbackTask = heartBeatAsync;
plc.execHeartBeatCallbackTaskInProg = executeInProg;
return new EndInitSetup<T> {
postInit = this.postInit,
postRegSetupInit = this
};
} catch {
throw;
}
}
/// <summary>
/// Builds and returns the final plc interface
/// </summary>
public T Build() => postInit.intf;
}
#region Interface building step 4
public class EndInitSetup<T> {
internal PostInit<T> postInit;
internal PostRegisterSetup<T> postRegSetupInit;
/// <summary> /// <summary>
/// Builds and returns the final plc interface /// Builds and returns the final plc interface
/// </summary> /// </summary>

View File

@@ -10,6 +10,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -25,6 +26,9 @@ namespace MewtocolNet {
/// <inheritdoc/> /// <inheritdoc/>
public event PlcConnectionEventHandler Connected; public event PlcConnectionEventHandler Connected;
/// <inheritdoc/>
public event PlcConnectionEventHandler Reconnected;
/// <inheritdoc/> /// <inheritdoc/>
public event PlcConnectionEventHandler Disconnected; public event PlcConnectionEventHandler Disconnected;
@@ -45,7 +49,6 @@ namespace MewtocolNet {
private int tcpMessagesSentThisCycle = 0; private int tcpMessagesSentThisCycle = 0;
private int pollerCycleDurationMs; private int pollerCycleDurationMs;
private volatile int queuedMessages;
private bool isConnected; private bool isConnected;
private PLCInfo plcInfo; private PLCInfo plcInfo;
private protected int stationNumber; private protected int stationNumber;
@@ -57,12 +60,14 @@ namespace MewtocolNet {
private protected int bytesPerSecondUpstream = 0; private protected int bytesPerSecondUpstream = 0;
private protected int bytesPerSecondDownstream = 0; private protected int bytesPerSecondDownstream = 0;
private protected AsyncQueue queue = new AsyncQueue();
private protected Stopwatch speedStopwatchUpstr; private protected Stopwatch speedStopwatchUpstr;
private protected Stopwatch speedStopwatchDownstr; private protected Stopwatch speedStopwatchDownstr;
private protected Task firstPollTask;
private Task reconnectTask; protected Task firstPollTask;
protected Task reconnectTask;
protected Task<MewtocolFrameResponse> regularSendTask;
protected Queue<Task<MewtocolFrameResponse>> userInputSendTasks = new Queue<Task<MewtocolFrameResponse>>();
private protected bool wasInitialStatusReceived; private protected bool wasInitialStatusReceived;
private protected MewtocolVersion mewtocolVersion; private protected MewtocolVersion mewtocolVersion;
@@ -77,17 +82,22 @@ namespace MewtocolNet {
internal event Action PolledCycle; internal event Action PolledCycle;
internal volatile bool pollerTaskStopped = true; internal volatile bool pollerTaskStopped = true;
internal volatile bool pollerFirstCycle; internal volatile bool pollerFirstCycle;
internal bool usePoller = false;
internal MemoryAreaManager memoryManager; internal MemoryAreaManager memoryManager;
private volatile bool isMessageLocked; //configuration
private protected bool isMessageLocked;
private volatile bool isReceiving; private volatile bool isReceiving;
private volatile bool isSending; private volatile bool isSending;
internal int sendReceiveTimeoutMs = 1000; internal int sendReceiveTimeoutMs = 1000;
internal int heartbeatIntervalMs = 3000;
internal int tryReconnectAttempts = 0; internal int tryReconnectAttempts = 0;
internal int tryReconnectDelayMs = 1000; internal int tryReconnectDelayMs = 1000;
internal bool usePoller = false;
internal bool alwaysGetMetadata = true;
#endregion #endregion
#region Public Read Only Properties / Fields #region Public Read Only Properties / Fields
@@ -96,7 +106,7 @@ namespace MewtocolNet {
public bool Disposed { get; private set; } public bool Disposed { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public int QueuedMessages => queuedMessages; public int QueuedMessages => userInputSendTasks.Count;
/// <inheritdoc/> /// <inheritdoc/>
public bool IsConnected { public bool IsConnected {
@@ -244,6 +254,20 @@ namespace MewtocolNet {
Logger.Log($"DIAG ERR: {PlcInfo.SelfDiagnosticError}", LogLevel.Verbose, this); Logger.Log($"DIAG ERR: {PlcInfo.SelfDiagnosticError}", LogLevel.Verbose, this);
Logger.Log($"CPU VER: {PlcInfo.CpuVersion}", LogLevel.Verbose, this); Logger.Log($"CPU VER: {PlcInfo.CpuVersion}", LogLevel.Verbose, this);
if(alwaysGetMetadata) {
Logger.LogVerbose($"METADATA: {PlcInfo.Metadata.MetaDataVersion}", this);
Logger.LogVerbose($"FP-WIN VERSION: {PlcInfo.Metadata.FPWinVersion}", this);
Logger.LogVerbose($"Project VERSION: {PlcInfo.Metadata.ProjectVersion}", this);
Logger.LogVerbose($"Company ID: {PlcInfo.Metadata.CompanyID}", this);
Logger.LogVerbose($"Application ID: {PlcInfo.Metadata.ApplicationID}", this);
Logger.LogVerbose($"Project ID: {PlcInfo.Metadata.ProjectID}", this);
Logger.LogVerbose($"LAST CONF CHNG: {PlcInfo.Metadata.LastConfigChangeDate}", this);
Logger.LogVerbose($"LAST POU CHNG: {PlcInfo.Metadata.LastPouChangeDate}", this);
Logger.LogVerbose($"LAST USR-LIB CHNG: {PlcInfo.Metadata.LastUserLibChangeDate}", this);
}
Logger.Log($">> Intial connection end <<", LogLevel.Verbose, this); Logger.Log($">> Intial connection end <<", LogLevel.Verbose, this);
if (callBack != null) { if (callBack != null) {
@@ -348,131 +372,120 @@ namespace MewtocolNet {
} }
internal async Task AwaitReconnectTaskAsync () { //internally used send task
internal async Task<MewtocolFrameResponse> SendCommandInternalAsync(string _msg, Action<double> onReceiveProgress = null) {
if (reconnectTask != null && !reconnectTask.IsCompleted) await reconnectTask; if (regularSendTask != null && !regularSendTask.IsCompleted) {
await Task.CompletedTask; //queue self
} var t = new Task<MewtocolFrameResponse>(() => SendCommandInternalAsync(_msg, onReceiveProgress).Result);
userInputSendTasks.Enqueue(t);
private Task<MewtocolFrameResponse> regularSendTask; OnPropChange(nameof(QueuedMessages));
/// <inheritdoc/> await t;
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null) {
if (isMessageLocked) OnPropChange(nameof(QueuedMessages));
throw new NotSupportedException("Can't send multiple messages in parallel");
isMessageLocked = true; return t.Result;
await AwaitReconnectTaskAsync();
if (!IsConnected && !isConnectingStage)
throw new NotSupportedException("The device must be connected to send a message");
//send request
queuedMessages++;
//wait for the last send task to complete
if (regularSendTask != null && !regularSendTask.IsCompleted) await regularSendTask;
regularSendTask = SendFrameAsync(_msg, onReceiveProgress, false);
if (await Task.WhenAny(regularSendTask, Task.Delay(2000)) != regularSendTask) {
isMessageLocked = false;
// timeout logic
return MewtocolFrameResponse.Timeout;
} }
//canceled if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
if (regularSendTask.IsCanceled) {
if (!IsConnected && !isConnectingStage && !isReconnectingStage)
throw new NotSupportedException("The device must be connected to send a message");
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));
if (timeoutAwaiter != regularSendTask) {
isMessageLocked = false;
regularSendTask = null;
// timeout logic
return MewtocolFrameResponse.Timeout;
}
} catch (OperationCanceledException) {
isMessageLocked = false;
return MewtocolFrameResponse.Canceled; return MewtocolFrameResponse.Canceled;
}
tcpMessagesSentThisCycle++;
queuedMessages--;
//success
if (regularSendTask.Result.Success) {
isMessageLocked = false;
return regularSendTask.Result;
}
//no success
if(reconnectTask == null) StartReconnectTask();
//await the single reconnect task
await AwaitReconnectTaskAsync();
//re-send the command
if (IsConnected) {
isMessageLocked = false;
return await SendCommandAsync(_msg, onReceiveProgress);
}
isMessageLocked = false;
return regularSendTask.Result;
}
/// <inheritdoc/>
public async Task<bool> SendNoResponseCommandAsync(string _msg) {
if (isMessageLocked)
throw new NotSupportedException("Can't send multiple messages in parallel");
isMessageLocked = true;
await AwaitReconnectTaskAsync();
if (!IsConnected && !isConnectingStage)
throw new NotSupportedException("The device must be connected to send a message");
//send request
queuedMessages++;
//wait for the last send task to complete
if (regularSendTask != null && !regularSendTask.IsCompleted) await regularSendTask;
regularSendTask = SendFrameAsync(_msg, null, true);
if (await Task.WhenAny(regularSendTask, Task.Delay(2000)) != regularSendTask) {
isMessageLocked = false;
// timeout logic
return false;
} }
//canceled //canceled
if (regularSendTask.IsCanceled) { if (regularSendTask.IsCanceled) {
isMessageLocked = false; isMessageLocked = false;
return false; regularSendTask = null;
return MewtocolFrameResponse.Canceled;
} }
MewtocolFrameResponse responseData = regularSendTask.Result;
tcpMessagesSentThisCycle++; tcpMessagesSentThisCycle++;
queuedMessages--;
isMessageLocked = false; isMessageLocked = false;
return true; regularSendTask = null;
return responseData;
} }
private protected async Task<MewtocolFrameResponse> SendFrameAsync(string frame, Action<double> onReceiveProgress, bool noResponse) { private protected async Task<MewtocolFrameResponse> SendOneDirectionalFrameAsync (string frame) {
try {
if (stream == null) return MewtocolFrameResponse.NotIntialized;
frame = $"{frame.BCC_Mew()}\r";
SetUpstreamStopWatchStart();
IsSending = true;
if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
//write inital command
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSource.Token);
IsSending = false;
//calc upstream speed
CalcUpstreamSpeed(writeBuffer.Length);
OnOutMsg(frame);
OnEndMsg();
} catch (Exception ex) {
IsSending = false;
return new MewtocolFrameResponse(400, ex.Message.ToString());
}
return MewtocolFrameResponse.EmptySuccess;
}
private protected async Task<MewtocolFrameResponse> SendTwoDirectionalFrameAsync(string frame, Action<double> onReceiveProgress = null) {
try { try {
@@ -511,13 +524,6 @@ namespace MewtocolNet {
OnOutMsg(frame); OnOutMsg(frame);
if(noResponse) {
Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this);
return MewtocolFrameResponse.EmptySuccess;
}
var readResult = await ReadCommandAsync(wordsCountRequested, onReceiveProgress); var readResult = await ReadCommandAsync(wordsCountRequested, onReceiveProgress);
//did not receive bytes but no errors, the com port was not configured right //did not receive bytes but no errors, the com port was not configured right
@@ -724,6 +730,12 @@ namespace MewtocolNet {
var formatted = $"R <- : {inMsg.Replace("\r", "(CR)")}"; var formatted = $"R <- : {inMsg.Replace("\r", "(CR)")}";
AddToLastMsgs(formatted); AddToLastMsgs(formatted);
Logger.Log(formatted, LogLevel.Critical, this); Logger.Log(formatted, LogLevel.Critical, this);
OnEndMsg();
}
private protected void OnEndMsg () {
Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this); Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this);
} }
@@ -742,6 +754,7 @@ namespace MewtocolNet {
if (IsConnected) { if (IsConnected) {
tSource.Cancel(); tSource.Cancel();
isMessageLocked = false;
Logger.Log("The PLC connection timed out", LogLevel.Error, this); Logger.Log("The PLC connection timed out", LogLevel.Error, this);
OnDisconnect(); OnDisconnect();
@@ -755,6 +768,7 @@ namespace MewtocolNet {
if (IsConnected) { if (IsConnected) {
tSource.Cancel(); tSource.Cancel();
isMessageLocked = false;
Logger.Log("The PLC connection was closed", LogLevel.Error, this); Logger.Log("The PLC connection was closed", LogLevel.Error, this);
OnDisconnect(); OnDisconnect();
@@ -774,9 +788,11 @@ namespace MewtocolNet {
private protected void OnSocketExceptionWhileConnected () { private protected void OnSocketExceptionWhileConnected () {
tSource.Cancel(); tSource.Cancel();
isMessageLocked = false;
IsConnected = false; IsConnected = false;
if (reconnectTask == null) StartReconnectTask();
} }
private protected virtual void OnConnected(PLCInfo plcinf) { private protected virtual void OnConnected(PLCInfo plcinf) {
@@ -809,10 +825,30 @@ namespace MewtocolNet {
} }
private void OnGenericUpdateTimerTick(object sender, System.Timers.ElapsedEventArgs e) { private protected void OnReconnected () {
GetAllRegisters().Cast<Register>() IsReceiving = false;
.ToList().ForEach(x => x.OnInterfaceCyclicTimerUpdate((int)cyclicGenericUpdateCounter.Interval)); IsSending = false;
BytesPerSecondDownstream = 0;
BytesPerSecondUpstream = 0;
PollerCycleDurationMs = 0;
isMessageLocked = false;
ClearRegisterVals();
KillPoller();
//GetAllRegisters().Cast<Register>().ToList().ForEach(x => x.OnPlcDisconnected());
//generate a new cancellation token source
tSource = new CancellationTokenSource();
IsConnected = true;
isReconnectingStage = false;
isConnectingStage = false;
reconnectTask = null;
Reconnected?.Invoke(this, new PlcConnectionArgs());
} }
@@ -823,8 +859,9 @@ namespace MewtocolNet {
BytesPerSecondDownstream = 0; BytesPerSecondDownstream = 0;
BytesPerSecondUpstream = 0; BytesPerSecondUpstream = 0;
PollerCycleDurationMs = 0; PollerCycleDurationMs = 0;
PlcInfo = null; PlcInfo = null;
isMessageLocked = false;
IsConnected = false; IsConnected = false;
ClearRegisterVals(); ClearRegisterVals();
@@ -845,6 +882,13 @@ namespace MewtocolNet {
} }
private void OnGenericUpdateTimerTick(object sender, System.Timers.ElapsedEventArgs e) {
GetAllRegisters().Cast<Register>()
.ToList().ForEach(x => x.OnInterfaceCyclicTimerUpdate((int)cyclicGenericUpdateCounter.Interval));
}
private void SetUpstreamStopWatchStart() { private void SetUpstreamStopWatchStart() {
if (speedStopwatchUpstr == null) { if (speedStopwatchUpstr == null) {

View File

@@ -19,6 +19,9 @@ namespace MewtocolNet {
internal Task heartbeatTask = Task.CompletedTask; internal Task heartbeatTask = Task.CompletedTask;
internal Func<Task> heartbeatCallbackTask;
internal bool execHeartBeatCallbackTaskInProg = false;
internal Task pollCycleTask; internal Task pollCycleTask;
private List<RegisterCollection> registerCollections = new List<RegisterCollection>(); private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
@@ -43,6 +46,8 @@ namespace MewtocolNet {
private System.Timers.Timer heartBeatTimer; private System.Timers.Timer heartBeatTimer;
internal volatile bool pollerFirstCycleCompleted;
#region Register Polling #region Register Polling
internal void WatchPollerDemand() { internal void WatchPollerDemand() {
@@ -50,6 +55,7 @@ namespace MewtocolNet {
memoryManager.MemoryLayoutChanged += () => TestPollerStartNeeded(); memoryManager.MemoryLayoutChanged += () => TestPollerStartNeeded();
Connected += (s, e) => TestPollerStartNeeded(); Connected += (s, e) => TestPollerStartNeeded();
Reconnected += (s, e) => TestPollerStartNeeded();
Disconnected += (s, e) => { Disconnected += (s, e) => {
@@ -93,6 +99,7 @@ namespace MewtocolNet {
/// </summary> /// </summary>
internal void KillPoller() { internal void KillPoller() {
pollerFirstCycleCompleted = false;
pollerTaskStopped = true; pollerTaskStopped = true;
} }
@@ -104,6 +111,7 @@ namespace MewtocolNet {
if (!pollerTaskStopped) return; if (!pollerTaskStopped) return;
pollerFirstCycleCompleted = false;
PollerCycleDurationMs = 0; PollerCycleDurationMs = 0;
pollerFirstCycle = true; pollerFirstCycle = true;
@@ -113,7 +121,9 @@ namespace MewtocolNet {
private void PollTimerTick(object sender, System.Timers.ElapsedEventArgs e) { private void PollTimerTick(object sender, System.Timers.ElapsedEventArgs e) {
if(!IsConnected || isConnectingStage) return; if(!IsConnected || isConnectingStage || isReconnectingStage) return;
heartBeatTimer.Stop();
heartbeatNeedsRun = true; heartbeatNeedsRun = true;
@@ -127,15 +137,24 @@ namespace MewtocolNet {
Logger.LogVerbose("Sending heartbeat", this); Logger.LogVerbose("Sending heartbeat", this);
if (await GetPLCInfoAsync(2000) == null) { if (await GetInfoAsync() == null) {
Logger.LogError("Heartbeat timed out", this); Logger.LogError("Heartbeat timed out", this);
OnSocketExceptionWhileConnected(); //OnSocketExceptionWhileConnected();
StartReconnectTask(); //StartReconnectTask();
return;
} }
if(heartbeatCallbackTask != null && (plcInfo.IsRunMode || execHeartBeatCallbackTaskInProg))
await heartbeatCallbackTask();
Logger.LogVerbose("End heartbeat", this);
heartBeatTimer.Start();
} }
/// <summary> /// <summary>
@@ -196,15 +215,30 @@ namespace MewtocolNet {
if (heartbeatNeedsRun) { if (heartbeatNeedsRun) {
await HeartbeatTickTask(); await HeartbeatTickTask();
heartbeatNeedsRun = false; heartbeatNeedsRun = false;
} }
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
await memoryManager.PollAllAreasAsync(); await memoryManager.PollAllAreasAsync(async () => {
if (userInputSendTasks != null && userInputSendTasks.Count > 0) {
var t = userInputSendTasks.Dequeue();
t.Start();
await t;
}
});
sw.Stop(); sw.Stop();
pollerFirstCycleCompleted = true;
PollerCycleDurationMs = (int)sw.ElapsedMilliseconds; PollerCycleDurationMs = (int)sw.ElapsedMilliseconds;
} }

View File

@@ -7,6 +7,8 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MewtocolNet.Helpers;
using System.Reflection;
namespace MewtocolNet { namespace MewtocolNet {
@@ -14,6 +16,8 @@ namespace MewtocolNet {
internal bool isConnectingStage = false; internal bool isConnectingStage = false;
internal protected bool isReconnectingStage = false;
internal int maxDataBlocksPerWrite = 8; internal int maxDataBlocksPerWrite = 8;
private CancellationTokenSource tTaskCancelSource = new CancellationTokenSource(); private CancellationTokenSource tTaskCancelSource = new CancellationTokenSource();
@@ -24,17 +28,17 @@ namespace MewtocolNet {
/// Gets generic information about the PLC /// Gets generic information about the PLC
/// </summary> /// </summary>
/// <returns>A PLCInfo class</returns> /// <returns>A PLCInfo class</returns>
public async Task<PLCInfo> GetPLCInfoAsync(int timeout = -1) { public async Task<PLCInfo> GetInfoAsync(bool detailed = true) {
MewtocolFrameResponse resRT = await SendCommandAsync("%EE#RT"); MewtocolFrameResponse resRT = await SendCommandInternalAsync("%EE#RT");
if (!resRT.Success) return null; if (!resRT.Success || tSource.Token.IsCancellationRequested) return null;
MewtocolFrameResponse? resEXRT = null; MewtocolFrameResponse? resEXRT = null;
if(isConnectingStage) { if (isConnectingStage && detailed) {
resEXRT = await SendCommandAsync("%EE#EX00RT00"); resEXRT = await SendCommandInternalAsync("%EE#EX00RT00");
} }
@@ -50,19 +54,21 @@ namespace MewtocolNet {
} }
if(!detailed) return plcInf;
//overwrite first with EXRT only on connecting stage //overwrite first with EXRT only on connecting stage
if (isConnectingStage && resEXRT != null && resEXRT.Value.Success && !plcInf.TryExtendFromEXRT(resEXRT.Value.Response)) { if (isConnectingStage && resEXRT != null && resEXRT.Value.Success && !plcInf.TryExtendFromEXRT(resEXRT.Value.Response)) {
throw new Exception("The EXRT message could not be parsed"); throw new Exception("The EXRT message could not be parsed");
} }
if(isConnectingStage) { if (isConnectingStage) {
//set the intial obj //set the intial obj
PlcInfo = plcInf; PlcInfo = plcInf;
} else { } else {
//update the obj with RT dynamic values only //update the obj with RT dynamic values only
PlcInfo.SelfDiagnosticError = plcInf.SelfDiagnosticError; PlcInfo.SelfDiagnosticError = plcInf.SelfDiagnosticError;
PlcInfo.OperationMode = plcInf.OperationMode; PlcInfo.OperationMode = plcInf.OperationMode;
} }
@@ -70,6 +76,55 @@ namespace MewtocolNet {
} }
/// <summary>
/// Gets the metadata information for the PLC program
/// </summary>
/// <returns>The metadata or null of it isn't used by the PLC program</returns>
/// <exception cref="NotSupportedException"></exception>
public async Task<PlcMetadata> GetMetadataAsync() {
//if the prog capacity and plc type are unknown retrieve them first
if (PlcInfo == null) await GetInfoAsync();
//still 0
if (PlcInfo.ProgramCapacity == 0) throw new NotSupportedException("Unable to access the program capacity of the PLC");
//meta data is always at last dt addresses of the plc
//so we take the capacity in k and multiply by 1024 to get the last register index and sub 3
//to get the last readable registers
var endAddress = (int)PlcInfo.ProgramCapacity * 1024 - 3; //32765 for 32k
var readBytes = 42;
var metaMarker = new byte[] { 0x4D, 0x65, 0x74, 0x41 };
var data = await ReadByteRangeNonBlocking(endAddress - 2 - (readBytes / 2), readBytes);
if (data != null && data.SearchBytePattern(metaMarker) == readBytes - 4) {
var meta = new PlcMetadata {
LastUserLibChangeDate = PlcBitConverter.ToDateTime(data, 0),
LastPouChangeDate = PlcBitConverter.ToDateTime(data, 4),
LastConfigChangeDate = PlcBitConverter.ToDateTime(data, 8),
FPWinVersion = PlcBitConverter.ToVersionNumber(data, 12),
ProjectVersion = PlcBitConverter.ToVersionNumber(data, 16),
ProjectID = BitConverter.ToUInt32(data, 20),
ApplicationID = BitConverter.ToUInt32(data, 24),
CompanyID = BitConverter.ToUInt32(data, 28),
MetaDataVersion = PlcBitConverter.ToVersionNumber(data, 32),
};
PlcInfo.Metadata = meta;
return meta;
}
return null;
}
#endregion #endregion
#region Operation mode changing #region Operation mode changing
@@ -80,7 +135,7 @@ namespace MewtocolNet {
string modeChar = setRun ? "R" : "P"; string modeChar = setRun ? "R" : "P";
string requeststring = $"%{GetStationNumber()}#RM{modeChar}"; string requeststring = $"%{GetStationNumber()}#RM{modeChar}";
var result = await SendCommandAsync(requeststring); var result = await SendCommandInternalAsync(requeststring);
if (result.Success) { if (result.Success) {
Logger.Log($"Operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this); Logger.Log($"Operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this);
@@ -93,7 +148,7 @@ namespace MewtocolNet {
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> RestartProgramAsync () { public async Task<bool> RestartProgramAsync() {
return await SetOperationModeAsync(false) && return await SetOperationModeAsync(false) &&
await SetOperationModeAsync(true); await SetOperationModeAsync(true);
@@ -101,14 +156,23 @@ namespace MewtocolNet {
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task FactoryResetAsync () { public async Task<bool> ToggleOperationModeAsync() {
var currMode = await GetInfoAsync(false);
return await SetOperationModeAsync(!currMode.IsRunMode);
}
/// <inheritdoc/>
public async Task FactoryResetAsync() {
//set to prog mode //set to prog mode
await SetOperationModeAsync(false); await SetOperationModeAsync(false);
//reset plc //reset plc
await SendCommandAsync($"%{GetStationNumber()}#0F"); await SendCommandInternalAsync($"%{GetStationNumber()}#0F");
await SendCommandAsync($"%{GetStationNumber()}#21"); await SendCommandInternalAsync($"%{GetStationNumber()}#21");
} }
@@ -116,13 +180,13 @@ namespace MewtocolNet {
#region Program Read / Write #region Program Read / Write
public async Task<PlcBinaryProgram> ReadProgramAsync () { public async Task<PlcBinaryProgram> ReadProgramAsync() {
var steps = new List<byte[]>(); var steps = new List<byte[]>();
int i = 0; int i = 0;
int stepsPerReq = 50; int stepsPerReq = 50;
while(i < int.MaxValue) { while (i < int.MaxValue) {
var sb = new StringBuilder($"%{GetStationNumber()}#RP"); var sb = new StringBuilder($"%{GetStationNumber()}#RP");
var stp1 = (i * stepsPerReq); var stp1 = (i * stepsPerReq);
@@ -131,7 +195,7 @@ namespace MewtocolNet {
sb.Append(stp1.ToString().PadLeft(5, '0')); sb.Append(stp1.ToString().PadLeft(5, '0'));
sb.Append(stp2.ToString().PadLeft(5, '0')); sb.Append(stp2.ToString().PadLeft(5, '0'));
var res = await SendCommandAsync(sb.ToString()); var res = await SendCommandInternalAsync(sb.ToString());
if (res.Success) { if (res.Success) {
@@ -143,8 +207,8 @@ namespace MewtocolNet {
if (split[0] == 0xFF && split[1] == 0xFF) break; if (split[0] == 0xFF && split[1] == 0xFF) break;
steps.Add(split); steps.Add(split);
} }
if (foundEndPattern != -1) { if (foundEndPattern != -1) {
break; break;
@@ -156,8 +220,8 @@ namespace MewtocolNet {
} }
return new PlcBinaryProgram { return new PlcBinaryProgram {
rawSteps = steps, rawSteps = steps,
}; };
} }
@@ -188,7 +252,7 @@ namespace MewtocolNet {
string endStr = (start + wordLength - 1).ToString().PadLeft(5, '0'); string endStr = (start + wordLength - 1).ToString().PadLeft(5, '0');
string requeststring = $"%{GetStationNumber()}#WDD{startStr}{endStr}{byteString}"; string requeststring = $"%{GetStationNumber()}#WDD{startStr}{endStr}{byteString}";
var result = await SendCommandAsync(requeststring); var result = await SendCommandInternalAsync(requeststring);
return result.Success; return result.Success;
@@ -216,9 +280,9 @@ namespace MewtocolNet {
int blocksOverflow = wordLength % maxReadBlockSize; int blocksOverflow = wordLength % maxReadBlockSize;
int totalBlocksToRead = blocksOverflow != 0 ? blocksToReadNoOverflow + 1 : blocksToReadNoOverflow; int totalBlocksToRead = blocksOverflow != 0 ? blocksToReadNoOverflow + 1 : blocksToReadNoOverflow;
List<byte> readBytes = new List<byte>(); List<byte> readBytes = new List<byte>();
async Task ReadBlock (int wordStart, int wordEnd, Action<double> readProg) { async Task ReadBlock(int wordStart, int wordEnd, Action<double> readProg) {
int blockSize = wordEnd - wordStart + 1; int blockSize = wordEnd - wordStart + 1;
string startStr = wordStart.ToString().PadLeft(5, '0'); string startStr = wordStart.ToString().PadLeft(5, '0');
@@ -226,13 +290,13 @@ namespace MewtocolNet {
string requeststring = $"%{GetStationNumber()}#RDD{startStr}{endStr}"; string requeststring = $"%{GetStationNumber()}#RDD{startStr}{endStr}";
var result = await SendCommandAsync(requeststring, onReceiveProgress: readProg); var result = await SendCommandInternalAsync(requeststring, onReceiveProgress: readProg);
if (result.Success && !string.IsNullOrEmpty(result.Response)) { if (result.Success && !string.IsNullOrEmpty(result.Response)) {
var bytes = result.Response.ParseDTRawStringAsBytes(); var bytes = result.Response.ParseDTRawStringAsBytes();
if(bytes != null) if (bytes != null)
readBytes.AddRange(bytes); readBytes.AddRange(bytes);
} }
@@ -253,7 +317,7 @@ namespace MewtocolNet {
var toplevelProg = (double)(i + 1) / totalBlocksToRead; var toplevelProg = (double)(i + 1) / totalBlocksToRead;
onProgress(toplevelProg * p); onProgress(toplevelProg * p);
} }
}); });
//read remaining block //read remaining block
@@ -265,7 +329,7 @@ namespace MewtocolNet {
curWordStart = start + ((i + 1) * maxReadBlockSize); curWordStart = start + ((i + 1) * maxReadBlockSize);
curWordEnd = curWordStart + blocksOverflow - 1; curWordEnd = curWordStart + blocksOverflow - 1;
await ReadBlock(curWordStart, curWordEnd, (p) => {}); await ReadBlock(curWordStart, curWordEnd, (p) => { });
} }

View File

@@ -252,10 +252,12 @@ namespace MewtocolNet {
Logger.Log($"Opened [SERIAL]: {GetConnectionInfo()}", LogLevel.Critical, this); Logger.Log($"Opened [SERIAL]: {GetConnectionInfo()}", LogLevel.Critical, this);
var plcinf = await GetPLCInfoAsync(100); var plcinf = await GetInfoAsync();
if (plcinf == null) CloseClient(); if (plcinf == null) CloseClient();
if (alwaysGetMetadata) await GetMetadataAsync();
return plcinf; return plcinf;
} catch (UnauthorizedAccessException) { } catch (UnauthorizedAccessException) {

View File

@@ -140,14 +140,13 @@ namespace MewtocolNet {
stream = client.GetStream(); stream = client.GetStream();
stream.ReadTimeout = 1000; stream.ReadTimeout = 1000;
//try to abort any non read message
//await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB");
//get plc info //get plc info
var plcinf = await GetPLCInfoAsync(ConnectTimeout); var plcinf = await GetInfoAsync();
if (plcinf != null) { if (plcinf != null) {
if(alwaysGetMetadata) await GetMetadataAsync();
IsConnected = true; IsConnected = true;
await base.ConnectAsync(callBack); await base.ConnectAsync(callBack);
OnConnected(plcinf); OnConnected(plcinf);
@@ -177,57 +176,73 @@ namespace MewtocolNet {
firstPollTask = new Task(() => { }); firstPollTask = new Task(() => { });
Logger.Log($">> Reconnect start <<", LogLevel.Verbose, this); Logger.Log($">> Reconnect start <<", LogLevel.Verbose, this);
isReconnectingStage = true;
isConnectingStage = true; isConnectingStage = true;
IsConnected = false;
BuildTcpClient(); BuildTcpClient();
var result = client.BeginConnect(ipAddr, Port, null, null); var result = client.BeginConnect(ipAddr, Port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(conTimeout)); var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout));
if (!success || !client.Connected) { if (!success || !client.Connected) {
Logger.Log("The PLC connection timed out", LogLevel.Error, this); Logger.Log("The PLC connection timed out", LogLevel.Error, this);
OnMajorSocketExceptionWhileConnecting(); OnMajorSocketExceptionWhileConnecting();
return; return;
} }
Logger.LogVerbose("TCP/IP Client connected", this); Logger.LogVerbose("TCP/IP Client connected", this);
if (HostEndpoint == null) { if (HostEndpoint == null) {
var ep = (IPEndPoint)client.Client.LocalEndPoint; var ep = (IPEndPoint)client.Client.LocalEndPoint;
Logger.Log($"Connecting [AUTO] endpoint: {ep.Address.MapToIPv4()}:{ep.Port}", LogLevel.Info, this); Logger.Log($"Connecting [AUTO] from: {ep.Address.MapToIPv4()}:{ep.Port} to {GetConnectionInfo()}", LogLevel.Info, this);
} }
//get the stream //get the stream
stream = client.GetStream(); stream = client.GetStream();
stream.ReadTimeout = 1000; stream.ReadTimeout = 1000;
Logger.LogVerbose("Attached stream, getting PLC info", this); isMessageLocked = false;
//get plc info //null ongoing tasks
var plcinf = await GetPLCInfoAsync(ConnectTimeout); regularSendTask = null;
reconnectTask = Task.CompletedTask;
if (plcinf != null) { //try to abort any non read message
//await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB");
IsConnected = true; //get plc info 2 times to clear old stuff from the buffer
await base.ConnectAsync();
Logger.LogVerbose("Connection re-established", this); OnReconnected();
OnConnected(plcinf);
} else { //var plcinf = await SendCommandAsync($"%{GetStationNumber()}#RT");
Logger.Log("Initial connection failed", LogLevel.Error, this); //if (plcinf != null) {
OnDisconnect();
} // Logger.Log("Reconnect successfull");
// OnReconnected();
// //await base.ConnectAsync();
// //OnConnected(plcinf);
//} else {
// Logger.Log("Initial connection failed", LogLevel.Error, this);
// OnDisconnect();
//}
await Task.CompletedTask; await Task.CompletedTask;
} catch (Exception ex) { } catch (SocketException) {
Logger.LogError($"Reconnect exception: {ex.Message}"); OnMajorSocketExceptionWhileConnecting();
isConnectingStage = false;
isReconnectingStage = false;
} }

View File

@@ -18,6 +18,7 @@ namespace MewtocolNet {
private OPMode operationMode; private OPMode operationMode;
private HWInformation hardwareInformation; private HWInformation hardwareInformation;
private string selfDiagnosticError; private string selfDiagnosticError;
private PlcMetadata metadata;
/// <summary> /// <summary>
/// The type of the PLC named by Panasonic /// The type of the PLC named by Panasonic
@@ -103,6 +104,17 @@ namespace MewtocolNet {
/// </summary> /// </summary>
public bool IsRunMode => OperationMode.HasFlag(OPMode.RunMode); public bool IsRunMode => OperationMode.HasFlag(OPMode.RunMode);
/// <summary>
/// Contains useful information about the PLC program and metadata
/// </summary>
public PlcMetadata Metadata {
get => metadata;
internal set {
metadata = value;
OnPropChange();
}
}
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
internal bool TryExtendFromEXRT(string msg) { internal bool TryExtendFromEXRT(string msg) {

View File

@@ -0,0 +1,56 @@
using System;
namespace MewtocolNet {
/// <summary>
/// Contains useful information about the PLC program and metadata
/// </summary>
public class PlcMetadata {
/// <summary>
/// The last date the used user librarys were changed
/// </summary>
public DateTime LastUserLibChangeDate { get; internal set; }
/// <summary>
/// The last date the program Pou's were changed
/// </summary>
public DateTime LastPouChangeDate { get; internal set; }
/// <summary>
/// The last date the PLC configuration was changed
/// </summary>
public DateTime LastConfigChangeDate { get; internal set; }
/// <summary>
/// The used FP-Win version to create the PLC program
/// </summary>
public string FPWinVersion { get; internal set; }
/// <summary>
/// The custom project version of the PLC program
/// </summary>
public string ProjectVersion { get; internal set; }
/// <summary>
/// Metadata format version
/// </summary>
public string MetaDataVersion { get; internal set; }
/// <summary>
/// The project ID of the PLC program
/// </summary>
public uint ProjectID { get; internal set; }
/// <summary>
/// The application / machine specific ID for the PLC program
/// </summary>
public uint ApplicationID { get; internal set; }
/// <summary>
/// The company ID of the PLC program creator
/// </summary>
public uint CompanyID { get; internal set; }
}
}

View File

@@ -134,6 +134,8 @@ namespace MewtocolNet.Registers {
protected void TriggerUpdateReceived () { protected void TriggerUpdateReceived () {
if (timeSinceLastUpdate == null) return;
updateCountTimerCycle++; updateCountTimerCycle++;
if(updateCountTimerCycle >= 1) { if(updateCountTimerCycle >= 1) {

View File

@@ -123,7 +123,9 @@ namespace MewtocolNet.Registers {
//if string correct the sizing of the byte hint was wrong //if string correct the sizing of the byte hint was wrong
var reservedSize = BitConverter.ToInt16(bytes, 0); var reservedSize = BitConverter.ToInt16(bytes, 0);
if (reservedStringLength != reservedSize) if (reservedStringLength != reservedSize &&
attachedInterface.PlcInfo.IsRunMode &&
(attachedInterface.pollerTaskStopped || attachedInterface.pollerFirstCycleCompleted))
throw new NotSupportedException( throw new NotSupportedException(
$"The STRING register at {GetMewName()} is not correctly sized, " + $"The STRING register at {GetMewName()} is not correctly sized, " +
$"the size should be STRING[{reservedSize}] instead of STRING[{reservedStringLength}]" $"the size should be STRING[{reservedSize}] instead of STRING[{reservedStringLength}]"

View File

@@ -41,6 +41,11 @@ namespace MewtocolNet.SetupClasses {
/// </summary> /// </summary>
public int SendReceiveTimeoutMs { get; set; } = 1000; public int SendReceiveTimeoutMs { get; set; } = 1000;
/// <summary>
/// The heartbeat interval in milliseconds, it is recommended to use 3000ms
/// </summary>
public int HeartbeatIntervalMs { get; set; } = 3000;
/// <summary> /// <summary>
/// Number of attempts to try and reconnect to the plc, 0 for none /// Number of attempts to try and reconnect to the plc, 0 for none
/// </summary> /// </summary>
@@ -51,6 +56,11 @@ namespace MewtocolNet.SetupClasses {
/// </summary> /// </summary>
public int TryReconnectDelayMs { get; set; } = 2000; public int TryReconnectDelayMs { get; set; } = 2000;
/// <summary>
/// Sets wether or not the interface should always retrieve metadata on connection start
/// </summary>
public bool AlwaysGetMetadata { get; set; } = true;
} }
} }

View File

@@ -106,9 +106,13 @@ namespace MewtocolNet.UnderlyingRegisters {
private void SetUnderlyingBytes(byte[] bytes, ulong addStart) { private void SetUnderlyingBytes(byte[] bytes, ulong addStart) {
int copyOffset = (int)((addStart - addressStart) * 2); int copyOffset = (int)((addStart - addressStart) * 2);
bytes.CopyTo(underlyingBytes, copyOffset);
UpdateAreaRegisterValues(); if(bytes.Length + copyOffset <= underlyingBytes.Length) {
bytes.CopyTo(underlyingBytes, copyOffset);
UpdateAreaRegisterValues();
}
} }

View File

@@ -314,7 +314,7 @@ namespace MewtocolNet.UnderlyingRegisters {
} }
internal async Task PollAllAreasAsync() { internal async Task PollAllAreasAsync(Func<Task> inbetweenCallback) {
foreach (var pollLevel in pollLevels) { foreach (var pollLevel in pollLevels) {
@@ -363,6 +363,8 @@ namespace MewtocolNet.UnderlyingRegisters {
//set the whole memory area at once //set the whole memory area at once
await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd); await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
await inbetweenCallback();
} }
pollLevel.lastReadTimeMs = (int)sw.Elapsed.TotalMilliseconds; pollLevel.lastReadTimeMs = (int)sw.Elapsed.TotalMilliseconds;