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 Completion { get; set; } = new(); } public class serialThreadLoop { private static readonly ConcurrentQueue writeQueue = new(); private static readonly ConcurrentQueue readQueue = new(); private static bool keepSendingRunning = false; public static Task 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 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 values = new List(); 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 resetValues = new List(); 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 writingValues = new List(); 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 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 TryReadOnce(MainWindow _mainWindow, int expectedLength, CancellationToken token) { var buffer = new List(); 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 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 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; } } }