505 lines
27 KiB
C#
505 lines
27 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|