Heavily improved the overall performance and async handling

- added a queuing manager for all messages
- tcp now keeps the connection after the first handshake
- async tasks are now run by the time order they were added
- minor bugfixes
This commit is contained in:
Felix Weiß
2022-07-15 15:53:26 +02:00
parent cdae9a60fb
commit 6c411d7318
9 changed files with 302 additions and 246 deletions

View File

@@ -33,134 +33,90 @@ namespace MewtocolNet {
/// </summary>
internal void AttachPoller () {
if (ContinousReaderRunning) return;
if (ContinousReaderRunning)
return;
cTokenAutoUpdater = new CancellationTokenSource();
Task.Factory.StartNew(async () => {
Logger.Log("Poller is attaching", LogLevel.Info, this);
cTokenAutoUpdater = new CancellationTokenSource();
try {
Logger.Log("Poller is attaching", LogLevel.Info, this);
Task.Factory.StartNew(async () => {
int it = 0;
var plcinf = await GetPLCInfoAsync();
if (plcinf == null) {
Logger.Log("PLC not reachable, stopping logger", LogLevel.Info, this);
return;
}
PolledCycle += MewtocolInterface_PolledCycle;
void MewtocolInterface_PolledCycle () {
StringBuilder stringBuilder = new StringBuilder();
foreach (var reg in GetAllRegisters()) {
string address = $"{reg.GetRegisterString()}{reg.GetStartingMemoryArea()}".PadRight(8, (char)32);
stringBuilder.AppendLine($"{address}{(reg.Name != null ? $" ({reg.Name})" : "")}: {reg.GetValueString()}");
}
Logger.Log($"Registers loaded are: \n" +
$"--------------------\n" +
$"{stringBuilder.ToString()}" +
$"--------------------",
LogLevel.Verbose, this);
Logger.Log("Logger did its first cycle successfully", LogLevel.Info, this);
PolledCycle -= MewtocolInterface_PolledCycle;
}
ContinousReaderRunning = true;
int getPLCinfoCycleCount = 0;
while (ContinousReaderRunning) {
//do priority tasks first
if (PriorityTasks != null && PriorityTasks.Count > 0) {
try {
await PriorityTasks?.FirstOrDefault(x => !x.IsCompleted);
} catch (NullReferenceException) { }
} else if (getPLCinfoCycleCount > 25) {
await GetPLCInfoAsync();
getPLCinfoCycleCount = 0;
}
foreach (var reg in Registers) {
if (reg is NRegister<short> shortReg) {
var lastVal = shortReg.Value;
var readout = (await ReadNumRegister(shortReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(shortReg);
}
}
if (reg is NRegister<ushort> ushortReg) {
var lastVal = ushortReg.Value;
var readout = (await ReadNumRegister(ushortReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(ushortReg);
}
}
if (reg is NRegister<int> intReg) {
var lastVal = intReg.Value;
var readout = (await ReadNumRegister(intReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(intReg);
}
}
if (reg is NRegister<uint> uintReg) {
var lastVal = uintReg.Value;
var readout = (await ReadNumRegister(uintReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(uintReg);
}
}
if (reg is NRegister<float> floatReg) {
var lastVal = floatReg.Value;
var readout = (await ReadNumRegister(floatReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(floatReg);
}
}
if (reg is NRegister<TimeSpan> tsReg) {
var lastVal = tsReg.Value;
var readout = (await ReadNumRegister(tsReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(tsReg);
}
}
if (reg is BRegister boolReg) {
var lastVal = boolReg.Value;
var readout = (await ReadBoolRegister(boolReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(boolReg);
}
}
if (reg is SRegister stringReg) {
var lastVal = stringReg.Value;
var readout = (await ReadStringRegister(stringReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(stringReg);
}
}
}
getPLCinfoCycleCount++;
while (it < Registers.Count + 1) {
if (it >= Registers.Count) {
it = 0;
//invoke cycle polled event
InvokePolledCycleDone();
continue;
}
}, cTokenAutoUpdater.Token);
var reg = Registers[it];
} catch (TaskCanceledException) { }
if (reg is NRegister<short> shortReg) {
var lastVal = shortReg.Value;
var readout = (await ReadNumRegister(shortReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(shortReg);
}
}
if (reg is NRegister<ushort> ushortReg) {
var lastVal = ushortReg.Value;
var readout = (await ReadNumRegister(ushortReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(ushortReg);
}
}
if (reg is NRegister<int> intReg) {
var lastVal = intReg.Value;
var readout = (await ReadNumRegister(intReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(intReg);
}
}
if (reg is NRegister<uint> uintReg) {
var lastVal = uintReg.Value;
var readout = (await ReadNumRegister(uintReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(uintReg);
}
}
if (reg is NRegister<float> floatReg) {
var lastVal = floatReg.Value;
var readout = (await ReadNumRegister(floatReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(floatReg);
}
}
if (reg is NRegister<TimeSpan> tsReg) {
var lastVal = tsReg.Value;
var readout = (await ReadNumRegister(tsReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(tsReg);
}
}
if (reg is BRegister boolReg) {
var lastVal = boolReg.Value;
var readout = (await ReadBoolRegister(boolReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(boolReg);
}
}
if (reg is SRegister stringReg) {
var lastVal = stringReg.Value;
var readout = (await ReadStringRegister(stringReg)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(stringReg);
}
}
it++;
}
});
}

View File

@@ -75,7 +75,7 @@ namespace MewtocolNet {
var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString);
if(res.Success) {
string val = res.Groups[2].Value;
return val.GetStringFromAsciiHex().Trim();
return val.GetStringFromAsciiHex()?.Trim();
}
return null;
}
@@ -143,7 +143,7 @@ namespace MewtocolNet {
internal static string GetStringFromAsciiHex (this string input) {
if (input.Length % 2 != 0)
throw new ArgumentException("input not a hex string");
return null;
byte[] bytes = new byte[input.Length / 2];
for (int i = 0; i < input.Length; i += 2) {
String hex = input.Substring(i, 2);
@@ -158,6 +158,8 @@ namespace MewtocolNet {
}
internal static byte[] HexStringToByteArray(this string hex) {
if (hex == null)
return null;
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))

View File

@@ -12,6 +12,9 @@ using MewtocolNet.Logging;
using System.Collections;
using System.Diagnostics;
using System.ComponentModel;
using System.Net;
using System.Threading;
using MewtocolNet.Queue;
namespace MewtocolNet {
@@ -86,7 +89,7 @@ namespace MewtocolNet {
/// </summary>
public int StationNumber => stationNumber;
private int cycleTimeMs;
private int cycleTimeMs = 25;
/// <summary>
/// The duration of the last message cycle
/// </summary>
@@ -98,9 +101,10 @@ namespace MewtocolNet {
}
}
internal List<Task> PriorityTasks { get; set; } = new List<Task>();
internal NetworkStream stream;
internal TcpClient client;
internal readonly SerialQueue queue = new SerialQueue();
private int RecBufferSize = 64;
internal int SendExceptionsInRow = 0;
#region Initialization
@@ -205,8 +209,6 @@ namespace MewtocolNet {
OnMajorSocketException();
PriorityTasks.Clear();
}
/// <summary>
@@ -335,9 +337,12 @@ namespace MewtocolNet {
var bytes = BitConverter.GetBytes(reg16.Value);
BitArray bitAr = new BitArray(bytes);
prop.SetValue(collection, bitAr[bitIndex]);
collection.TriggerPropertyChanged(prop.Name);
if (bitIndex < bitAr.Length && bitIndex >= 0) {
prop.SetValue(collection, bitAr[bitIndex]);
collection.TriggerPropertyChanged(prop.Name);
}
} else if (bitWiseFound != null && reg is NRegister<int> reg32) {
var casted = (RegisterAttribute)bitWiseFound;
var bitIndex = casted.AssignedBitIndex;
@@ -542,6 +547,25 @@ namespace MewtocolNet {
#region Low level command handling
private async Task ConnectTCP () {
var targetIP = IPAddress.Parse(ip);
client = new TcpClient() {
ReceiveBufferSize = RecBufferSize,
NoDelay = false,
ExclusiveAddressUse = true
};
await client.ConnectAsync(targetIP, port);
stream = client.GetStream();
stream.ReadTimeout = 1000;
Console.WriteLine($"Connected {client.Connected}");
}
/// <summary>
/// Calculates checksum and sends a command to the PLC then awaits results
/// </summary>
@@ -553,29 +577,42 @@ namespace MewtocolNet {
_msg += "\r";
//send request
try {
string response = null;
var response = await queue.Enqueue(() => SendSingleBlock(_msg));
if (ContinousReaderRunning) {
if (response == null) {
return new CommandResult {
Success = false,
Error = "0000",
ErrorDescription = "null result"
};
}
//if the poller is active then add all messages to a qeueue
//error catching
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
Match m = errorcheck.Match(response.ToString());
if (m.Success) {
string eCode = m.Groups[1].Value;
string eDes = Links.LinkedData.ErrorCodes[Convert.ToInt32(eCode)];
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Response is: {response}");
Logger.Log($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}", LogLevel.Error);
Console.ResetColor();
return new CommandResult {
Success = false,
Error = eCode,
ErrorDescription = eDes
};
}
var awaittask = SendSingleBlock(_msg);
PriorityTasks.Add(awaittask);
await awaittask;
return new CommandResult {
Success = true,
Error = "0000",
Response = response.ToString()
};
PriorityTasks.Remove(awaittask);
response = awaittask.Result;
} else {
//poller not active let the user manage message timing
response = await SendSingleBlock(_msg);
}
if (response == null) {
} catch {
return new CommandResult {
Success = false,
Error = "0000",
@@ -583,97 +620,63 @@ namespace MewtocolNet {
};
}
//error catching
Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase);
Match m = errorcheck.Match(response.ToString());
if (m.Success) {
string eCode = m.Groups[1].Value;
string eDes = Links.LinkedData.ErrorCodes[Convert.ToInt32(eCode)];
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Response is: {response}");
Console.WriteLine($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}");
Console.ResetColor();
return new CommandResult {
Success = false,
Error = eCode,
ErrorDescription = eDes
};
}
return new CommandResult {
Success = true,
Error = "0000",
Response = response.ToString()
};
}
private async Task<string> SendSingleBlock (string _blockString) {
Stopwatch sw = Stopwatch.StartNew();
using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) {
try {
await client.ConnectAsync(ip, port);
using (NetworkStream stream = client.GetStream()) {
var message = _blockString.ToHexASCIIBytes();
var messageAscii = BitConverter.ToString(message).Replace("-", " ");
//send request
try {
using (var sendStream = new MemoryStream(message)) {
await sendStream.CopyToAsync(stream);
Logger.Log($"OUT MSG: {_blockString}", LogLevel.Critical, this);
//log message sent
ASCIIEncoding enc = new ASCIIEncoding();
string characters = enc.GetString(message);
}
} catch (IOException) {
Logger.Log($"Critical IO exception on send", LogLevel.Critical, this);
return null;
} catch (SocketException) {
OnMajorSocketException();
return null;
}
//await result
StringBuilder response = new StringBuilder();
try {
byte[] responseBuffer = new byte[256];
do {
int bytes = stream.Read(responseBuffer, 0, responseBuffer.Length);
response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes));
}
while (stream.DataAvailable);
} catch (IOException) {
Logger.Log($"Critical IO exception on receive", LogLevel.Critical, this);
return null;
} catch (SocketException) {
OnMajorSocketException();
return null;
}
sw.Stop();
var curCycle = (int)sw.ElapsedMilliseconds;
if (Math.Abs(CycleTimeMs - curCycle) > 2) {
CycleTimeMs = curCycle;
}
Logger.Log($"IN MSG ({(int)sw.Elapsed.TotalMilliseconds}ms): {response}", LogLevel.Critical, this);
return response.ToString();
}
} catch (SocketException) {
OnMajorSocketException();
if (client == null || !client.Connected ) {
await ConnectTCP();
if (!client.Connected)
return null;
}
var message = _blockString.ToHexASCIIBytes();
//send request
using (var sendStream = new MemoryStream(message)) {
await sendStream.CopyToAsync(stream);
Logger.Log($"[--------------------------------]", LogLevel.Critical, this);
Logger.Log($"--> OUT MSG: {_blockString}", LogLevel.Critical, this);
}
//await result
StringBuilder response = new StringBuilder();
try {
byte[] responseBuffer = new byte[128 * 16];
bool endLineCode = false;
bool startMsgCode = false;
while (!endLineCode && !startMsgCode) {
do {
int bytes = await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
endLineCode = responseBuffer.Any(x => x == 0x0D);
startMsgCode = responseBuffer.Count(x => x == 0x25) > 1;
if (!endLineCode && !startMsgCode) break;
response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes));
}
while (stream.DataAvailable);
}
} catch (IOException) {
Logger.Log($"Critical IO exception on receive", LogLevel.Critical, this);
return null;
} catch (SocketException) {
OnMajorSocketException();
return null;
}
if(!string.IsNullOrEmpty(response.ToString())) {
Logger.Log($"<-- IN MSG (TXT): {response} ({response.Length} bytes)", LogLevel.Critical, this);
return response.ToString();
} else {
return null;
}
}
@@ -709,4 +712,5 @@ namespace MewtocolNet {
}
}

View File

@@ -117,7 +117,6 @@ namespace MewtocolNet {
string startStr = start.ToString().PadLeft(5, '0');
var wordLength = count / 2;
bool wasOdd = false;
if (count % 2 != 0)
wordLength++;
@@ -128,8 +127,13 @@ namespace MewtocolNet {
if(result.Success && !string.IsNullOrEmpty(result.Response)) {
var res = result;
var bytes = result.Response.ParseDTByteString(wordLength * 4).HexStringToByteArray();
if (bytes == null)
return null;
return bytes.BigToMixedEndian().Take(count).ToArray();
}
@@ -196,7 +200,6 @@ namespace MewtocolNet {
/// </summary>
/// <typeparam name="T">Type of number (short, ushort, int, uint, float)</typeparam>
/// <param name="_toRead">The register to read</param>
/// <param name="_stationNumber">Station number to access</param>
/// <returns>A result with the given NumberRegister containing the readback value and a result struct</returns>
public async Task<NRegisterResult<T>> ReadNumRegister<T> (NRegister<T> _toRead) {
@@ -205,40 +208,47 @@ namespace MewtocolNet {
string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolIdent()}";
var result = await SendCommandAsync(requeststring);
if(!result.Success || string.IsNullOrEmpty(result.Response)) {
return new NRegisterResult<T> {
Result = result,
Register = _toRead
};
var failedResult = new NRegisterResult<T> {
Result = result,
Register = _toRead
};
if (!result.Success || string.IsNullOrEmpty(result.Response)) {
return failedResult;
}
if (numType == typeof(short)) {
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
if (resultBytes == null) return failedResult;
var val = short.Parse(resultBytes, NumberStyles.HexNumber);
_toRead.SetValueFromPLC(val);
} else if (numType == typeof(ushort)) {
var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder();
if (resultBytes == null) return failedResult;
var val = ushort.Parse(resultBytes, NumberStyles.HexNumber);
_toRead.SetValueFromPLC(val);
} else if (numType == typeof(int)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
if (resultBytes == null) return failedResult;
var val = int.Parse(resultBytes, NumberStyles.HexNumber);
_toRead.SetValueFromPLC(val);
} else if (numType == typeof(uint)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
if (resultBytes == null) return failedResult;
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
_toRead.SetValueFromPLC(val);
} else if (numType == typeof(float)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
if (resultBytes == null) return failedResult;
//convert to unsigned int first
var val = uint.Parse(resultBytes, NumberStyles.HexNumber);
@@ -250,6 +260,7 @@ namespace MewtocolNet {
} else if (numType == typeof(TimeSpan)) {
var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder();
if (resultBytes == null) return failedResult;
//convert to unsigned int first
var vallong = long.Parse(resultBytes, NumberStyles.HexNumber);
var valMillis = vallong * 10;

View File

@@ -145,7 +145,7 @@ namespace MewtocolNet.Registers {
return boolReg.Value.ToString();
}
if (this is SRegister stringReg) {
return stringReg.Value.ToString();
return stringReg.Value ?? "";
}
return "Type of the register is not supported.";