Refactor code structure for improved readability and maintainability
This commit is contained in:
504
DaireApplication/Loops/serialThreadLoop.cs
Normal file
504
DaireApplication/Loops/serialThreadLoop.cs
Normal file
@@ -0,0 +1,504 @@
|
||||
using Avalonia.Threading;
|
||||
using AvaloniaApplication1.ViewModels;
|
||||
using DaireApplication.DataBase;
|
||||
using DaireApplication.ViewModels;
|
||||
using DaireApplication.Views;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DaireApplication.Loops
|
||||
{
|
||||
public class SerialRequest
|
||||
{
|
||||
public enum RequestType { Read, Write }
|
||||
public RequestType Type { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
public int ExpectedLength { get; set; }
|
||||
public TaskCompletionSource<byte[]> Completion { get; set; } = new();
|
||||
}
|
||||
|
||||
public class serialThreadLoop
|
||||
{
|
||||
private static readonly ConcurrentQueue<SerialRequest> writeQueue = new();
|
||||
private static readonly ConcurrentQueue<SerialRequest> readQueue = new();
|
||||
private static bool keepSendingRunning = false;
|
||||
|
||||
public static Task<byte[]> EnqueueWrite(byte[] data, int expectedLength)
|
||||
{
|
||||
var req = new SerialRequest { Type = SerialRequest.RequestType.Write, Data = data, ExpectedLength = expectedLength };
|
||||
writeQueue.Enqueue(req);
|
||||
return req.Completion.Task;
|
||||
}
|
||||
public static Task<byte[]> EnqueueRead(byte[] data, int expectedLength)
|
||||
{
|
||||
var req = new SerialRequest { Type = SerialRequest.RequestType.Read, Data = data, ExpectedLength = expectedLength };
|
||||
readQueue.Enqueue(req);
|
||||
return req.Completion.Task;
|
||||
}
|
||||
|
||||
public static async void SendViaSerial(MainWindow _mainWindow)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Debug.WriteLine("SendViaSerial started");
|
||||
// Start the keepSending background task if not already running
|
||||
if (!keepSendingRunning)
|
||||
{
|
||||
//Debug.WriteLine("Starting KeepSendingLoop");
|
||||
keepSendingRunning = true;
|
||||
_ = Task.Run(() => KeepSendingLoop(_mainWindow));
|
||||
}
|
||||
|
||||
List<float> values = new List<float>();
|
||||
while (_mainWindow.serialThreadRunning)
|
||||
{
|
||||
//Debug.WriteLine($"SendViaSerial loop iteration - Port status: {(_mainWindow._port?.IsOpen ?? false)}");
|
||||
if (_mainWindow._port != null && _mainWindow._port.IsOpen)
|
||||
{
|
||||
if (_mainWindow.reSendHolding)
|
||||
{
|
||||
values.Clear();
|
||||
_mainWindow._configrations = _mainWindow._config.ReadConfigrations();
|
||||
values.Add(_mainWindow.holdingRegister.resetError);
|
||||
values.Add(_mainWindow.holdingRegister.hvOut);
|
||||
values.Add(_mainWindow.holdingRegister.lvOut);
|
||||
values.Add(_mainWindow.holdingRegister.motor);
|
||||
values.Add(_mainWindow.holdingRegister.setTemp1);
|
||||
values.Add(_mainWindow.holdingRegister.setTemp2);
|
||||
values.Add(_mainWindow.holdingRegister.setTemp3);
|
||||
values.Add(_mainWindow.holdingRegister.setTemp4);
|
||||
values.Add((int)(_mainWindow._configrations[0].i_neut * 10));
|
||||
values.Add((int)(_mainWindow._configrations[0].i_mot1 * 10));
|
||||
values.Add((int)(_mainWindow._configrations[0].i_mot2 * 10));
|
||||
foreach (var item in _mainWindow._configrations)
|
||||
{
|
||||
values.Add(item.Max * 10);
|
||||
values.Add(item.Min * 10);
|
||||
values.Add(ConvertToDecimal(item.H_out.ToList()));
|
||||
values.Add(ConvertToDecimal(item.FC_out.ToList()));
|
||||
values.Add(ConvertToDecimal(item.SC_out.ToList()));
|
||||
values.Add(item.FC_Threshold * 10);
|
||||
values.Add(item.HeatConRange * 10);
|
||||
values.Add(item.kp);
|
||||
values.Add(item.ki);
|
||||
values.Add(item.kd);
|
||||
values.Add(item.kl);
|
||||
}
|
||||
_mainWindow._port.DiscardInBuffer();
|
||||
_mainWindow._port.DiscardOutBuffer();
|
||||
byte[] requstConfig = await _mainWindow._modBusMaster.WriteMultipleRegisters(0, values.ToArray());
|
||||
await EnqueueWrite(requstConfig, 7);
|
||||
_mainWindow.reSendHolding = false;
|
||||
}
|
||||
if (_mainWindow.sendConfig)
|
||||
{
|
||||
values.Clear();
|
||||
_mainWindow._configrations = _mainWindow._config.ReadConfigrations();
|
||||
if (!_mainWindow.dontResetOutPuts)
|
||||
{
|
||||
values.Add(0);
|
||||
values.Add(0);
|
||||
values.Add(0);
|
||||
values.Add(-10000);
|
||||
values.Add(-10000);
|
||||
values.Add(-10000);
|
||||
values.Add(-10000);
|
||||
}
|
||||
values.Add((int)(_mainWindow._configrations[0].i_neut * 10));
|
||||
values.Add((int)(_mainWindow._configrations[0].i_mot1 * 10));
|
||||
values.Add((int)(_mainWindow._configrations[0].i_mot2 * 10));
|
||||
foreach (var item in _mainWindow._configrations)
|
||||
{
|
||||
values.Add(item.Max * 10);
|
||||
values.Add(item.Min * 10);
|
||||
values.Add(ConvertToDecimal(item.H_out.ToList()));
|
||||
values.Add(ConvertToDecimal(item.FC_out.ToList()));
|
||||
values.Add(ConvertToDecimal(item.SC_out.ToList()));
|
||||
values.Add(item.FC_Threshold * 10);
|
||||
values.Add(item.HeatConRange * 10);
|
||||
values.Add(item.kp);
|
||||
values.Add(item.ki);
|
||||
values.Add(item.kd);
|
||||
values.Add(item.kl);
|
||||
}
|
||||
|
||||
byte[] requstConfig = await _mainWindow._modBusMaster.WriteMultipleRegisters(_mainWindow.dontResetOutPuts ? (ushort)8 : (ushort)1, values.ToArray());
|
||||
await EnqueueWrite(requstConfig, 7);
|
||||
_mainWindow.sendConfig = false;
|
||||
_mainWindow.dontResetOutPuts = false;
|
||||
}
|
||||
if (_mainWindow.restBoard)
|
||||
{
|
||||
_mainWindow.holdingRegister.hvOut = 0;
|
||||
_mainWindow.holdingRegister.lvOut = 0;
|
||||
_mainWindow.holdingRegister.motor = 0;
|
||||
_mainWindow.holdingRegister.setTemp1 = -10000;
|
||||
_mainWindow.holdingRegister.setTemp2 = -10000;
|
||||
_mainWindow.holdingRegister.setTemp3 = -10000;
|
||||
_mainWindow.holdingRegister.setTemp4 = -10000;
|
||||
|
||||
// Send reset data directly without using the async pattern
|
||||
List<float> resetValues = new List<float>();
|
||||
resetValues.Add(_mainWindow.holdingRegister.resetError);
|
||||
resetValues.Add(_mainWindow.holdingRegister.hvOut);
|
||||
resetValues.Add(_mainWindow.holdingRegister.lvOut);
|
||||
resetValues.Add(_mainWindow.holdingRegister.motor);
|
||||
resetValues.Add(_mainWindow.holdingRegister.setTemp1);
|
||||
resetValues.Add(_mainWindow.holdingRegister.setTemp2);
|
||||
resetValues.Add(_mainWindow.holdingRegister.setTemp3);
|
||||
resetValues.Add(_mainWindow.holdingRegister.setTemp4);
|
||||
|
||||
byte[] resetRequest = await _mainWindow._modBusMaster.WriteMultipleRegisters(0, resetValues.ToArray());
|
||||
await EnqueueWrite(resetRequest, 7);
|
||||
|
||||
_mainWindow.restBoard = false;
|
||||
}
|
||||
// Check if there's a pending write request
|
||||
if (_mainWindow._writeCompletionSource?.Task.Status == TaskStatus.WaitingForActivation)
|
||||
{
|
||||
List<float> writingValues = new List<float>();
|
||||
writingValues.Add(_mainWindow.holdingRegister.resetError);
|
||||
writingValues.Add(_mainWindow.holdingRegister.hvOut);
|
||||
writingValues.Add(_mainWindow.holdingRegister.lvOut);
|
||||
writingValues.Add(_mainWindow.holdingRegister.motor);
|
||||
writingValues.Add(_mainWindow.holdingRegister.setTemp1);
|
||||
writingValues.Add(_mainWindow.holdingRegister.setTemp2);
|
||||
writingValues.Add(_mainWindow.holdingRegister.setTemp3);
|
||||
writingValues.Add(_mainWindow.holdingRegister.setTemp4);
|
||||
byte[] requstConfig = await _mainWindow._modBusMaster.WriteMultipleRegisters(0, writingValues.ToArray());
|
||||
var result = await EnqueueWrite(requstConfig, 7);
|
||||
// Signal completion
|
||||
bool success = result.Length != 1 || result[0] != 0xFF;
|
||||
_mainWindow.SetWriteComplete(success);
|
||||
}
|
||||
var requstReadingInputs = await _mainWindow._modBusMaster.ReadInputRegisters(0, 18);
|
||||
await EnqueueRead(requstReadingInputs, 41);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static async Task KeepSendingLoop(MainWindow _mainWindow)
|
||||
{
|
||||
//Debug.WriteLine("KeepSendingLoop started");
|
||||
var startTime = DateTime.Now;
|
||||
int intervalMs = _mainWindow.screenData.sendingTime;
|
||||
int requestCount = 0;
|
||||
|
||||
while (_mainWindow.serialThreadRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.screenData = _mainWindow._screeen.ReadScreens()[0];
|
||||
//Debug.WriteLine($"KeepSendingLoop iteration - Port status: {(_mainWindow._port?.IsOpen ?? false)}, SendingTime: {_mainWindow.screenData.sendingTime}ms");
|
||||
|
||||
// Skip if port is not valid
|
||||
if (_mainWindow._port == null || !_mainWindow._port.IsOpen)
|
||||
{
|
||||
//Debug.WriteLine("Port not valid, waiting...");
|
||||
await Task.Delay(100); // Wait a bit before checking again
|
||||
continue;
|
||||
}
|
||||
|
||||
var scheduledTime = startTime.AddMilliseconds(requestCount * intervalMs);
|
||||
var now = DateTime.Now;
|
||||
var waitTime = (scheduledTime - now).TotalMilliseconds;
|
||||
if (waitTime > 0)
|
||||
await Task.Delay((int)waitTime);
|
||||
|
||||
SerialRequest req = null;
|
||||
if (!writeQueue.TryPeek(out req))
|
||||
readQueue.TryPeek(out req);
|
||||
|
||||
if (req != null)
|
||||
{
|
||||
var requestStart = DateTime.Now;
|
||||
var response = await keepSendingScenario(_mainWindow, req.Data, req.ExpectedLength,_mainWindow.screenData.sendingTime);
|
||||
var requestEnd = DateTime.Now;
|
||||
var requestElapsed = (requestEnd - requestStart).TotalMilliseconds;
|
||||
if (response.Length != 1 || response[0] != 0xFF)
|
||||
{
|
||||
//Debug.WriteLine($"SUCCESS: Total time for request: {requestElapsed} ms");
|
||||
if (req.Type == SerialRequest.RequestType.Read)
|
||||
{
|
||||
_mainWindow.inputesResponse = response;
|
||||
}
|
||||
req.Completion.SetResult(response);
|
||||
}
|
||||
else // failed after retries or timeout
|
||||
{
|
||||
//Debug.WriteLine($"FAILED: Total time for request: {requestElapsed} ms");
|
||||
req.Completion.SetResult(response); // set failure result
|
||||
}
|
||||
if (req.Type == SerialRequest.RequestType.Write)
|
||||
writeQueue.TryDequeue(out _);
|
||||
else
|
||||
readQueue.TryDequeue(out _);
|
||||
}
|
||||
|
||||
requestCount++;
|
||||
// If we are behind schedule, catch up
|
||||
if ((DateTime.Now - startTime).TotalMilliseconds > requestCount * intervalMs)
|
||||
requestCount = (int)((DateTime.Now - startTime).TotalMilliseconds / intervalMs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Debug.WriteLine($"Error in KeepSendingLoop: {ex.Message}");
|
||||
await Task.Delay(100); // Wait a bit before retrying
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<byte[]> keepSendingScenario(MainWindow _mainWindow, byte[] request, int expectedLength, int interval)
|
||||
{
|
||||
// Enforce minimum interval between packets
|
||||
using var cts = new CancellationTokenSource();
|
||||
int SENDING_INTERVAL = interval; // Minimum interval between sends
|
||||
|
||||
var now = DateTime.Now;
|
||||
var elapsed = (now - _mainWindow._lastPacketSendTime).TotalMilliseconds;
|
||||
if (elapsed < _mainWindow.screenData.sendingTime)
|
||||
{
|
||||
await Task.Delay((int)(SENDING_INTERVAL - elapsed));
|
||||
}
|
||||
_mainWindow._lastPacketSendTime = DateTime.Now;
|
||||
|
||||
//Debug.WriteLine("keepSending started");
|
||||
|
||||
// Calculate 4 character delay based on baud rate
|
||||
double fourCharDelay = (1.0 / _mainWindow.screenData.boundRate) * 44000;
|
||||
int noResponseRetryCount = 0;
|
||||
const int MAX_NO_RESPONSE_RETRIES = 3; // Try 3 times, once per second
|
||||
const int NO_RESPONSE_TIMEOUT = 1000; // 1 second between no-response retries
|
||||
const int TOTAL_INVALID_RETRY_TIME = 3000; // 3 seconds total for invalid responses
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
var lastSendTime = DateTime.Now;
|
||||
var lastValidResponseTime = DateTime.Now;
|
||||
bool hadValidResponse = false;
|
||||
|
||||
while ((DateTime.Now - startTime).TotalMilliseconds < TOTAL_INVALID_RETRY_TIME)
|
||||
{
|
||||
// Calculate time since last send
|
||||
var timeSinceLastSend = (DateTime.Now - lastSendTime).TotalMilliseconds;
|
||||
var timeSinceLastValidResponse = (DateTime.Now - lastValidResponseTime).TotalMilliseconds;
|
||||
|
||||
// Check for communication timeout
|
||||
if (timeSinceLastValidResponse >= TOTAL_INVALID_RETRY_TIME && !hadValidResponse)
|
||||
{
|
||||
//Debug.WriteLine("No valid response received within 3 seconds");
|
||||
if (!MainWindow.errors.Any(x => x.Condition == Error.GridCondition.NoBoardCom))
|
||||
{
|
||||
MainWindow.errors.Add(new Error
|
||||
{
|
||||
errorDate = DateTime.Now,
|
||||
Condition = Error.GridCondition.NoBoardCom
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// For no response case, check frequently for response
|
||||
if (noResponseRetryCount > 0 && timeSinceLastSend < NO_RESPONSE_TIMEOUT)
|
||||
{
|
||||
// Check for response every 10ms
|
||||
if (_mainWindow._port != null && _mainWindow._port.IsOpen)
|
||||
{
|
||||
if (_mainWindow._port.BytesToRead >= expectedLength)
|
||||
{
|
||||
// Wait 4-char delay when we have enough bytes
|
||||
await Task.Delay((int)fourCharDelay);
|
||||
var response = await TryReadOnce(_mainWindow, expectedLength, cts.Token);
|
||||
//Debug.WriteLine($"Response received during wait after {timeSinceLastSend}ms");
|
||||
|
||||
if (response.Length == expectedLength && IsValidCrc(response))
|
||||
{
|
||||
hadValidResponse = true;
|
||||
lastValidResponseTime = DateTime.Now; // Reset valid response timer
|
||||
var err = MainWindow.errors.FirstOrDefault(x => x.Condition == Error.GridCondition.NoBoardCom);
|
||||
if (err != null) err.isDeleted = true;
|
||||
|
||||
// For valid response, still respect minimum interval
|
||||
//if (timeSinceLastSend < SENDING_INTERVAL)
|
||||
//{
|
||||
// var remainingTime = SENDING_INTERVAL - timeSinceLastSend;
|
||||
// Debug.WriteLine($"Valid response received before {SENDING_INTERVAL}ms, waiting {remainingTime}ms");
|
||||
// await Task.Delay((int)remainingTime);
|
||||
//}
|
||||
return response;
|
||||
}
|
||||
|
||||
// For any response (even invalid), increment retry count and continue immediately
|
||||
noResponseRetryCount++;
|
||||
if (noResponseRetryCount >= MAX_NO_RESPONSE_RETRIES)
|
||||
{
|
||||
//Debug.WriteLine("Max retries exceeded");
|
||||
if (!MainWindow.errors.Any(x => x.Condition == Error.GridCondition.NoBoardCom))
|
||||
{
|
||||
MainWindow.errors.Add(new Error
|
||||
{
|
||||
errorDate = DateTime.Now,
|
||||
Condition = Error.GridCondition.NoBoardCom
|
||||
});
|
||||
}
|
||||
return new byte[] { 0xFF };
|
||||
}
|
||||
|
||||
// Break wait and send next retry immediately
|
||||
lastSendTime = DateTime.Now.AddMilliseconds(-NO_RESPONSE_TIMEOUT); // Force immediate retry
|
||||
break;
|
||||
}
|
||||
/*await Task.Delay(10);*/ // Small delay between checks
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// For invalid response case, wait minimum interval between retries
|
||||
if (timeSinceLastSend < SENDING_INTERVAL)
|
||||
{
|
||||
await Task.Delay(1); // Minimal delay to prevent tight loop
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send the request
|
||||
_mainWindow._port.Write(request, 0, request.Length);
|
||||
lastSendTime = DateTime.Now;
|
||||
//Debug.WriteLine($"Sent request at {lastSendTime:HH:mm:ss.fffffff}");
|
||||
|
||||
// Wait 4-char delay after sending
|
||||
await Task.Delay((int)fourCharDelay);
|
||||
|
||||
// Try to read response
|
||||
if (_mainWindow._port != null && _mainWindow._port.IsOpen && _mainWindow._port.BytesToRead >= expectedLength)
|
||||
{
|
||||
// Wait another 4-char delay when we have enough bytes
|
||||
await Task.Delay((int)fourCharDelay);
|
||||
var response = await TryReadOnce(_mainWindow, expectedLength, cts.Token);
|
||||
|
||||
// Handle valid response
|
||||
if (response.Length >= expectedLength && IsValidCrc(response))
|
||||
{
|
||||
hadValidResponse = true;
|
||||
lastValidResponseTime = DateTime.Now; // Reset valid response timer
|
||||
var err = MainWindow.errors.FirstOrDefault(x => x.Condition == Error.GridCondition.NoBoardCom);
|
||||
if (err != null) err.isDeleted = true;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle invalid/no response
|
||||
noResponseRetryCount++;
|
||||
//Debug.WriteLine($"Invalid/No response, attempt {noResponseRetryCount} of {MAX_NO_RESPONSE_RETRIES}");
|
||||
if (noResponseRetryCount >= MAX_NO_RESPONSE_RETRIES)
|
||||
{
|
||||
//Debug.WriteLine("Max retries exceeded");
|
||||
if (!MainWindow.errors.Any(x => x.Condition == Error.GridCondition.NoBoardCom))
|
||||
{
|
||||
MainWindow.errors.Add(new Error
|
||||
{
|
||||
errorDate = DateTime.Now,
|
||||
Condition = Error.GridCondition.NoBoardCom
|
||||
});
|
||||
}
|
||||
return new byte[] { 0xFF };
|
||||
}
|
||||
}
|
||||
|
||||
return new byte[] { 0xFF };
|
||||
}
|
||||
|
||||
private static async Task<byte[]> TryReadOnce(MainWindow _mainWindow, int expectedLength, CancellationToken token)
|
||||
{
|
||||
var buffer = new List<byte>();
|
||||
var startTime = DateTime.UtcNow;
|
||||
var timeout = 3000;
|
||||
while ((DateTime.UtcNow - startTime).TotalMilliseconds < timeout)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (_mainWindow._port != null && _mainWindow._port.IsOpen)
|
||||
{
|
||||
int available = _mainWindow._port.BytesToRead;
|
||||
if (available > 0)
|
||||
{
|
||||
byte[] temp = new byte[available];
|
||||
_mainWindow._port.Read(temp, 0, available);
|
||||
buffer.AddRange(temp);
|
||||
if (buffer.Count >= 3)
|
||||
{
|
||||
var response = buffer.ToArray();
|
||||
if (response.Length < expectedLength)
|
||||
{
|
||||
//Debug.WriteLine($"Invalid response length: got {response.Length}, expected {expectedLength}");
|
||||
return new byte[] { 0xFF };
|
||||
}
|
||||
if (!IsValidCrc(response))
|
||||
{
|
||||
//Debug.WriteLine("Invalid CRC in response");
|
||||
return new byte[] { 0xFF };
|
||||
}
|
||||
var err = MainWindow.errors.FirstOrDefault(x => x.Condition == Error.GridCondition.NoBoardCom);
|
||||
if (err != null) err.isDeleted = true;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
await Task.Delay(1);
|
||||
}
|
||||
//Debug.WriteLine("Response timeout");
|
||||
return new byte[] { 0xFF };
|
||||
}
|
||||
|
||||
private static bool IsValidCrc(byte[] data)
|
||||
{
|
||||
if (data.Length < 3) return false;
|
||||
ushort calculated = ComputeCRC(data.AsSpan(0, data.Length - 2));
|
||||
ushort received = (ushort)(data[^2] | (data[^1] << 8));
|
||||
return calculated == received;
|
||||
}
|
||||
private static ushort ComputeCRC(ReadOnlySpan<byte> data)
|
||||
{
|
||||
ushort crc = 0xFFFF;
|
||||
foreach (var b in data)
|
||||
{
|
||||
crc ^= b;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if ((crc & 0x0001) != 0)
|
||||
{
|
||||
crc >>= 1;
|
||||
crc ^= 0xA001;
|
||||
}
|
||||
else
|
||||
{
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
static int ConvertToDecimal(List<int> bitPositions)
|
||||
{
|
||||
int result = 0;
|
||||
if (bitPositions.Count() == 1 && bitPositions[0] == -1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
foreach (var pos in bitPositions)
|
||||
{
|
||||
if (pos is >= 0 and < 16)
|
||||
result |= 1 << pos;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user