Refactor code structure for improved readability and maintainability

This commit is contained in:
2025-08-06 21:21:34 +02:00
commit f539289f45
96 changed files with 45934 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using DaireApplication.ViewModels;
using DaireApplication.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DaireApplication.Loops
{
public class CheckInterNetLoop
{
public static async void CheckInterNet(MainWindow _mainWindow)
{
string textToDelete = "";
while (true)
{
if (!Error.IsInternetAvailable())
{
Dispatcher.UIThread.Post(() =>
{
if (_mainWindow.ContentArea.Content is Home || _mainWindow.ContentArea.Content is Admin)
{
if (!_mainWindow.nowifiLogo.IsVisible)
{
_mainWindow.wifiLogo.IsVisible = false;
_mainWindow.nowifiLogo.IsVisible = true;
}
}
else
{
_mainWindow.wifiLogo.IsVisible = false;
_mainWindow.nowifiLogo.IsVisible = false;
}
//if (!_mainWindow.warningMsg.Text.Contains("No Internet Access"))
//{
// _mainWindow.warningMsg.Text += "\nNo Internet Access";
// _mainWindow.warningLogo.IsVisible = true;
//}
});
}
else
{
Dispatcher.UIThread.Post(() =>
{
if (_mainWindow.ContentArea.Content is Home || _mainWindow.ContentArea.Content is Admin)
{
if (!_mainWindow.wifiLogo.IsVisible)
{
_mainWindow.nowifiLogo.IsVisible = false;
_mainWindow.wifiLogo.IsVisible = true;
}
}
else
{
_mainWindow.wifiLogo.IsVisible = false;
_mainWindow.nowifiLogo.IsVisible = false;
}
//if (_mainWindow.warningMsg.Text.Contains("No Internet Access"))
//{
// _mainWindow.warningMsg.Text=_mainWindow.warningMsg.Text.Replace("\nNo Internet Access", "");
//}
});
}
if (!string.IsNullOrEmpty(_mainWindow.warningMessage))
{
Dispatcher.UIThread.Post(() =>
{
if (!_mainWindow.warningMsg.Text.Contains($"\n-{_mainWindow.warningMessage}"))
{
textToDelete = _mainWindow.warningMessage;
_mainWindow.warningMsg.Text += $"\n-{_mainWindow.warningMessage}";
_mainWindow.warningLogo.IsVisible = true;
}
});
}
else
{
Dispatcher.UIThread.Post(() =>
{
if (_mainWindow.warningMsg.Text.Contains($"-{textToDelete}"))
{
_mainWindow.warningMsg.Text= _mainWindow.warningMsg.Text.Replace($"\n-{textToDelete}", "");
}
});
}
if ( string.IsNullOrEmpty(_mainWindow.warningMessage))
{
Dispatcher.UIThread.Post(() =>
{
_mainWindow.warningLogo.IsVisible = false;
});
}
Thread.Sleep(10);
}
}
}
}

View File

