mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Fix queue and dc issues
This commit is contained in:
@@ -26,7 +26,6 @@ internal class Program {
|
||||
|
||||
//connect async to the plc
|
||||
await plc.ConnectAsync();
|
||||
await plc.SendCommandAsync($"%EE#RP0000000004");
|
||||
|
||||
//check if the connection was established
|
||||
if (!plc.IsConnected) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
mc:Ignorable="d"
|
||||
MinWidth="500"
|
||||
MinHeight="400"
|
||||
Height="600"
|
||||
Height="850"
|
||||
Width="800"
|
||||
Title="MewtocolNet WPF Demo">
|
||||
<Grid>
|
||||
@@ -100,7 +100,14 @@
|
||||
|
||||
<TextBlock Text="{Binding AppViewModel.Plc.BytesPerSecondUpstream, Mode=OneWay}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
|
||||
<Border Width="1"
|
||||
Margin="5"
|
||||
Background="Gray"/>
|
||||
|
||||
<TextBlock Text="{Binding AppViewModel.Plc.QueuedMessages, StringFormat='{}Q: {0}', Mode=OneWay}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<StackPanel.Style>
|
||||
<Style TargetType="StackPanel">
|
||||
<Style.Triggers>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Examples.WPF.ViewModels;
|
||||
using MewtocolNet;
|
||||
using MewtocolNet.ComCassette;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -53,24 +54,28 @@ public partial class ConnectView : UserControl {
|
||||
|
||||
var parsedInt = int.Parse(viewModel.SelectedPort);
|
||||
|
||||
IRegister<short> heartbeatSetter = null!;
|
||||
|
||||
App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt)
|
||||
.WithPoller()
|
||||
.WithInterfaceSettings(s => {
|
||||
s.TryReconnectAttempts = 30;
|
||||
s.TryReconnectDelayMs = 2000;
|
||||
.WithInterfaceSettings(setting => {
|
||||
setting.TryReconnectAttempts = 30;
|
||||
setting.TryReconnectDelayMs = 2000;
|
||||
setting.HeartbeatIntervalMs = 3000;
|
||||
})
|
||||
.WithCustomPollLevels(l => {
|
||||
l.SetLevel(2, 3);
|
||||
l.SetLevel(3, TimeSpan.FromSeconds(5));
|
||||
l.SetLevel(4, TimeSpan.FromSeconds(10));
|
||||
.WithCustomPollLevels(lvl => {
|
||||
lvl.SetLevel(2, 3);
|
||||
lvl.SetLevel(3, TimeSpan.FromSeconds(5));
|
||||
lvl.SetLevel(4, TimeSpan.FromSeconds(10));
|
||||
})
|
||||
.WithRegisters(b => {
|
||||
|
||||
|
||||
//b.Struct<short>("DT0").Build();
|
||||
//b.Struct<short>("DT0").AsArray(30).Build();
|
||||
|
||||
b.Struct<short>("DT1000").Build();
|
||||
//b.Struct<Word>("DT1000").Build();
|
||||
b.Struct<short>("DT1000").Build(out heartbeatSetter);
|
||||
b.Struct<Word>("DT1000").Build();
|
||||
|
||||
b.Struct<ushort>("DT1001").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("DT1042", 5).PollLevel(4).Build();
|
||||
|
||||
})
|
||||
.WithHeartbeatTask(async () => {
|
||||
|
||||
await heartbeatSetter.WriteAsync((short)new Random().Next(short.MinValue, short.MaxValue));
|
||||
|
||||
})
|
||||
.Build();
|
||||
|
||||
@@ -92,12 +102,6 @@ public partial class ConnectView : UserControl {
|
||||
|
||||
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;
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
Click="ClickedDisconnect"/>
|
||||
<MenuItem Header="Connect" IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}"
|
||||
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>
|
||||
</Menu>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Examples.WPF.ViewModels;
|
||||
using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using MewtocolNet.Registers;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
28
MewtocolNet/Helpers/PlcBitConverter.cs
Normal file
28
MewtocolNet/Helpers/PlcBitConverter.cs
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -94,24 +94,21 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
||||
/// <returns>Returns the result</returns>
|
||||
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);
|
||||
//Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the PLCs operation mode to the given one
|
||||
/// </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>
|
||||
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>
|
||||
/// Restarts the plc program
|
||||
/// </summary>
|
||||
|
||||
@@ -321,10 +321,12 @@ namespace MewtocolNet
|
||||
imew.memoryManager.pollLevelOrMode = res.PollLevelOverwriteMode;
|
||||
|
||||
imew.maxDataBlocksPerWrite = res.MaxDataBlocksPerWrite;
|
||||
imew.sendReceiveTimeoutMs = res.SendReceiveTimeoutMs;
|
||||
imew.heartbeatIntervalMs = res.HeartbeatIntervalMs;
|
||||
imew.tryReconnectAttempts = res.TryReconnectAttempts;
|
||||
imew.tryReconnectDelayMs = res.TryReconnectDelayMs;
|
||||
|
||||
imew.alwaysGetMetadata = res.AlwaysGetMetadata;
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -352,7 +354,7 @@ namespace MewtocolNet
|
||||
/// <summary>
|
||||
/// A builder for attaching register collections
|
||||
/// </summary>
|
||||
public EndInit<T> WithRegisterCollections(Action<RegCollector> collector) {
|
||||
public PostRegisterSetup<T> WithRegisterCollections(Action<RegCollector> collector) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -363,7 +365,7 @@ namespace MewtocolNet
|
||||
imew.WithRegisterCollections(res.collections);
|
||||
}
|
||||
|
||||
return new EndInit<T> {
|
||||
return new PostRegisterSetup<T> {
|
||||
postInit = this
|
||||
};
|
||||
|
||||
@@ -378,7 +380,7 @@ namespace MewtocolNet
|
||||
/// <summary>
|
||||
/// A builder for attaching register collections
|
||||
/// </summary>
|
||||
public PostInit<T> WithRegisters(Action<RBuild> builder) {
|
||||
public PostRegisterSetup<T> WithRegisters(Action<RBuild> builder) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -389,7 +391,9 @@ namespace MewtocolNet
|
||||
|
||||
plc.AddRegisters(regBuilder.assembler.assembled.ToArray());
|
||||
|
||||
return this;
|
||||
return new PostRegisterSetup<T> {
|
||||
postInit = this,
|
||||
};
|
||||
|
||||
} catch {
|
||||
|
||||
@@ -408,12 +412,53 @@ namespace MewtocolNet
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interface building step 3
|
||||
|
||||
public class EndInit<T> {
|
||||
public class PostRegisterSetup<T> {
|
||||
|
||||
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>
|
||||
/// Builds and returns the final plc interface
|
||||
/// </summary>
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -25,6 +26,9 @@ namespace MewtocolNet {
|
||||
/// <inheritdoc/>
|
||||
public event PlcConnectionEventHandler Connected;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event PlcConnectionEventHandler Reconnected;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event PlcConnectionEventHandler Disconnected;
|
||||
|
||||
@@ -45,7 +49,6 @@ namespace MewtocolNet {
|
||||
|
||||
private int tcpMessagesSentThisCycle = 0;
|
||||
private int pollerCycleDurationMs;
|
||||
private volatile int queuedMessages;
|
||||
private bool isConnected;
|
||||
private PLCInfo plcInfo;
|
||||
private protected int stationNumber;
|
||||
@@ -57,12 +60,14 @@ namespace MewtocolNet {
|
||||
private protected int bytesPerSecondUpstream = 0;
|
||||
private protected int bytesPerSecondDownstream = 0;
|
||||
|
||||
private protected AsyncQueue queue = new AsyncQueue();
|
||||
|
||||
private protected Stopwatch speedStopwatchUpstr;
|
||||
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 MewtocolVersion mewtocolVersion;
|
||||
@@ -77,17 +82,22 @@ namespace MewtocolNet {
|
||||
internal event Action PolledCycle;
|
||||
internal volatile bool pollerTaskStopped = true;
|
||||
internal volatile bool pollerFirstCycle;
|
||||
internal bool usePoller = false;
|
||||
internal MemoryAreaManager memoryManager;
|
||||
|
||||
private volatile bool isMessageLocked;
|
||||
//configuration
|
||||
|
||||
private protected bool isMessageLocked;
|
||||
private volatile bool isReceiving;
|
||||
private volatile bool isSending;
|
||||
|
||||
internal int sendReceiveTimeoutMs = 1000;
|
||||
internal int heartbeatIntervalMs = 3000;
|
||||
internal int tryReconnectAttempts = 0;
|
||||
internal int tryReconnectDelayMs = 1000;
|
||||
|
||||
internal bool usePoller = false;
|
||||
internal bool alwaysGetMetadata = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Read Only Properties / Fields
|
||||
@@ -96,7 +106,7 @@ namespace MewtocolNet {
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueuedMessages => queuedMessages;
|
||||
public int QueuedMessages => userInputSendTasks.Count;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsConnected {
|
||||
@@ -244,6 +254,20 @@ namespace MewtocolNet {
|
||||
Logger.Log($"DIAG ERR: {PlcInfo.SelfDiagnosticError}", 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);
|
||||
|
||||
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/>
|
||||
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null) {
|
||||
await t;
|
||||
|
||||
if (isMessageLocked)
|
||||
throw new NotSupportedException("Can't send multiple messages in parallel");
|
||||
OnPropChange(nameof(QueuedMessages));
|
||||
|
||||
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, onReceiveProgress, false);
|
||||
|
||||
if (await Task.WhenAny(regularSendTask, Task.Delay(2000)) != regularSendTask) {
|
||||
|
||||
isMessageLocked = false;
|
||||
|
||||
// timeout logic
|
||||
return MewtocolFrameResponse.Timeout;
|
||||
return t.Result;
|
||||
|
||||
}
|
||||
|
||||
//canceled
|
||||
if (regularSendTask.IsCanceled) {
|
||||
if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
if (regularSendTask.IsCanceled) {
|
||||
|
||||
isMessageLocked = false;
|
||||
return false;
|
||||
regularSendTask = null;
|
||||
|
||||
return MewtocolFrameResponse.Canceled;
|
||||
|
||||
}
|
||||
|
||||
MewtocolFrameResponse responseData = regularSendTask.Result;
|
||||
|
||||
tcpMessagesSentThisCycle++;
|
||||
queuedMessages--;
|
||||
|
||||
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 {
|
||||
|
||||
@@ -511,13 +524,6 @@ namespace MewtocolNet {
|
||||
|
||||
OnOutMsg(frame);
|
||||
|
||||
if(noResponse) {
|
||||
|
||||
Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this);
|
||||
return MewtocolFrameResponse.EmptySuccess;
|
||||
|
||||
}
|
||||
|
||||
var readResult = await ReadCommandAsync(wordsCountRequested, onReceiveProgress);
|
||||
|
||||
//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)")}";
|
||||
AddToLastMsgs(formatted);
|
||||
Logger.Log(formatted, LogLevel.Critical, this);
|
||||
OnEndMsg();
|
||||
|
||||
}
|
||||
|
||||
private protected void OnEndMsg () {
|
||||
|
||||
Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this);
|
||||
|
||||
}
|
||||
@@ -742,6 +754,7 @@ namespace MewtocolNet {
|
||||
if (IsConnected) {
|
||||
|
||||
tSource.Cancel();
|
||||
isMessageLocked = false;
|
||||
|
||||
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
||||
OnDisconnect();
|
||||
@@ -755,6 +768,7 @@ namespace MewtocolNet {
|
||||
if (IsConnected) {
|
||||
|
||||
tSource.Cancel();
|
||||
isMessageLocked = false;
|
||||
|
||||
Logger.Log("The PLC connection was closed", LogLevel.Error, this);
|
||||
OnDisconnect();
|
||||
@@ -774,9 +788,11 @@ namespace MewtocolNet {
|
||||
private protected void OnSocketExceptionWhileConnected () {
|
||||
|
||||
tSource.Cancel();
|
||||
|
||||
isMessageLocked = false;
|
||||
IsConnected = false;
|
||||
|
||||
if (reconnectTask == null) StartReconnectTask();
|
||||
|
||||
}
|
||||
|
||||
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>()
|
||||
.ToList().ForEach(x => x.OnInterfaceCyclicTimerUpdate((int)cyclicGenericUpdateCounter.Interval));
|
||||
IsReceiving = false;
|
||||
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;
|
||||
BytesPerSecondUpstream = 0;
|
||||
PollerCycleDurationMs = 0;
|
||||
PlcInfo = null;
|
||||
PlcInfo = null;
|
||||
|
||||
isMessageLocked = false;
|
||||
IsConnected = false;
|
||||
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() {
|
||||
|
||||
if (speedStopwatchUpstr == null) {
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace MewtocolNet {
|
||||
|
||||
internal Task heartbeatTask = Task.CompletedTask;
|
||||
|
||||
internal Func<Task> heartbeatCallbackTask;
|
||||
internal bool execHeartBeatCallbackTaskInProg = false;
|
||||
|
||||
internal Task pollCycleTask;
|
||||
|
||||
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
|
||||
@@ -43,6 +46,8 @@ namespace MewtocolNet {
|
||||
|
||||
private System.Timers.Timer heartBeatTimer;
|
||||
|
||||
internal volatile bool pollerFirstCycleCompleted;
|
||||
|
||||
#region Register Polling
|
||||
|
||||
internal void WatchPollerDemand() {
|
||||
@@ -50,6 +55,7 @@ namespace MewtocolNet {
|
||||
memoryManager.MemoryLayoutChanged += () => TestPollerStartNeeded();
|
||||
|
||||
Connected += (s, e) => TestPollerStartNeeded();
|
||||
Reconnected += (s, e) => TestPollerStartNeeded();
|
||||
|
||||
Disconnected += (s, e) => {
|
||||
|
||||
@@ -93,6 +99,7 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
internal void KillPoller() {
|
||||
|
||||
pollerFirstCycleCompleted = false;
|
||||
pollerTaskStopped = true;
|
||||
|
||||
}
|
||||
@@ -104,6 +111,7 @@ namespace MewtocolNet {
|
||||
|
||||
if (!pollerTaskStopped) return;
|
||||
|
||||
pollerFirstCycleCompleted = false;
|
||||
PollerCycleDurationMs = 0;
|
||||
pollerFirstCycle = true;
|
||||
|
||||
@@ -113,7 +121,9 @@ namespace MewtocolNet {
|
||||
|
||||
private void PollTimerTick(object sender, System.Timers.ElapsedEventArgs e) {
|
||||
|
||||
if(!IsConnected || isConnectingStage) return;
|
||||
if(!IsConnected || isConnectingStage || isReconnectingStage) return;
|
||||
|
||||
heartBeatTimer.Stop();
|
||||
|
||||
heartbeatNeedsRun = true;
|
||||
|
||||
@@ -127,15 +137,24 @@ namespace MewtocolNet {
|
||||
|
||||
Logger.LogVerbose("Sending heartbeat", this);
|
||||
|
||||
if (await GetPLCInfoAsync(2000) == null) {
|
||||
if (await GetInfoAsync() == null) {
|
||||
|
||||
Logger.LogError("Heartbeat timed out", this);
|
||||
|
||||
OnSocketExceptionWhileConnected();
|
||||
StartReconnectTask();
|
||||
//OnSocketExceptionWhileConnected();
|
||||
//StartReconnectTask();
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if(heartbeatCallbackTask != null && (plcInfo.IsRunMode || execHeartBeatCallbackTaskInProg))
|
||||
await heartbeatCallbackTask();
|
||||
|
||||
Logger.LogVerbose("End heartbeat", this);
|
||||
|
||||
heartBeatTimer.Start();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,15 +215,30 @@ namespace MewtocolNet {
|
||||
if (heartbeatNeedsRun) {
|
||||
|
||||
await HeartbeatTickTask();
|
||||
|
||||
heartbeatNeedsRun = false;
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
pollerFirstCycleCompleted = true;
|
||||
PollerCycleDurationMs = (int)sw.ElapsedMilliseconds;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MewtocolNet.Helpers;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MewtocolNet {
|
||||
|
||||
@@ -14,6 +16,8 @@ namespace MewtocolNet {
|
||||
|
||||
internal bool isConnectingStage = false;
|
||||
|
||||
internal protected bool isReconnectingStage = false;
|
||||
|
||||
internal int maxDataBlocksPerWrite = 8;
|
||||
|
||||
private CancellationTokenSource tTaskCancelSource = new CancellationTokenSource();
|
||||
@@ -24,17 +28,17 @@ namespace MewtocolNet {
|
||||
/// Gets generic information about the PLC
|
||||
/// </summary>
|
||||
/// <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;
|
||||
|
||||
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
|
||||
if (isConnectingStage && resEXRT != null && resEXRT.Value.Success && !plcInf.TryExtendFromEXRT(resEXRT.Value.Response)) {
|
||||
|
||||
throw new Exception("The EXRT message could not be parsed");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(isConnectingStage) {
|
||||
if (isConnectingStage) {
|
||||
//set the intial obj
|
||||
PlcInfo = plcInf;
|
||||
} else {
|
||||
//update the obj with RT dynamic values only
|
||||
PlcInfo.SelfDiagnosticError = plcInf.SelfDiagnosticError;
|
||||
PlcInfo.SelfDiagnosticError = plcInf.SelfDiagnosticError;
|
||||
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
|
||||
|
||||
#region Operation mode changing
|
||||
@@ -80,7 +135,7 @@ namespace MewtocolNet {
|
||||
string modeChar = setRun ? "R" : "P";
|
||||
|
||||
string requeststring = $"%{GetStationNumber()}#RM{modeChar}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
var result = await SendCommandInternalAsync(requeststring);
|
||||
|
||||
if (result.Success) {
|
||||
Logger.Log($"Operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this);
|
||||
@@ -93,7 +148,7 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> RestartProgramAsync () {
|
||||
public async Task<bool> RestartProgramAsync() {
|
||||
|
||||
return await SetOperationModeAsync(false) &&
|
||||
await SetOperationModeAsync(true);
|
||||
@@ -101,14 +156,23 @@ namespace MewtocolNet {
|
||||
}
|
||||
|
||||
/// <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
|
||||
await SetOperationModeAsync(false);
|
||||
|
||||
//reset plc
|
||||
await SendCommandAsync($"%{GetStationNumber()}#0F");
|
||||
await SendCommandAsync($"%{GetStationNumber()}#21");
|
||||
await SendCommandInternalAsync($"%{GetStationNumber()}#0F");
|
||||
await SendCommandInternalAsync($"%{GetStationNumber()}#21");
|
||||
|
||||
}
|
||||
|
||||
@@ -116,13 +180,13 @@ namespace MewtocolNet {
|
||||
|
||||
#region Program Read / Write
|
||||
|
||||
public async Task<PlcBinaryProgram> ReadProgramAsync () {
|
||||
public async Task<PlcBinaryProgram> ReadProgramAsync() {
|
||||
|
||||
var steps = new List<byte[]>();
|
||||
|
||||
int i = 0;
|
||||
int stepsPerReq = 50;
|
||||
while(i < int.MaxValue) {
|
||||
while (i < int.MaxValue) {
|
||||
|
||||
var sb = new StringBuilder($"%{GetStationNumber()}#RP");
|
||||
var stp1 = (i * stepsPerReq);
|
||||
@@ -131,7 +195,7 @@ namespace MewtocolNet {
|
||||
sb.Append(stp1.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) {
|
||||
|
||||
@@ -143,8 +207,8 @@ namespace MewtocolNet {
|
||||
if (split[0] == 0xFF && split[1] == 0xFF) break;
|
||||
steps.Add(split);
|
||||
}
|
||||
|
||||
if (foundEndPattern != -1) {
|
||||
|
||||
if (foundEndPattern != -1) {
|
||||
|
||||
break;
|
||||
|
||||
@@ -156,8 +220,8 @@ namespace MewtocolNet {
|
||||
|
||||
}
|
||||
|
||||
return new PlcBinaryProgram {
|
||||
rawSteps = steps,
|
||||
return new PlcBinaryProgram {
|
||||
rawSteps = steps,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -188,7 +252,7 @@ namespace MewtocolNet {
|
||||
string endStr = (start + wordLength - 1).ToString().PadLeft(5, '0');
|
||||
|
||||
string requeststring = $"%{GetStationNumber()}#WDD{startStr}{endStr}{byteString}";
|
||||
var result = await SendCommandAsync(requeststring);
|
||||
var result = await SendCommandInternalAsync(requeststring);
|
||||
|
||||
return result.Success;
|
||||
|
||||
@@ -216,9 +280,9 @@ namespace MewtocolNet {
|
||||
int blocksOverflow = wordLength % maxReadBlockSize;
|
||||
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;
|
||||
string startStr = wordStart.ToString().PadLeft(5, '0');
|
||||
@@ -226,13 +290,13 @@ namespace MewtocolNet {
|
||||
|
||||
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)) {
|
||||
|
||||
var bytes = result.Response.ParseDTRawStringAsBytes();
|
||||
|
||||
if(bytes != null)
|
||||
if (bytes != null)
|
||||
readBytes.AddRange(bytes);
|
||||
|
||||
}
|
||||
@@ -253,7 +317,7 @@ namespace MewtocolNet {
|
||||
var toplevelProg = (double)(i + 1) / totalBlocksToRead;
|
||||
onProgress(toplevelProg * p);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
//read remaining block
|
||||
@@ -265,7 +329,7 @@ namespace MewtocolNet {
|
||||
curWordStart = start + ((i + 1) * maxReadBlockSize);
|
||||
curWordEnd = curWordStart + blocksOverflow - 1;
|
||||
|
||||
await ReadBlock(curWordStart, curWordEnd, (p) => {});
|
||||
await ReadBlock(curWordStart, curWordEnd, (p) => { });
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -252,10 +252,12 @@ namespace MewtocolNet {
|
||||
|
||||
Logger.Log($"Opened [SERIAL]: {GetConnectionInfo()}", LogLevel.Critical, this);
|
||||
|
||||
var plcinf = await GetPLCInfoAsync(100);
|
||||
var plcinf = await GetInfoAsync();
|
||||
|
||||
if (plcinf == null) CloseClient();
|
||||
|
||||
if (alwaysGetMetadata) await GetMetadataAsync();
|
||||
|
||||
return plcinf;
|
||||
|
||||
} catch (UnauthorizedAccessException) {
|
||||
|
||||
@@ -140,14 +140,13 @@ namespace MewtocolNet {
|
||||
stream = client.GetStream();
|
||||
stream.ReadTimeout = 1000;
|
||||
|
||||
//try to abort any non read message
|
||||
//await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB");
|
||||
|
||||
//get plc info
|
||||
var plcinf = await GetPLCInfoAsync(ConnectTimeout);
|
||||
var plcinf = await GetInfoAsync();
|
||||
|
||||
if (plcinf != null) {
|
||||
|
||||
if(alwaysGetMetadata) await GetMetadataAsync();
|
||||
|
||||
IsConnected = true;
|
||||
await base.ConnectAsync(callBack);
|
||||
OnConnected(plcinf);
|
||||
@@ -177,57 +176,73 @@ namespace MewtocolNet {
|
||||
firstPollTask = new Task(() => { });
|
||||
|
||||
Logger.Log($">> Reconnect start <<", LogLevel.Verbose, this);
|
||||
|
||||
|
||||
isReconnectingStage = true;
|
||||
isConnectingStage = true;
|
||||
IsConnected = false;
|
||||
|
||||
BuildTcpClient();
|
||||
|
||||
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) {
|
||||
|
||||
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
||||
OnMajorSocketExceptionWhileConnecting();
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
Logger.LogVerbose("TCP/IP Client connected", this);
|
||||
|
||||
if (HostEndpoint == null) {
|
||||
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
|
||||
stream = client.GetStream();
|
||||
stream.ReadTimeout = 1000;
|
||||
|
||||
Logger.LogVerbose("Attached stream, getting PLC info", this);
|
||||
isMessageLocked = false;
|
||||
|
||||
//get plc info
|
||||
var plcinf = await GetPLCInfoAsync(ConnectTimeout);
|
||||
//null ongoing tasks
|
||||
regularSendTask = null;
|
||||
reconnectTask = Task.CompletedTask;
|
||||
|
||||
if (plcinf != null) {
|
||||
//try to abort any non read message
|
||||
//await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB");
|
||||
|
||||
IsConnected = true;
|
||||
await base.ConnectAsync();
|
||||
//get plc info 2 times to clear old stuff from the buffer
|
||||
|
||||
Logger.LogVerbose("Connection re-established", this);
|
||||
OnConnected(plcinf);
|
||||
OnReconnected();
|
||||
|
||||
} else {
|
||||
//var plcinf = await SendCommandAsync($"%{GetStationNumber()}#RT");
|
||||
|
||||
Logger.Log("Initial connection failed", LogLevel.Error, this);
|
||||
OnDisconnect();
|
||||
//if (plcinf != null) {
|
||||
|
||||
}
|
||||
// Logger.Log("Reconnect successfull");
|
||||
|
||||
// OnReconnected();
|
||||
|
||||
// //await base.ConnectAsync();
|
||||
// //OnConnected(plcinf);
|
||||
|
||||
//} else {
|
||||
|
||||
// Logger.Log("Initial connection failed", LogLevel.Error, this);
|
||||
// OnDisconnect();
|
||||
|
||||
//}
|
||||
|
||||
await Task.CompletedTask;
|
||||
|
||||
} catch (Exception ex) {
|
||||
} catch (SocketException) {
|
||||
|
||||
Logger.LogError($"Reconnect exception: {ex.Message}");
|
||||
OnMajorSocketExceptionWhileConnecting();
|
||||
isConnectingStage = false;
|
||||
isReconnectingStage = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace MewtocolNet {
|
||||
private OPMode operationMode;
|
||||
private HWInformation hardwareInformation;
|
||||
private string selfDiagnosticError;
|
||||
private PlcMetadata metadata;
|
||||
|
||||
/// <summary>
|
||||
/// The type of the PLC named by Panasonic
|
||||
@@ -103,6 +104,17 @@ namespace MewtocolNet {
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
internal bool TryExtendFromEXRT(string msg) {
|
||||
|
||||
56
MewtocolNet/PlcMetadata.cs
Normal file
56
MewtocolNet/PlcMetadata.cs
Normal 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; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -134,6 +134,8 @@ namespace MewtocolNet.Registers {
|
||||
|
||||
protected void TriggerUpdateReceived () {
|
||||
|
||||
if (timeSinceLastUpdate == null) return;
|
||||
|
||||
updateCountTimerCycle++;
|
||||
|
||||
if(updateCountTimerCycle >= 1) {
|
||||
|
||||
@@ -123,7 +123,9 @@ namespace MewtocolNet.Registers {
|
||||
//if string correct the sizing of the byte hint was wrong
|
||||
var reservedSize = BitConverter.ToInt16(bytes, 0);
|
||||
|
||||
if (reservedStringLength != reservedSize)
|
||||
if (reservedStringLength != reservedSize &&
|
||||
attachedInterface.PlcInfo.IsRunMode &&
|
||||
(attachedInterface.pollerTaskStopped || attachedInterface.pollerFirstCycleCompleted))
|
||||
throw new NotSupportedException(
|
||||
$"The STRING register at {GetMewName()} is not correctly sized, " +
|
||||
$"the size should be STRING[{reservedSize}] instead of STRING[{reservedStringLength}]"
|
||||
|
||||
@@ -41,6 +41,11 @@ namespace MewtocolNet.SetupClasses {
|
||||
/// </summary>
|
||||
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>
|
||||
/// Number of attempts to try and reconnect to the plc, 0 for none
|
||||
/// </summary>
|
||||
@@ -51,6 +56,11 @@ namespace MewtocolNet.SetupClasses {
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -106,9 +106,13 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
private void SetUnderlyingBytes(byte[] bytes, ulong addStart) {
|
||||
|
||||
int copyOffset = (int)((addStart - addressStart) * 2);
|
||||
bytes.CopyTo(underlyingBytes, copyOffset);
|
||||
|
||||
UpdateAreaRegisterValues();
|
||||
if(bytes.Length + copyOffset <= underlyingBytes.Length) {
|
||||
|
||||
bytes.CopyTo(underlyingBytes, copyOffset);
|
||||
UpdateAreaRegisterValues();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
internal async Task PollAllAreasAsync() {
|
||||
internal async Task PollAllAreasAsync(Func<Task> inbetweenCallback) {
|
||||
|
||||
foreach (var pollLevel in pollLevels) {
|
||||
|
||||
@@ -363,6 +363,8 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
//set the whole memory area at once
|
||||
await dtArea.RequestByteReadAsync(dtArea.AddressStart, dtArea.AddressEnd);
|
||||
|
||||
await inbetweenCallback();
|
||||
|
||||
}
|
||||
|
||||
pollLevel.lastReadTimeMs = (int)sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
Reference in New Issue
Block a user