@@ -0,0 +1,100 @@
using Avalonia.Media;
using Avalonia.Threading;
using AvaloniaApplication1.ViewModels;
using DaireApplication.DataBase;
using DaireApplication.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DaireApplication.Loops
{
public class InteractiveUILoop
{
public static async void Flashing(MainWindow _mainWindow)
{
while (true)
{
// Flashing Pre Heater on
if (_mainWindow.isFlashPreHeating)
{
Dispatcher.UIThread.Post(() =>
{
if (_mainWindow.PreHeatingBtn.Foreground == Brushes.Transparent)
{
_mainWindow.PreHeatingBtn.Foreground = Brushes.White;
}
else
{
_mainWindow.PreHeatingBtn.Foreground = Brushes.Transparent;
}
});
}
// Flashing Pre Heater off
if (!_mainWindow.isFlashPreHeating)
{
Dispatcher.UIThread.Post(() =>
{
_mainWindow.PreHeatingBtn.Foreground = Brushes.White;
});
}
// Flashing Mixer on
if (_mainWindow.startMixerMotorFlashing == 1)
{
Dispatcher.UIThread.Post(() =>
{
if (_mainWindow.ContentArea.Content is Settings result)
{
var motorLable = result.MixerSP.Children[1] as Avalonia.Controls.Label;
var motorRectangel = result.MixerSP.Children[2] as Avalonia.Controls.Shapes.Rectangle;
if (motorLable.Foreground.ToString() == "#ff231f20")
{
motorLable.Foreground = Brushes.Transparent ;
motorRectangel.Fill = Brushes.Transparent;
}
else
{
motorLable.Foreground = Brush.Parse("#ff231f20");
motorRectangel.Fill = Brush.Parse(_mainWindow.PassiveColor);
}
}
});
}
// Flashing Fountain on (only when not in pedal auto mode)
if (_mainWindow.startFountainMotorFlashing == 1 && !_mainWindow.isPedalAutoMode)
{
Dispatcher.UIThread.Post(() =>
{
if (_mainWindow.ContentArea.Content is Settings result)
{
var fountainLable = result.FountainSP.Children[1] as Avalonia.Controls.Label;
var fountainRectangel = result.FountainSP.Children[2] as Avalonia.Controls.Shapes.Rectangle;
if (fountainLable.Foreground.ToString() == "#ff231f20")
{
fountainLable.Foreground = Brushes.Transparent;
fountainRectangel.Fill = Brushes.Transparent;
}
else
{
fountainLable.Foreground = Brush.Parse("#ff231f20");
fountainRectangel.Fill = Brush.Parse(_mainWindow.PassiveColor);
}
}
});
}
Thread.Sleep(250);
}
}
}
}

View File

@@ -0,0 +1,172 @@
using Avalonia.Threading;
using DaireApplication.Views;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace DaireApplication.Loops;
public class ScreenLoop
{
static void SetBrightness(int value)
{
//File.WriteAllText("/sys/class/backlight/backlight/brightness", value.ToString());
}
public static async void Screen(MainWindow _mainWindow)
{
var screenData = _mainWindow._screeen.ReadScreens()?[0];
SetBrightness((int)(screenData.brightness / 100 * 255));
while (true)
{
try
{
screenData = _mainWindow._screeen.ReadScreens()?[0];
if ((DateTime.Now - _mainWindow.lastActivity).TotalSeconds >= screenData?.offSec)
{
if (!MainWindow.isOff)
{
MainWindow.isOff = true;
SetBrightness(0);
}
}
else if ((DateTime.Now - _mainWindow.lastActivity).TotalSeconds >= screenData?.dimSec)
{
MainWindow.isOff = false;
SetBrightness(51); // 20% of 255
}
if (MainWindow.errors.Count > 0)
{
foreach (var item in MainWindow.errors.ToList())
{
if ((DateTime.Now - item.errorDate).TotalSeconds >= 2.5)
{
if (!item.isShowen)
{
Dispatcher.UIThread.Post(() =>
{
if (_mainWindow.errorMsg.Text.Contains($"- {item.GetDisplayNames(item.Condition)}"))
{
item.isShowen = true;
}
else
{
_mainWindow.errorMsg.Text += $"\n- {item.GetDisplayNames(item.Condition)}";
item.isShowen = true;
}
});
}
if (item.isDeleted)
{
Dispatcher.UIThread.Post(() =>
{
_mainWindow.errorMsg.Text = _mainWindow.errorMsg.Text
.Replace($"- {item.GetDisplayNames(item.Condition)}", "");
MainWindow.errors.Remove(item);
});
}
Dispatcher.UIThread.Post(() =>
{
_mainWindow.errorMsg.Text = _mainWindow.errorMsg.Text.Trim();
if (_mainWindow.ContentArea.Content is Settings settings && MainWindow.errors.Count > 0)
{
//_mainWindow.footerMsg.Text = "";
settings.mixerBtn.IsEnabled = false;
settings.fountainBtn.IsEnabled = false;
settings.moldHeaterBtn.IsEnabled = false;
settings.vibrationBtn.IsEnabled = false;
settings.vibHeaterBtn.IsEnabled = false;
_mainWindow.PreHeatingBtn.IsEnabled = false;
//_mainWindow.recipeStartBtn.IsEnabled = false;
}
});
}
}
if (MainWindow.errors.Count > 0)
{
if ((DateTime.Now - MainWindow.errors.Min(x => x.errorDate)).TotalSeconds >= 3.5)
{
Dispatcher.UIThread.Post(() =>
{
_mainWindow.errorLogo.IsVisible = true;
_mainWindow.errorTitel.Text = $"Error Number: {MainWindow.errors.Count}";
_mainWindow.errorMsg.Text = _mainWindow.errorMsg.Text.Trim();
//_mainWindow.footerMsg.Text = $"Error Numbers: {MainWindow.errors.Count}";
//_mainWindow.errorLogoBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
});
}
}
}
else
{
Dispatcher.UIThread.Post(() =>
{
//MainWindow.errors.Clear();
_mainWindow.errorLogo.IsVisible = false;
_mainWindow.errorPopupOverlay.IsVisible = false;
if (_mainWindow.ContentArea.Content is Settings settings)
{
//_mainWindow.footerMsg.Text = "Ready";
settings.moldHeaterBtn.IsEnabled = true;
settings.vibrationBtn.IsEnabled = true;
settings.vibHeaterBtn.IsEnabled = true;
_mainWindow.recipeStartBtn.IsEnabled = true;
if (_mainWindow.startRecipe != 1)
{
_mainWindow.PreHeatingBtn.IsEnabled = true;
settings.mixerBtn.IsEnabled = true;
settings.fountainBtn.IsEnabled = true;
}
var isFountOn = false;
var isMixerOn = false;
var fountainMotor =_mainWindow._mapping.Find(x => x.Name.ToLower() == "Helix".ToLower());
var mixerMotor =_mainWindow._mapping.Find(x => x.Name.ToLower() == "Mixer".ToLower());
if (isFountOn !=fountainMotor.BitNumbers.All(bit => (_mainWindow.holdingRegister.motor & (1 << bit)) != 0))
{
isFountOn = fountainMotor.BitNumbers.All(bit => (_mainWindow.holdingRegister.motor & (1 << bit)) != 0);
}
if (isMixerOn != mixerMotor.BitNumbers.All(bit => (_mainWindow.holdingRegister.motor & (1 << bit)) != 0))
{
isMixerOn = mixerMotor.BitNumbers.All(bit => (_mainWindow.holdingRegister.motor & (1 << bit)) != 0);
}
if (isFountOn && isMixerOn) // both motores are on
{
if (_mainWindow.startRecipe == 1 && (_mainWindow.Heating == 10 || _mainWindow.cooling == 10 || _mainWindow.pouring == 10))
{
_mainWindow.unPause = true;
}
}
}
});
}
await Task.Delay(10);
}
catch (Exception)
{
}
}
}
}

View File

@@ -0,0 +1,54 @@
using DaireApplication.Views;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DaireApplication.Loops
{
public class TouchLoop
{
static void SetBrightness(int value)
{
//File.WriteAllText("/sys/class/backlight/backlight/brightness", value.ToString());
}
public static async void Touch(MainWindow _mainWindow)
{
static void WatchInput(int bright, MainWindow _mainWindow)
{
string inputDevice = "/dev/input/event1"; // adjust based on your touch device
using (FileStream fs = new FileStream(inputDevice, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[24];
fs.Read(buffer, 0, buffer.Length);
_mainWindow.lastActivity = DateTime.Now;
SetBrightness(bright); // Restore brightness if touch
}
}
var screenData = _mainWindow._screeen.ReadScreens()?[0];
while (true)
{
try
{
screenData = _mainWindow._screeen.ReadScreens()?[0];
int brightnessValue = (int)((screenData.brightness) / 100.0 * 255);
MainWindow.isOff = false;
//WatchInput(brightnessValue, _mainWindow);
Thread.Sleep(200);
}
catch (Exception)
{
}
}
}
}
}

View 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;
}
}
}