using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Threading; using AvaloniaApplication1.ViewModels; using DaireApplication.DataBase; using DaireApplication.Loops; using DaireApplication.ViewModels; using DynamicData; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO.Ports; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace DaireApplication.Views; public partial class MainWindow : Window { #region properties List buffer = new List(); public ModBusMaster _modBusMaster = new ModBusMaster(); public MachineTable _machine = new MachineTable(); public ConfigrationTable _config = new ConfigrationTable(); public List _configrations = new List(); public Mapping _map = new Mapping(); public ScreeenTable _screeen = new(); public ErrorSettingsTable _error = new(); public List _mapping = new List(); public static bool isOff { get; set; } const double deadZone = 1.5; // Changeable based on stability needs bool shouldRunFountain = false; public readonly SemaphoreSlim _keepSendingLock = new(1, 1); public SerialPort _port { get; set; } private static Thread monitorThread; private static Thread screenThread; private static Thread touchThread; private static Thread internetThread; private static Thread InteractiveUIThread; private static Thread serialThread; public bool serialThreadRunning = true; public bool isRunning = true; public bool restBoard { get; set; } public bool sendConfig { get; set; } = true; public bool reSendHolding { get; set; } public bool dontResetOutPuts { get; set; } bool allBitsOn = false; bool allPedalBitsOn = false; public int pedalState { get; set; } = -1; public int pedalStateChanged { get; set; } = -1; public float recipeHeatingGoal { get; set; } public float recipeCoolingGoal { get; set; } public float recipePouringGoal { get; set; } public string ActiveColor { get; set; } = "#A4275D"; public string PassiveColor { get; set; } = "#666666"; //Pre Heating public bool isFlashPreHeating { get; set; } = false; public bool isReadingTemp { get; set; } = true; public int startPreHeating { get; set; } = -1; public int writingMaxTemp { get; set; } = -1; //Mixer Motor private Timer mixerTimer; private Timer preMixerTimer; private Timer unifiedMotorTimer; private static int mixerSeconds = 1; private static int preMixerSeconds = 1; public bool setMixerTimerOnce { get; set; } = false; public bool checkMixerTWT_HWTH { get; set; } = false; public bool isMixerMotorOn { get; set; } = false; public int startMixerMotor { get; set; } = -1; public int startMixerMotorFlashing { get; set; } = -1; public int sendComMixerMotor { get; set; } = -1; //Fountain Motor private Timer fountainTimer; private Timer fountainPauseTimer; private Timer noChoiceChoosenTimer; private static int fountainSeconds = 1; private static int fountainPauseSeconds = 1; private static int noChoiceChoosenSeconds = 0; public bool setFountainTimerOnce { get; set; } = false; public bool checkFountainTMT_PMT { get; set; } = false; public bool isFountainMotorOn { get; set; } = false; public int startFountainMotor { get; set; } = -1; public int startFountainMotorFlashing { get; set; } = -1; public int sendComFountainMotor { get; set; } = -1; public double comTankTemp { get; set; } = 0; public double comFountainTemp { get; set; } = 0; public double comPumpTemp { get; set; } = 0; //MOLD HEATER(off:0,on:1) , VIBRATION(off:0,on:1) , VIB. HEATER(off:0,on:1) public int moldHeaterMotor { get; set; } = -1; public int vibrationMotor { get; set; } = -1; public int vibHeaterMotor { get; set; } = -1; //Pedal(manual=0,auto=1) public int pedalMotor { get; set; } = -1; //Recipe Start public int startRecipe { get; set; } = 0; public int sendComTankTemp { get; set; } = -1; //phase 1 heating public int Heating { get; set; } = -1; public int sendComHeating { get; set; } = -1; public int setHeatingTimerOnce { get; set; } = -1; public Timer heatingTimer; public int heatingSeconds { get; set; } = 0; //phase 2 cooling public int cooling { get; set; } = -1; public int sendComCooling { get; set; } = -1; public int setCoolingTimerOnce { get; set; } = -1; public Timer coolingTimer; public int coolingSeconds { get; set; } = 0; //phase 3 pouring public int pouring { get; set; } = -1; public int sendComPouring { get; set; } = -1; public int setPouringTimerOnce { get; set; } = -1; public Timer pouringTimer; public int pouringSeconds { get; set; } = 0; //start the pumb public int PumbOn { get; set; } = -1; public Timer pedalOnTimer; public Timer pedalOffTimer; public int pedalOnSeconds { get; set; } = 0; public int pedalOffSeconds { get; set; } = 0; // 1 turn off ,0 turn on public int setPedalTimerOnce { get; set; } = -1; //Board public bool resetPort { get; set; } = false; public bool keepSendingFlag { get; set; } = false; public DateTime lastActivity = DateTime.Now; public ScreeenTable screenData = new(); public static List errors = new(); public bool pause { get; set; } public bool unPause { get; set; } public bool isPaused { get; set; } public bool pauseTimer { get; set; } public bool pauseTempTracking { get; set; } public string warningMessage { get; set; } public HoldingRegister holdingRegister = new HoldingRegister(); public bool turnOnFountainMotor { get; set; } public bool stopRecipeFlag { get; set; } public bool isCoolingDelayMode { get; set; } = false; public bool isPouringDelayMode { get; set; } = false; public bool tempWarningAccepted { get; set; } = false; public bool isPedalAutoMode { get; set; } = false; public bool isAutomaticFountainControlActive { get; set; } = false; // Recipe phase tracking for footer message management private enum RecipePhase { None, PreHeating, HeatingDelay, CoolingPhase, CoolingDelay, PouringPhase, Completed } private RecipePhase currentRecipePhase = RecipePhase.None; /// /// Safely update footer message based on current recipe phase /// private string lastFooterMessage = ""; private void UpdateFooterMessage(RecipePhase phase, string message) { // Don't update if recipe is completed (unless setting to completed) if (currentRecipePhase == RecipePhase.Completed && phase != RecipePhase.Completed) { return; } // Only update if message has actually changed if (lastFooterMessage == message) { return; } // Only update if we're in the correct phase or transitioning to it if (currentRecipePhase == phase || phase != RecipePhase.None) { currentRecipePhase = phase; lastFooterMessage = message; Dispatcher.UIThread.Post(() => { footerMsg.Text = message; }); } } public Timer stopMixerTimer; public int stopMixerSecondes { get; set; } public Timer stopFountainTimer; public int stopFountainSecondes { get; set; } public DateTime _lastPacketSendTime = DateTime.MinValue; public byte[] inputesResponse = new byte[] { 0xFF }; // Replace isWriting flag with async method public TaskCompletionSource _writeCompletionSource = new TaskCompletionSource(); #endregion public Task WriteToSerialAsync(string caller) { _writeCompletionSource = new TaskCompletionSource(); // Optionally log or store the caller for debugging Debug.WriteLine($"WriteToSerialAsync called by: {caller}"); return _writeCompletionSource.Task; } public void SetWriteComplete(bool success = true) { _writeCompletionSource?.TrySetResult(success); } #region Construction public MainWindow() { InitializeComponent(); // Hide cursor using unclutter try { var process = new System.Diagnostics.Process(); process.StartInfo.FileName = "unclutter"; process.StartInfo.Arguments = "-idle 0"; // Hide immediately process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; process.Start(); } catch (Exception ex) { Console.WriteLine($"Failed to start unclutter: {ex.Message}"); } ContentArea.Content = new Home(this); this.Closing += OnClosingWindow; _machine = _machine.ReadMachine(); _configrations = _config.ReadConfigrations(); _mapping = _map.ReadMappings(); serialThread = new Thread(() => serialThreadLoop.SendViaSerial(this)) { IsBackground = true }; serialThread.Start(); internetThread = new Thread(() => CheckInterNetLoop.CheckInterNet(this)) { IsBackground = true }; internetThread.Start(); screenThread = new Thread(() => ScreenLoop.Screen(this)) { IsBackground = true }; screenThread.Start(); InteractiveUIThread = new Thread(() => InteractiveUILoop.Flashing(this)) { IsBackground = true }; InteractiveUIThread.Start(); touchThread = new Thread(() => TouchLoop.Touch(this)) { IsBackground = true }; touchThread.Start(); monitorThread = new Thread(() => MonitorPortsLoop()) { IsBackground = true, Priority = ThreadPriority.Highest }; monitorThread.Start(); } #endregion private async void MonitorPortsLoop() { byte[] tankeResponse = new byte[256]; var fountainResponse = new byte[256]; double tankBottomTempValue = -1; double tankWallTempValue = -1; double pumpTempValue = -1; double fountainTempValue = -1; var tankBottom = _mapping.Find(x => x.Name == "Tank Bottom Temp"); var tankWall = _mapping.Find(x => x.Name == "Tank Wall Temp"); var pump = _mapping.Find(x => x.Name == "Pump Temp"); var fountain = _mapping.Find(x => x.Name == "Fountain Temp"); static List ToBinary(int number) { return Convert.ToString(number, 2) .PadLeft(16, '0') .Reverse() .Select(c => c == '1') .ToList(); } while (true) { if (isRunning) { Dispatcher.UIThread.Post(() => { footerDate.Text = DateTime.Now.ToString("dd/MM/yyyy"); footerTime.Text = DateTime.Now.ToString("hh:mm tt"); }); screenData = _screeen.ReadScreens()?[0]; try { if (!SerialPort.GetPortNames().Contains(screenData.port)) { if (_port != null && _port.IsOpen) { _port.Close(); } _port = null; Dispatcher.UIThread.Post(() => { errors.Clear(); footerMsg.Text = "Not Connected:Port Name Not Found"; //Debug.WriteLine("port name not found"); footerMsg.Foreground = Avalonia.Media.Brushes.DarkRed; footerMsg.IsVisible = true; }); } else { if (resetPort) { Debug.WriteLine("Port reset initiated"); resetAll(); if (_port != null && _port.IsOpen) { Debug.WriteLine("Closing existing port"); _port.Close(); } _port = null; if (ConnectToSerialPort()) { Debug.WriteLine("Port reconnected successfully"); Dispatcher.UIThread.Post(() => { //Conntected footerMsg.Text = "Connected"; footerMsg.IsVisible = true; sendConfig = true; }); } else { Debug.WriteLine("Failed to reconnect port"); Dispatcher.UIThread.Post(() => { errors.Clear(); footerMsg.Text = "Not Connected"; footerMsg.IsVisible = true; }); } resetPort = false; Debug.WriteLine("Port reset completed"); } if (_port == null) { // Connect to the found device if (!ConnectToSerialPort()) { Dispatcher.UIThread.Post(() => { errors.Clear(); footerMsg.Text = "Not Connected"; }); } else { serialThreadRunning = true; sendConfig = true; Dispatcher.UIThread.Post(() => { footerMsg.Text = "Connected"; }); } } else { try { if (!_port.IsOpen) { _port.Open(); } List inputValues = new List(); if (isReadingTemp) { if (!_port.IsOpen) { _port.Open(); } // reading inputes var requstReadingInputs = await _modBusMaster.ReadInputRegisters(0, 18); if (inputesResponse.Length != 1 && inputesResponse[0] != 0xFF) { var result = inputesResponse.Skip(3).Take(inputesResponse.Count() - 5).ToArray(); for (int i = 0; i < result.Length; i = i + 2) { inputValues.Add(((result[i] << 8) | result[i + 1])); } var brdFlags = ToBinary(inputValues[0]); var inputes = ToBinary(inputValues[1]); #region Errors //Errors try { // Grid Vac if (inputValues[2] > 220 * 1.1 || inputValues[3] > 220 * 1.1 || inputValues[4] > 220 * 1.1) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.GridVACHigh) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.GridVACHigh }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.GridVACHigh) != null) { errors.First(x => x.Condition == Error.GridCondition.GridVACHigh).isDeleted = true; } } if (inputValues[2] < 220 * 0.9 || inputValues[3] < 220 * 0.9 || inputValues[4] < 220 * 0.9) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.GridVACLow) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.GridVACLow }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.GridVACLow) != null) { errors.First(x => x.Condition == Error.GridCondition.GridVACLow).isDeleted = true; } } //// Grid Freq _error = _error.ReadErrorSettings()[0]; if (inputValues[17] > (_error.gridFreq * 10) * 1.1) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.GridFrequencyHigh) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.GridFrequencyHigh }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.GridFrequencyHigh) != null) { errors.First(x => x.Condition == Error.GridCondition.GridFrequencyHigh).isDeleted = true; } } if (inputValues[17] < (_error.gridFreq * 10) * 0.9) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.GridFrequencyLow) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.GridFrequencyLow }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.GridFrequencyLow) != null) { errors.First(x => x.Condition == Error.GridCondition.GridFrequencyLow).isDeleted = true; } } //// Ext Power if (brdFlags[3]) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.NoExternalPower) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.NoExternalPower }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.NoExternalPower) != null) { errors.First(x => x.Condition == Error.GridCondition.NoExternalPower).isDeleted = true; } } //// missing Phase if (brdFlags[5] && _error.ReadErrorSettings()[0].phaseNumber == 3) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.MissingPhase) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.MissingPhase }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.MissingPhase) != null) { errors.First(x => x.Condition == Error.GridCondition.MissingPhase).isDeleted = true; } } //// Phase sequence if (brdFlags[4] && _error.ReadErrorSettings()[0].phaseNumber == 3) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.PhaseSequence) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.PhaseSequence }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.PhaseSequence) != null) { errors.First(x => x.Condition == Error.GridCondition.PhaseSequence).isDeleted = true; } } //com port1 if (brdFlags[1]) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.ComPort1) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.ComPort1 }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.ComPort1) != null) { errors.First(x => x.Condition == Error.GridCondition.ComPort1).isDeleted = true; } } //com port2 if (brdFlags[2]) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.ComPort2) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.ComPort2 }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.ComPort2) != null) { errors.First(x => x.Condition == Error.GridCondition.ComPort2).isDeleted = true; } } //hi curr neut if (brdFlags[6]) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.HiCurrNeut) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.HiCurrNeut }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.HiCurrNeut) != null) { errors.First(x => x.Condition == Error.GridCondition.HiCurrNeut).isDeleted = true; } } //hi curr mot1 if (brdFlags[7]) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.HiCurrMot1) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.HiCurrMot1 }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.HiCurrMot1) != null) { errors.First(x => x.Condition == Error.GridCondition.HiCurrMot1).isDeleted = true; } } //hi curr mot2 if (brdFlags[8]) { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.HiCurrMot2) == null) { errors.Add(new Error { errorDate = DateTime.Now, Condition = Error.GridCondition.HiCurrMot2 }); } } else { if (errors.FirstOrDefault(x => x.Condition == Error.GridCondition.HiCurrMot2) != null) { errors.First(x => x.Condition == Error.GridCondition.HiCurrMot2).isDeleted = true; } } } catch (Exception) { } #endregion Dispatcher.UIThread.Post(async () => { if (ContentArea.Content is Diagnostics diagnostics) { //Board Falgs foreach (var item in diagnostics.flagRectangles) { if (brdFlags[int.Parse(item.Tag.ToString())]) { item.Fill = Brush.Parse(diagnostics.RedColor); } else { item.Fill = Brush.Parse(diagnostics.GrayColor); } } //power Inputes diagnostics.ph1.Text = inputValues[2].ToString(); diagnostics.ph2.Text = inputValues[3].ToString(); diagnostics.ph3.Text = inputValues[4].ToString(); diagnostics.i_nut.Text = (inputValues[5] / 10.0).ToString("0.0"); diagnostics.gridFreq.Text = (inputValues[17] / 10.0).ToString("0.0"); // Inputes foreach (var item in diagnostics.InputesElements.OfType().ToList()) { var text = item.Children[0] as TextBlock; var border = item.Children[1] as Border; if (inputes[int.Parse(item.Tag.ToString())]) { text.Text = "ACTIVE"; border.Background = Brush.Parse(diagnostics.PinkColor); diagnostics.InputesElements.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.GreenColor); } else { text.Text = "PASSIVE"; border.Background = Brush.Parse(diagnostics.GrayColor); diagnostics.InputesElements.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.GrayColor); } } //Analog diagnostics.an1.Text = inputValues[12].ToString(); diagnostics.an2.Text = inputValues[13].ToString(); //Temp diagnostics.t1.Text = ((short)inputValues[8] / 10f).ToString("0.0"); diagnostics.t2.Text = ((short)inputValues[9] / 10f).ToString("0.0"); diagnostics.t3.Text = ((short)inputValues[10] / 10f).ToString("0.0"); diagnostics.t4.Text = ((short)inputValues[11] / 10f).ToString("0.0"); //InternalTemp diagnostics.internalTemp.Text = (inputValues[15] / 10.0).ToString("0.0"); diagnostics.hsTemp.Text = (inputValues[14] / 10.0).ToString("0.0"); if (inputValues[16] > 40) { diagnostics.extPowerLed.Fill = Brush.Parse(diagnostics.RedColor); } else { diagnostics.extPowerLed.Fill = Brush.Parse(diagnostics.GrayColor); } diagnostics.ExtPwr.Text = (inputValues[16] / 10.0).ToString("0.0"); } if (ContentArea.Content is Settings settings) { if (settings.pedalStateTxt.Text != "AUTO") { var pedal = _mapping.Find(x => x.Name.ToLower() == "pedal"); ushort registerValue = (ushort)inputValues[1]; if (allBitsOn != pedal.BitNumbers.All(bit => (registerValue & (1 << bit)) != 0)) { allBitsOn = pedal.BitNumbers.All(bit => (registerValue & (1 << bit)) != 0); } if (!allBitsOn) { settings.pedalUnderLine.Fill = Brush.Parse(settings.PassiveColor); } else { settings.pedalUnderLine.Fill = Brush.Parse(settings.ActiveColor); } } if (startFountainMotorFlashing != 1) { //fountain var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (allBitsOn != fount.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0)) { allBitsOn = fount.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0); } var fountainLable = settings.FountainSP.Children[1] as Avalonia.Controls.Label; var fontainRectangel = settings.FountainSP.Children[2] as Avalonia.Controls.Shapes.Rectangle; if (!allBitsOn) { fountainLable.Content = "OFF"; fountainLable.Foreground = Brush.Parse("#ff231f20"); fontainRectangel.Fill = Brush.Parse(PassiveColor); //isFountainMotorOn = false; } else { fountainLable.Content = "ON"; fountainLable.Foreground = Brush.Parse("#ff231f20"); fontainRectangel.Fill = Brush.Parse(ActiveColor); //isFountainMotorOn = true; } } if (startMixerMotorFlashing != 1) { //mixer var mixer = _mapping.Find(x => x.Name.ToLower() == "Mixer".ToLower()); if (allBitsOn != mixer.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0)) { allBitsOn = mixer.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0); } var mixerLable = settings.MixerSP.Children[1] as Avalonia.Controls.Label; var mixerRectangel = settings.MixerSP.Children[2] as Avalonia.Controls.Shapes.Rectangle; if (!allBitsOn) { mixerLable.Content = "OFF"; mixerLable.Foreground = Brush.Parse("#ff231f20"); mixerRectangel.Fill = Brush.Parse(PassiveColor); //isMixerMotorOn = false; } else { mixerLable.Content = "ON"; mixerLable.Foreground = Brush.Parse("#ff231f20"); mixerRectangel.Fill = Brush.Parse(ActiveColor); //isMixerMotorOn = true; } } } if (ContentArea.Content is ManualControl manual) { manual.pumbRealTemp.Text = comPumpTemp.ToString("0.0"); manual.ChocolateRealTemp.Text = comFountainTemp.ToString("0.0"); manual.tankWallRealTemp.Text = tankWallTempValue.ToString("0.0"); manual.pumbRealTemp.Text = comTankTemp.ToString("0.0"); } }); _mapping = _map.ReadMappings(); //reading Tank Bottom tankBottom = _mapping.Find(x => x.Name == "Tank Bottom Temp"); if (tankBottom != null) { if (tankBottom.BitNumbers.Count > 0) { tankBottomTempValue = 0; foreach (var item in tankBottom.BitNumbers) { tankBottomTempValue += ((short)inputValues[item] / 10f); } tankBottomTempValue /= tankBottom.BitNumbers.Count; tankBottomTempValue = Math.Round(tankBottomTempValue, 1); } } //reading Tank Wall tankWall = _mapping.Find(x => x.Name == "Tank Wall Temp"); if (tankWall != null) { if (tankWall.BitNumbers.Count > 0) { tankWallTempValue = 0; foreach (var item in tankWall.BitNumbers) { tankWallTempValue += ((short)inputValues[item] / 10f); } tankWallTempValue /= tankWall.BitNumbers.Count; tankWallTempValue = Math.Round(tankWallTempValue, 1); } } //reading Pump pump = _mapping.Find(x => x.Name == "Pump Temp"); if (pump != null) { if (pump.BitNumbers.Count > 0) { pumpTempValue = 0; foreach (var item in pump.BitNumbers) { pumpTempValue += ((short)inputValues[item] / 10f); } pumpTempValue /= pump.BitNumbers.Count; pumpTempValue = Math.Round(pumpTempValue, 1); } } //reading Fountain fountain = _mapping.Find(x => x.Name == "Fountain Temp"); if (fountain != null) { if (fountain.BitNumbers.Count > 0) { fountainTempValue = 0; foreach (var item in fountain.BitNumbers) { fountainTempValue += ((short)inputValues[item] / 10f); } fountainTempValue /= fountain.BitNumbers.Count; fountainTempValue = Math.Round(fountainTempValue, 1); } } Dispatcher.UIThread.Post(() => { if (tankBottomTempValue == -1 && tankWallTempValue == -1) { if (ContentArea.Content is Settings) { footerMsg.Text = "No Tank Data To Read"; tankBottomTempValue = 0; tankWallTempValue = 0; } } else { //comTankTemp = tankBottomTempValue < tankWallTempValue ? tankBottomTempValue : tankWallTempValue; comTankTemp = tankBottomTempValue; comTankTemp = comTankTemp == -1 ? 0 : comTankTemp; } if (pumpTempValue == -1) { if (ContentArea.Content is Settings) { footerMsg.Text = "No Pump Data To Read"; pumpTempValue = 0; } } else { comPumpTemp = pumpTempValue; } if (fountainTempValue == -1) { if (ContentArea.Content is Settings) { footerMsg.Text = "No Chocolate Data To Read"; fountainTempValue = 0; } } else { comFountainTemp = fountainTempValue; } if (ContentArea.Content is Settings result) { if (result.TankTempValue.Content?.ToString() != comTankTemp.ToString() || result.FountainTempValue.Content?.ToString() != comFountainTemp.ToString()) { result.TankTempValue.Content = (comTankTemp).ToString("0.0"); result.FountainTempValue.Content = (comFountainTemp).ToString("0.0"); } } //if (comTankTemp >= _machine.TankMaxHeat && comFountainTemp >= _machine.PumbMaxHeat) //{ // if (preMixerTimer==null) // { // preMixerTimer = new Timer(PreMixerTimer, null, 0, 1000); // } // mixerSeconds++; //} //else //{ // if (preMixerTimer != null) // { // preMixerTimer.Change(Timeout.Infinite, Timeout.Infinite); // preMixerTimer = null; // } //} } ); } //read mot val if (inputValues.Count != 0) { Dispatcher.UIThread.Post(async () => { if (ContentArea.Content is Diagnostics diagnostics) { diagnostics.curr1.Text = (inputValues[6] / 10.0).ToString("0.0"); diagnostics.curr2.Text = (inputValues[7] / 10.0).ToString("0.0"); } }); } } //Pre Heating if (startPreHeating == 1) { List setTempValues = new List(); setTempValues.AddRange([holdingRegister.setTemp1, holdingRegister.setTemp2, holdingRegister.setTemp3, holdingRegister.setTemp4]); if (writingMaxTemp == 1) { tankBottom = _mapping.Find(x => x.Name == "Tank Bottom Temp"); if (tankBottom != null) { if (tankBottom.BitNumbers.Count > 0) { foreach (var item in tankBottom.BitNumbers) { setTempValues[item - 8] = (int)_machine.TankMaxHeat * 10; } } } tankWall = _mapping.Find(x => x.Name == "Tank Wall Temp"); if (tankWall != null) { if (tankWall.BitNumbers.Count > 0) { foreach (var item in tankWall.BitNumbers) { setTempValues[item - 8] = (int)_machine.TankMaxHeat * 10; } } } pump = _mapping.Find(x => x.Name == "Pump Temp"); if (pump != null) { if (pump.BitNumbers.Count > 0) { foreach (var item in pump.BitNumbers) { setTempValues[item - 8] = (int)_machine.PumbMaxHeat * 10; } } } fountain = _mapping.Find(x => x.Name == "Fountain Temp"); if (fountain != null) { if (fountain.BitNumbers.Count > 0) { foreach (var item in fountain.BitNumbers) { setTempValues[item - 8] = -10000; } } } holdingRegister.setTemp1 = setTempValues[0]; holdingRegister.setTemp2 = setTempValues[1]; holdingRegister.setTemp3 = setTempValues[2]; holdingRegister.setTemp4 = setTempValues[3]; await WriteToSerialAsync("PreHeating"); Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { result.recipeSettings.IsEnabled = false; isFlashPreHeating = true; footerMsg.Text = "Pre-Heating Active"; } }); writingMaxTemp = -1; } } if (startPreHeating == 0) { List setTempValues = new List(); setTempValues.AddRange([holdingRegister.setTemp1, holdingRegister.setTemp2, holdingRegister.setTemp3, holdingRegister.setTemp4]); if (writingMaxTemp == 0) { tankBottom = _mapping.Find(x => x.Name == "Tank Bottom Temp"); if (tankBottom != null) { if (tankBottom.BitNumbers.Count > 0) { foreach (var item in tankBottom.BitNumbers) { setTempValues[item - 8] = -10000; } } } tankWall = _mapping.Find(x => x.Name == "Tank Wall Temp"); if (tankWall != null) { if (tankWall.BitNumbers.Count > 0) { foreach (var item in tankWall.BitNumbers) { setTempValues[item - 8] = -10000; } } } pump = _mapping.Find(x => x.Name == "Pump Temp"); if (pump != null) { if (pump.BitNumbers.Count > 0) { foreach (var item in pump.BitNumbers) { setTempValues[item - 8] = -10000; } } } fountain = _mapping.Find(x => x.Name == "Fountain Temp"); if (fountain != null) { if (fountain.BitNumbers.Count > 0) { foreach (var item in fountain.BitNumbers) { setTempValues[item - 8] = -10000; } } } holdingRegister.setTemp1 = setTempValues[0]; holdingRegister.setTemp2 = setTempValues[1]; holdingRegister.setTemp3 = setTempValues[2]; holdingRegister.setTemp4 = setTempValues[3]; await WriteToSerialAsync("PreHeating"); Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { result.recipeSettings.IsEnabled = true; if (!stopRecipeFlag) { footerMsg.Text = "Pre-Heating Stopped"; } } }); writingMaxTemp = -1; } } //Mixer Motor //Mixer Motorf if (checkMixerTWT_HWTH) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { recipeHeatingGoal = result._recipeTable.HeatingGoal; recipeCoolingGoal = result._recipeTable.CoolingGoal; recipePouringGoal = result._recipeTable.PouringGoal; } if ((comTankTemp >= _machine.TankMaxHeat - screenData.warningLimit) && (comTankTemp <= _machine.TankMaxHeat + screenData.warningLimit)) { if (startMixerMotor != 1) { if (mixerTimer == null) { if (mixerSeconds == 1) { mixerSeconds = _machine.MixerDelay; } mixerTimer = new Timer(MixerTimer, null, 0, 1000); //setMixerTimerOnce = false; } } } else if (comTankTemp <= recipeCoolingGoal - 3 && startMixerMotor == 1) { if (startMixerMotor != 0) { if (stopMixerTimer == null) { stopMixerSecondes = 5; stopMixerTimer = new Timer(StopMixerTimer, null, 0, 1000); } } startMixerMotorFlashing = 1; } else if (startMixerMotor != 1) { if (startMixerMotor != 0) { sendComMixerMotor = 0; } startMixerMotorFlashing = 1; } }); } if (!checkMixerTWT_HWTH) { if (sendComMixerMotor == 0 && startMixerMotorFlashing == 0) { startMixerMotor = -1; var mixer = _mapping.Find(x => x.Name.ToLower() == "Mixer".ToLower()); if (mixer != null) { if (mixer.BitNumbers.Count > 0) { //turn the motor off and make the button stable foreach (var bit in mixer.BitNumbers) { holdingRegister.motor &= (ushort)~(1 << bit); } await WriteToSerialAsync("Mixer Off due Clicking"); Dispatcher.UIThread.Post(() => { if (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; motorLable.Content = "OFF"; motorLable.Foreground = Brush.Parse("#ff231f20"); motorRectangel.Fill = Brush.Parse(PassiveColor); if (startRecipe != 1 && !stopRecipeFlag) { footerMsg.Text = "Mixer is OFF"; } } }); sendComMixerMotor = -1; startMixerMotorFlashing = -1; } } } } if (startMixerMotorFlashing == 1) { if (sendComMixerMotor == 0) { //turn the motor off startMixerMotor = 0; var mixer = _mapping.Find(x => x.Name.ToLower() == "Mixer".ToLower()); if (mixer != null) { if (mixer.BitNumbers.Count > 0) { //turn the motor off and make the button stable foreach (var bit in mixer.BitNumbers) { holdingRegister.motor &= (ushort)~(1 << bit); } await WriteToSerialAsync("Mixer Off due to drop in temp"); Dispatcher.UIThread.Post(() => { if (startRecipe != 1) { footerMsg.Text = "waiting for tank target temperature"; } }); sendComMixerMotor = -1; } } } } if (startMixerMotorFlashing == 0 && sendComMixerMotor == 1 && !isPaused) { startMixerMotor = 1; //turn the motor on and make the button stable var mixer = _mapping.Find(x => x.Name.ToLower() == "Mixer".ToLower()); if (mixer != null) { if (mixer.BitNumbers.Count > 0) { foreach (var bit in mixer.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } await WriteToSerialAsync("Mixer On"); Dispatcher.UIThread.Post(() => { if (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; motorLable.Content = "ON"; motorLable.Foreground = Brush.Parse("#ff231f20"); motorRectangel.Fill = Brush.Parse(ActiveColor); if (startRecipe != 1) { footerMsg.Text = "Temperature is OK,Mixer is on"; } //if (comTankTemp < result._recipeTable.PouringGoal || // comFountainTemp < result._recipeTable.PouringGoal) //{ // mixerSeconds = 1; // //checkTMT_PMT = true; // setMixerTimerOnce = true; //} //else //{ // //checkTMT_PMT = true; // setMixerTimerOnce = true; //} } }); startMixerMotorFlashing = -1; sendComMixerMotor = -1; } } } //Fountain Motor - Normal temperature-based control (when not in pedal auto mode) if (checkFountainTMT_PMT && !isPedalAutoMode) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { recipeHeatingGoal = result._recipeTable.HeatingGoal; recipeCoolingGoal = result._recipeTable.CoolingGoal; recipePouringGoal = result._recipeTable.PouringGoal; } if ((comPumpTemp >= _machine.PumbMaxHeat - screenData.warningLimit) && (comPumpTemp <= _machine.PumbMaxHeat + screenData.warningLimit)) // check the temp and make it stop only if it below cooling temp - 3 degrees { if (startFountainMotor != 1) { if (fountainTimer is null) { if (fountainSeconds == 1) { fountainSeconds = _machine.PumbDelay; } fountainTimer = new Timer(FountainTimer, null, 0, 1000); //setFountainTimerOnce = false; } } } else if (comPumpTemp <= recipeCoolingGoal - 3 && startFountainMotor == 1) { if (startFountainMotor != 0) { if (stopFountainTimer == null) { stopFountainSecondes = 5; stopFountainTimer = new Timer(StopFountainTimer, null, 0, 1000); } } if (!isPedalAutoMode) { startFountainMotorFlashing = 1; } } else if (startFountainMotor != 1) { if (startFountainMotor != 0) { sendComFountainMotor = 0; } if (!isPedalAutoMode) { startFountainMotorFlashing = 1; } } Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { if (startFountainMotor != 1 && fountainTimer == null) { if (!result.fountainDelayCounter.Text.Equals(comPumpTemp.ToString("0.0"))) { result.fountainDelayTxt.Text = "Current Temp:"; result.fountainDelayCounter.Text = comPumpTemp.ToString("0.0"); result.fountainTargetTxt.Text = "Target Temp:"; result.fountainTagetTemp.Text = _machine.PumbMaxHeat.ToString("0.0"); result.fountainDelayTxt.IsVisible = true; result.fountainDelayCounter.IsVisible = true; result.fountainTargetTxt.IsVisible = true; result.fountainTagetTemp.IsVisible = true; } } } }); }); } //Fountain Motor - Manual control (when not in pedal auto mode) if (!checkFountainTMT_PMT && !isPedalAutoMode) { if (sendComFountainMotor == 0 && startFountainMotorFlashing == 0) { startFountainMotor = -1; var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (fount != null) { if (fount.BitNumbers.Count > 0) { //turn the motor off and make the button stable foreach (var bit in fount.BitNumbers) { holdingRegister.motor &= (ushort)~(1 << bit); } await WriteToSerialAsync("Fountain Off due to click"); //waitting for 10 sec before pause if (fountainPauseTimer == null && startRecipe == 1 && (Heating == 1 || heatingTimer != null || cooling == 1 || coolingTimer != null || pouring == 1 || pouringTimer != null )) { fountainPauseSeconds = 10; fountainPauseTimer = new Timer(FountainPauseTimer, null, 0, 1000); } Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { result.fountainDelayCounter.Text = "-1"; var fountainLable = result.FountainSP.Children[1] as Avalonia.Controls.Label; var fontainRectangel = result.FountainSP.Children[2] as Avalonia.Controls.Shapes.Rectangle; fountainLable.Content = "OFF"; fountainLable.Foreground = Brush.Parse("#ff231f20"); fontainRectangel.Fill = Brush.Parse(PassiveColor); if (startRecipe != 1 && !stopRecipeFlag) { footerMsg.Text = "Chocolate is OFF"; } stopRecipeFlag = false; } }); sendComFountainMotor = -1; startFountainMotorFlashing = -1; } } } } if (startFountainMotorFlashing == 1 && !isPedalAutoMode) { if (sendComFountainMotor == 0) { startFountainMotor = 0; //turn the motor off var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (fount != null) { if (fount.BitNumbers.Count > 0) { foreach (var bit in fount.BitNumbers) { holdingRegister.motor &= (ushort)~(1 << bit); } await WriteToSerialAsync("Fountain Off due to drop in temp"); //pause the recipe if it started //waitting for 10 sec before pause if (fountainPauseTimer == null && startRecipe == 1 && (Heating == 1 || heatingTimer != null || cooling == 1 || coolingTimer != null || pouring == 1 || pouringTimer != null )) { fountainPauseSeconds = 10; fountainPauseTimer = new Timer(FountainPauseTimer, null, 0, 1000); } Dispatcher.UIThread.Post(() => { if (startRecipe == 1 && (Heating == 1 || cooling == 1 || pouring == 1)) { //footerMsg.Text = "Recipe Paused... waiting for target temperature"; } else if (startRecipe != 1) { footerMsg.Text = "waiting for pump target temperature"; } }); //checkTMT_PMT = true; sendComFountainMotor = -1; } } } } if (startFountainMotorFlashing == 0 && sendComFountainMotor == 1 && errors.Count == 0 && !isPaused) { startFountainMotor = 1; //turn the motor on and make the button stable var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (fount != null) { if (fount.BitNumbers.Count > 0) { foreach (var bit in fount.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } await WriteToSerialAsync("Fountain On"); if (isPaused) { unPause = true; } //if (startRecipe == 1 && (Heating == 10 || cooling == 10 || pouring == 10)) //{ // if (Heating == 10) // { // Heating = 1; // sendComHeating = 1; // } // else if (cooling == 10) // { // cooling = 1; // sendComCooling = 1; // } // else if (pouring == 10) // { // pouring = 1; // sendComPouring = 1; // } //} if (fountainPauseTimer != null) { fountainPauseTimer = null; fountainPauseSeconds = 10; } Dispatcher.UIThread.Post(async () => { if (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; fountainLable.Content = "ON"; fountainLable.Foreground = Brush.Parse("#ff231f20"); fountainRectangel.Fill = Brush.Parse(ActiveColor); if (startRecipe == 1 && (Heating == 1 || cooling == 1 || pouring == 1)) { if (Heating == 1) { footerMsg.Text = "Heating phase"; } else if (cooling == 1) { footerMsg.Text = "Cooling phase"; } else if (pouring == 1) { footerMsg.Text = "Prepare for pouring"; } } else if (startRecipe != 1) { footerMsg.Text = "Temperature is OK,Chocolate is on"; } //if (comTankTemp < result._recipeTable.PouringGoal || // comFountainTemp < result._recipeTable.PouringGoal) //{ // fountainSeconds = 1; // setFountainTimerOnce = true; //} //else //{ // setFountainTimerOnce = true; //} } }); startFountainMotorFlashing = -1; sendComFountainMotor = -1; } } } // Fountain Motor - Direct control is now handled by pedal state in auto mode if (moldHeaterMotor == 0) // off MOLD HEATER { var moldHeater = _mapping.Find(x => x.Name == "Mold Heater"); if (moldHeater != null) { if (moldHeater.BitNumbers.Count > 0) { foreach (var bit in moldHeater.BitNumbers) { holdingRegister.lvOut &= (ushort)~(1 << bit); } await WriteToSerialAsync("MoldHeater"); Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { var StackPanel = result.moldHeaterBtn.Content as StackPanel; var Label = StackPanel.Children; var underLine = Label[2] as Avalonia.Controls.Shapes.Rectangle; var targetLable = Label[1] as Label; targetLable.Content = "OFF"; underLine.Fill = Brush.Parse("#666666"); } else if (ContentArea.Content is ManualControl manual) { manual.MoldHeaterStatus.Text = "OFF"; manual.MoldHeaterUnderline.Fill = Brush.Parse("#666666"); } }); } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Admin"; }); } } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Programming Team"; }); } moldHeaterMotor = -1; } else if (moldHeaterMotor == 1) // on MOLD HEATER { var moldHeater = _mapping.Find(x => x.Name == "Mold Heater"); if (moldHeater != null) { if (moldHeater.BitNumbers.Count > 0) { foreach (var bit in moldHeater.BitNumbers) { holdingRegister.lvOut |= (ushort)(1 << bit); } await WriteToSerialAsync("MoldHeater"); Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { var StackPanel = result.moldHeaterBtn.Content as StackPanel; var Label = StackPanel.Children; var underLine = Label[2] as Avalonia.Controls.Shapes.Rectangle; var targetLable = Label[1] as Label; targetLable.Content = "ON"; underLine.Fill = Brush.Parse("#A4275D"); } else if (ContentArea.Content is ManualControl manual) { manual.MoldHeaterStatus.Text = "ON"; manual.MoldHeaterUnderline.Fill = Brush.Parse("#A4275D"); } }); } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Admin"; }); } } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Programming Team"; }); } moldHeaterMotor = -1; } if (vibrationMotor == 0) // off VIBRATION { var vibrator = _mapping.Find(x => x.Name == "Vibrator"); if (vibrator != null) { if (vibrator.BitNumbers.Count > 0) { foreach (var bit in vibrator.BitNumbers) { holdingRegister.hvOut &= (ushort)~(1 << bit); } await WriteToSerialAsync("Vibrator"); Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { var StackPanel = result.vibrationBtn.Content as StackPanel; var Label = StackPanel.Children; var underLine = Label[2] as Avalonia.Controls.Shapes.Rectangle; var targetLable = Label[1] as Label; targetLable.Content = "OFF"; underLine.Fill = Brush.Parse("#666666"); } else if (ContentArea.Content is ManualControl manual) { manual.VibrationStatus.Text = "OFF"; manual.VibrationUnderline.Fill = Brush.Parse("#666666"); } }); } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Admin"; }); } } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Programming Team"; }); } vibrationMotor = -1; } else if (vibrationMotor == 1) // on VIBRATION { var vibrator = _mapping.Find(x => x.Name == "Vibrator"); if (vibrator != null) { if (vibrator.BitNumbers.Count > 0) { foreach (var bit in vibrator.BitNumbers) { holdingRegister.hvOut |= (ushort)(1 << bit); } await WriteToSerialAsync("Vibrator"); Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { var StackPanel = result.vibrationBtn.Content as StackPanel; var Label = StackPanel.Children; var underLine = Label[2] as Avalonia.Controls.Shapes.Rectangle; var targetLable = Label[1] as Label; targetLable.Content = "ON"; underLine.Fill = Brush.Parse("#A4275D"); } else if (ContentArea.Content is ManualControl manual) { manual.VibrationStatus.Text = "ON"; manual.VibrationUnderline.Fill = Brush.Parse("#A4275D"); } }); } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Admin"; }); } } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Programming Team"; }); } vibrationMotor = -1; } if (vibHeaterMotor == 0) // off VIB. HEATER { var vibHeater = _mapping.Find(x => x.Name == "Vibrator Heater"); if (vibHeater != null) { if (vibHeater.BitNumbers.Count > 0) { foreach (var bit in vibHeater.BitNumbers) { holdingRegister.lvOut &= (ushort)~(1 << bit); } await WriteToSerialAsync("VibratorHeater"); Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { var StackPanel = result.vibHeaterBtn.Content as StackPanel; var Label = StackPanel.Children; var underLine = Label[2] as Avalonia.Controls.Shapes.Rectangle; var targetLable = Label[1] as Label; targetLable.Content = "OFF"; underLine.Fill = Brush.Parse("#666666"); } else if (ContentArea.Content is ManualControl manual) { manual.VibHeaterStatus.Text = "OFF"; manual.VibHeaterUnderline.Fill = Brush.Parse("#666666"); } }); } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Admin"; }); } } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Programming Team"; }); } vibHeaterMotor = -1; } else if (vibHeaterMotor == 1) // on VIB. HEATER { var vibHeater = _mapping.Find(x => x.Name == "Vibrator Heater"); if (vibHeater != null) { if (vibHeater.BitNumbers.Count > 0) { foreach (var bit in vibHeater.BitNumbers) { holdingRegister.lvOut |= (ushort)(1 << bit); } await WriteToSerialAsync("VibratorHeater"); Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { var StackPanel = result.vibHeaterBtn.Content as StackPanel; var Label = StackPanel.Children; var underLine = Label[2] as Avalonia.Controls.Shapes.Rectangle; var targetLable = Label[1] as Label; targetLable.Content = "ON"; underLine.Fill = Brush.Parse("#A4275D"); } else if (ContentArea.Content is ManualControl manual) { manual.VibHeaterStatus.Text = "ON"; manual.VibHeaterUnderline.Fill = Brush.Parse("#A4275D"); } }); } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Admin"; }); } } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Programming Team"; }); } vibHeaterMotor = -1; } //Start Recipe if (startRecipe == 1) { var tankBtm = _mapping.Find(x => x.Name.ToLower() == "Tank Bottom Temp".ToLower()); tankWall = _mapping.Find(x => x.Name.ToLower() == "Tank Wall Temp".ToLower()); var pumb = _mapping.Find(x => x.Name.ToLower() == "Pump Temp".ToLower()); var fount = _mapping.Find(x => x.Name.ToLower() == "Fountain Temp".ToLower()); var fountainMotor = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); var mixerMotor = _mapping.Find(x => x.Name.ToLower() == "Mixer".ToLower()); if (tankBtm != null && tankWall != null && pumb != null && fount != null) { if (tankBtm.BitNumbers.Count > 0 && tankWall.BitNumbers.Count > 0 && pumb.BitNumbers.Count > 0 && fount.BitNumbers.Count > 0) { List setTempValues = new List(); setTempValues.AddRange([holdingRegister.setTemp1, holdingRegister.setTemp2, holdingRegister.setTemp3, holdingRegister.setTemp4]); byte[] response = new byte[] { 0xFF }; var isFountOn = false; var isMixerOn = false; if (isFountOn != fountainMotor.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0)) { isFountOn = fountainMotor.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0); } if (isMixerOn != mixerMotor.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0)) { isMixerOn = mixerMotor.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0); } if (isFountOn && isMixerOn) // both motors are on { // Check if both mixer and chocolate have reached heating goal bool mixerGoalMet = comTankTemp >= recipeHeatingGoal; bool chocolateGoalMet = comFountainTemp >= recipeHeatingGoal; bool bothGoalsMet = mixerGoalMet && chocolateGoalMet; if (bothGoalsMet && (cooling != 1 && pouring != 1)) { // Both goals met - check temperature before starting heating timer bool tempTooHigh = comFountainTemp > (recipeHeatingGoal + screenData.warningLimit); if (tempTooHigh && !tempWarningAccepted && heatingTimer == null) { // Show error window when temperature is too high before starting heating delay if (noChoiceChoosenTimer == null) { noChoiceChoosenSeconds = 0; noChoiceChoosenTimer = new Timer(NoChoiceChoosenTimer, null, 0, 1000); } Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { settings.tempErrorPopupOverlay.IsVisible = true; } // Don't change footer message - keep showing pre-heating status }); } else if (heatingTimer == null) { // Temperature is acceptable OR user accepted warning - start heating timer heatingTimer = new Timer(HeatingTimer, null, 1000, 1000); heatingSeconds = _machine.HeatingDelay; } } else if ((Heating != 1) && (cooling != 1 || cooling != 10) && (pouring != 1 || pouring != 10) && heatingTimer == null && coolingTimer == null && pouringTimer == null && startRecipe == 1) { // Goals not met yet - continue heating (only if recipe is still running) sendComTankTemp = 1; Heating = 1; sendComHeating = 1; setHeatingTimerOnce = 1; // Only update footer if we're in pre-heating phase UpdateFooterMessage(RecipePhase.PreHeating, $"Pre-heating"); } } //if (comPumpTemp>=_machine.PumbMaxHeat) //{ // //start timer // if (fountainTimer==null) // { // if (fountainSeconds == 1) // { // fountainSeconds = _machine.PumbDelay; // } // fountainTimer = new Timer(FountainTimer, null, 0, 1000); // } // if (turnOnFountainMotor) // { // fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); // if (fount != null) // { // if (fount.BitNumbers.Count > 0) // { // foreach (var bit in fount.BitNumbers) // { // holdingRegister.motor |= (ushort)(1 << bit); // } // isWriting = true; // } // } // turnOnFountainMotor = false; // } //} //else //{ // startFountainMotorFlashing = 1; //} if (sendComTankTemp == 1) { sendComTankTemp = -1; } if (pause) { isPaused = true; if (Heating == 1) { Heating = 10; } else if (cooling == 1) { cooling = 10; } else if (pouring == 1) { pouring = 10; } //PumbOn = -1; if (tankBtm != null) { if (tankBtm.BitNumbers.Count > 0) { foreach (var item in tankBtm.BitNumbers) { setTempValues[item - 8] = (int)_machine.TankMaxHeat * 10; } } } if (tankWall != null) { if (tankWall.BitNumbers.Count > 0) { foreach (var item in tankWall.BitNumbers) { setTempValues[item - 8] = (int)_machine.TankMaxHeat * 10; } } } if (pumb != null) { if (pumb.BitNumbers.Count > 0) { foreach (var item in pumb.BitNumbers) { setTempValues[item - 8] = (int)_machine.PumbMaxHeat * 10; } } } if (fount != null) { if (fount.BitNumbers.Count > 0) { foreach (var item in fount.BitNumbers) { setTempValues[item - 8] = -10000; } } } holdingRegister.setTemp1 = setTempValues[0]; holdingRegister.setTemp2 = setTempValues[1]; holdingRegister.setTemp3 = setTempValues[2]; holdingRegister.setTemp4 = setTempValues[3]; holdingRegister.motor = 0; await WriteToSerialAsync("RecipePause"); startMixerMotor = 0; startFountainMotor = 0; if (heatingTimer != null) { heatingTimer.Change(Timeout.Infinite, Timeout.Infinite); heatingSeconds = _machine.HeatingDelay; heatingTimer = null; } if (coolingTimer != null) { coolingTimer.Change(Timeout.Infinite, Timeout.Infinite); coolingSeconds = _machine.CoolingDelay; coolingTimer = null; } if (pouringTimer != null) { pouringTimer.Change(Timeout.Infinite, Timeout.Infinite); pouringSeconds = _machine.PouringDelay; pouringTimer = null; } if (pedalOffTimer != null) { pedalOffTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOffSeconds = 0; } if (pedalOnTimer != null) { pedalOnTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOnSeconds = 0; } if (fountainPauseTimer != null) { fountainPauseTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainPauseSeconds = 10; fountainPauseTimer = null; } if (fountainTimer != null) { fountainTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainTimer = null; } if (mixerTimer != null) { mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); mixerTimer = null; } Dispatcher.UIThread.Post(async () => { footerMsg.Text = "Recipe Paused"; }); pause = false; } else if (unPause) { isPaused = false; if (Heating == 10) { Heating = 1; sendComHeating = 1; } else if (cooling == 10) { cooling = 1; sendComCooling = 1; } else if (pouring == 10) { pouring = 1; sendComPouring = 1; } PumbOn = 1; Dispatcher.UIThread.Post(async () => { footerMsg.Text = "Recipe Continued"; }); unPause = false; } if (Heating == 1) { if (errors.Count > 0) { if ((DateTime.Now - errors.Min(x => x.errorDate)).TotalSeconds >= 3.5) { if (!isPaused) { pause = true; } } } else { Dispatcher.UIThread.Post(async () => { if (ContentArea.Content is Settings result) { // Only start heating delay timer when temperature reaches or exceeds the heating goal if (comFountainTemp * 10 >= (result._recipeTable?.HeatingGoal * 10) - (screenData.warningLimit * 10)) { if (setHeatingTimerOnce == 1) { heatingTimer = new Timer(HeatingTimer, null, 1000, 1000); heatingSeconds = _machine.HeatingDelay; setHeatingTimerOnce = -1; } } else if (sendComHeating == 1) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { recipeHeatingGoal = result._recipeTable.HeatingGoal; recipeCoolingGoal = result._recipeTable.CoolingGoal; recipePouringGoal = result._recipeTable.PouringGoal; } }); foreach (var item in tankBtm.BitNumbers) { setTempValues[item - 8] = (int)_machine.TankMaxHeat; } foreach (var item in tankWall.BitNumbers) { setTempValues[item - 8] = (int)_machine.TankMaxHeat; } foreach (var item in pumb.BitNumbers) { setTempValues[item - 8] = (int)_machine.PumbMaxHeat; } foreach (var item in fount.BitNumbers) { setTempValues[item - 8] = (int)recipeHeatingGoal; } holdingRegister.setTemp1 = setTempValues[0] * 10; holdingRegister.setTemp2 = setTempValues[1] * 10; holdingRegister.setTemp3 = setTempValues[2] * 10; holdingRegister.setTemp4 = setTempValues[3] * 10; if (fountainMotor != null) { if (fountainMotor.BitNumbers.Count > 0) { foreach (var bit in fountainMotor.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } isFountainMotorOn = true; } } if (mixerMotor != null) { if (mixerMotor.BitNumbers.Count > 0) { foreach (var bit in mixerMotor.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } isMixerMotorOn = true; } } await WriteToSerialAsync("HeatingPhase"); Dispatcher.UIThread.Post(async () => { footerMsg.Text = "Heating phase"; }); sendComHeating = -1; } } }); } } else if (cooling == 1) { if (errors.Count > 0) { if (!isPaused) { //pause = true; } } else { if (sendComCooling == 1) { foreach (var item in tankBtm.BitNumbers) { setTempValues[item - 8] = -1000; } foreach (var item in tankWall.BitNumbers) { setTempValues[item - 8] = -1000; } foreach (var item in pumb.BitNumbers) { setTempValues[item - 8] = (int)_machine.PumbMinHeat; } foreach (var item in fount.BitNumbers) { setTempValues[item - 8] = (int)recipeCoolingGoal; } holdingRegister.setTemp1 = setTempValues[0] * 10; holdingRegister.setTemp2 = setTempValues[1] * 10; holdingRegister.setTemp3 = setTempValues[2] * 10; holdingRegister.setTemp4 = setTempValues[3] * 10; if (fountainMotor != null) { if (fountainMotor.BitNumbers.Count > 0) { foreach (var bit in fountainMotor.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } isFountainMotorOn = true; } } if (mixerMotor != null) { if (mixerMotor.BitNumbers.Count > 0) { foreach (var bit in mixerMotor.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } isMixerMotorOn = true; } } await WriteToSerialAsync("CoolingPhase"); Dispatcher.UIThread.Post(async () => { footerMsg.Text = "Cooling phase"; }); sendComCooling = -1; } Dispatcher.UIThread.Post(async () => { if (ContentArea.Content is Settings result) { // Check if current temperature is already at pouring goal - skip cooling phase if (Math.Abs((double)(comFountainTemp * 10) - (double)(result._recipeTable?.PouringGoal * 10 ?? 0)) <= (screenData.warningLimit * 10)) { if (setCoolingTimerOnce == 1) { // Skip cooling phase, go directly to pouring cooling = -1; pouring = 1; sendComPouring = 1; setPouringTimerOnce = 1; setCoolingTimerOnce = -1; Dispatcher.UIThread.Post(() => { footerMsg.Text = "Already at pouring temperature - starting pouring phase"; }); } } // Only start cooling delay timer when temperature reaches or goes below the cooling goal else if (comFountainTemp * 10 <= (result._recipeTable?.CoolingGoal * 10) + (screenData.warningLimit * 10)) { if (setCoolingTimerOnce == 1) { if (coolingTimer == null) { coolingTimer = new Timer(CoolingTimer, null, 1000, 1000); } else { // Ensure the timer is running at the correct interval coolingTimer.Change(1000, 1000); } coolingSeconds = _machine.CoolingDelay; setCoolingTimerOnce = -1; } } } }); } } else if (pouring == 1) { if (errors.Count > 0) { if (!isPaused) { //pause = true; } } else { if (sendComPouring == 1) { foreach (var item in tankBtm.BitNumbers) { setTempValues[item - 8] = (int)_machine.TankMaxHeat; } foreach (var item in tankWall.BitNumbers) { setTempValues[item - 8] = (int)_machine.TankMaxHeat; } foreach (var item in pumb.BitNumbers) { setTempValues[item - 8] = (int)_machine.PumbMaxHeat; } foreach (var item in fount.BitNumbers) { setTempValues[item - 8] = (int)recipePouringGoal; } holdingRegister.setTemp1 = setTempValues[0] * 10; holdingRegister.setTemp2 = setTempValues[1] * 10; holdingRegister.setTemp3 = setTempValues[2] * 10; holdingRegister.setTemp4 = setTempValues[3] * 10; if (fountainMotor != null) { if (fountainMotor.BitNumbers.Count > 0) { foreach (var bit in fountainMotor.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } isFountainMotorOn = true; } } if (mixerMotor != null) { if (mixerMotor.BitNumbers.Count > 0) { foreach (var bit in mixerMotor.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } isMixerMotorOn = true; } } await WriteToSerialAsync("PouringPhase"); Dispatcher.UIThread.Post(async () => { footerMsg.Text = "Prepare for pouring"; }); sendComPouring = -1; } Dispatcher.UIThread.Post(async () => { if (ContentArea.Content is Settings result) { // Only start pouring delay timer when temperature reaches the pouring goal (within tolerance) if (Math.Abs((double)(comFountainTemp * 10) - (double)(result._recipeTable?.PouringGoal * 10 ?? 0)) <= (screenData.warningLimit * 10)) { if (setPouringTimerOnce == 1) { foreach (var item in tankBtm.BitNumbers) { setTempValues[item - 8] = (int)(recipePouringGoal + _machine.PreHeatingTemp); } foreach (var item in tankWall.BitNumbers) { setTempValues[item - 8] = (int)(recipePouringGoal + _machine.PreHeatingTemp); } foreach (var item in pumb.BitNumbers) { setTempValues[item - 8] = -1000; } foreach (var item in fount.BitNumbers) { setTempValues[item - 8] = (int)recipePouringGoal; } holdingRegister.setTemp1 = setTempValues[0] * 10; holdingRegister.setTemp2 = setTempValues[1] * 10; holdingRegister.setTemp3 = setTempValues[2] * 10; holdingRegister.setTemp4 = setTempValues[3] * 10; if (fountainMotor != null) { if (fountainMotor.BitNumbers.Count > 0) { foreach (var bit in fountainMotor.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } isFountainMotorOn = true; } } if (mixerMotor != null) { if (mixerMotor.BitNumbers.Count > 0) { foreach (var bit in mixerMotor.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } isMixerMotorOn = true; } } await WriteToSerialAsync("PouringPhase"); if (pouringTimer == null) { pouringTimer = new Timer(PouringTimer, null, 1000, 1000); } else { // Ensure the timer is running at the correct interval pouringTimer.Change(1000, 1000); } pouringSeconds = _machine.PouringDelay; setPouringTimerOnce = -1; } } } }); } } } else { Dispatcher.UIThread.Post(() => { footerMsg.Text = "Contact Admin"; }); } } } if (PumbOn == 1) { //Dispatcher.UIThread.Post(() => //{ // recipeStartBtn.IsEnabled = true; //}); if (pedalMotor == 0) // Manual Pedal { // Reset auto mode flag and stop timers isPedalAutoMode = false; // Stop pedal timers if (pedalOnTimer != null) { pedalOnTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOnTimer.Dispose(); pedalOnTimer = null; } if (pedalOffTimer != null) { pedalOffTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOffTimer.Dispose(); pedalOffTimer = null; } Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { settings.pedalDelayTxt.IsVisible = false; settings.pedalDelayCounter.IsVisible = false; } }); var pedal = _mapping.Find(x => x.Name.ToLower() == "pedal"); if (pedal != null) { // READING THE INPUT REGISTER if (errors.Count == 0) { ushort registerValue = (ushort)inputValues[1]; if (allPedalBitsOn != pedal.BitNumbers.All(bit => (registerValue & (1 << bit)) != 0)) { allPedalBitsOn = pedal.BitNumbers.All(bit => (registerValue & (1 << bit)) != 0); pedalStateChanged = 1; } else { pedalStateChanged = 0; } if (!allPedalBitsOn) { pedalState = 1;// All bits ON } else { pedalState = 0; // At least one bit is OFF } // READING THE motor REGISTER var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (fount != null) { if (fount.BitNumbers.Count > 0) { bool valueChanged = false; if (pedalState == 1) // If all monitored bits are ON { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { result.pedalUnderLine.Fill = Brush.Parse(result.PassiveColor); } }); if (!(comPumpTemp <= recipeCoolingGoal - 3)) { foreach (var bit in fount.BitNumbers) { if (!ToBinary(holdingRegister.motor)[bit]) { //Debug.WriteLine("input value:" + registerValue); //Debug.WriteLine("pedal on:" + allBitsOn); holdingRegister.motor |= (ushort)(1 << bit); valueChanged = true; } } if (valueChanged) { await WriteToSerialAsync("PedalManual"); valueChanged = false; if (isPaused) { unPause = true; } } } } else // If at least one monitored bit is OFF { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { result.pedalUnderLine.Fill = Brush.Parse(result.ActiveColor); } }); if (pedalStateChanged == 1) { foreach (var bit in fount.BitNumbers) { if (ToBinary(holdingRegister.motor)[bit]) { holdingRegister.motor &= (ushort)~(1 << bit); valueChanged = true; } } if (valueChanged) { await WriteToSerialAsync("PedalManual"); valueChanged = false; } } if (fountainPauseTimer == null && startRecipe == 1 && (Heating == 1 || heatingTimer != null || cooling == 1 || coolingTimer != null || pouring == 1 || pouringTimer != null )) { fountainPauseSeconds = 10; fountainPauseTimer = new Timer(FountainPauseTimer, null, 0, 1000); } } if (pedalStateChanged == 1) { // WRITE UPDATED VALUE TO HVO REGISTER //isWriting = true; } } } } } } else if (pedalMotor == 1) // auto Pedal { // Set auto mode flag isPedalAutoMode = true; // Direct fountain control based on pedal state var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (fount != null && fount.BitNumbers.Count > 0) { if (pedalState == 0) // Pedal ON - Turn fountain ON { foreach (var bit in fount.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); // Set the motor bit ON } isFountainMotorOn = true; startFountainMotor = 1; sendComFountainMotor = 1; startFountainMotorFlashing = -1; // No flashing await WriteToSerialAsync("Pedal Auto - Fountain ON"); // Update UI to show fountain is ON Dispatcher.UIThread.Post(() => { if (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; fountainLable.Content = "ON"; fountainLable.Foreground = Brush.Parse("#ff231f20"); fountainRectangel.Fill = Brush.Parse(ActiveColor); } }); } else if (pedalState == 1) // Pedal OFF - Turn fountain OFF { foreach (var bit in fount.BitNumbers) { holdingRegister.motor &= (ushort)~(1 << bit); // Clear the motor bit OFF } isFountainMotorOn = false; startFountainMotor = 0; sendComFountainMotor = 0; startFountainMotorFlashing = -1; // No flashing await WriteToSerialAsync("Pedal Auto - Fountain OFF"); // Update UI to show fountain is OFF Dispatcher.UIThread.Post(() => { if (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; fountainLable.Content = "OFF"; fountainLable.Foreground = Brush.Parse("#ff231f20"); fountainRectangel.Fill = Brush.Parse(PassiveColor); } }); } } // Reset pedal state and handle timing pedalState = -1; // Handle pedal timing (alternating ON/OFF based on recipe settings) if (setPedalTimerOnce == 1) // Start OFF timer { if (pedalOffTimer == null) { pedalOffTimer = new Timer(PedalOffTimer, null, 1000, 1000); } else { pedalOffTimer.Change(1000, 1000); } setPedalTimerOnce = -1; } else if (setPedalTimerOnce == 0) // Start ON timer { if (pedalOnTimer == null) { pedalOnTimer = new Timer(PedalOnTimer, null, 1000, 1000); } else { pedalOnTimer.Change(1000, 1000); } setPedalTimerOnce = -1; } } //change diag UI var hvo = ToBinary(holdingRegister.hvOut); var lvo = ToBinary(holdingRegister.lvOut); var motor = ToBinary(holdingRegister.motor); Dispatcher.UIThread.Post(async () => { if (ContentArea.Content is Diagnostics diagnostics) { foreach (var item in diagnostics.MotoreState.OfType().ToList()) { var stackPanel = item.Children[0] as StackPanel; var text = stackPanel.Children[1] as TextBlock; var border = item.Children[1] as Border; if (motor[int.Parse(item.Tag.ToString())]) { text.Text = "ON"; border.Background = Brush.Parse(diagnostics.PinkColor); diagnostics.MotoreState.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.OrangeColor); } else { text.Text = "OFF"; border.Background = Brush.Parse(diagnostics.GrayColor); diagnostics.MotoreState.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.GrayColor); } } // HVO foreach (var item in diagnostics.hvoOutPuts.OfType().ToList()) { var text = item.Children[1] as TextBlock; var border = item.Children[2] as Border; if (hvo[int.Parse(item.Tag.ToString())]) { text.Text = "ON"; border.Background = Brush.Parse(diagnostics.PinkColor); diagnostics.hvoOutPuts.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.OrangeColor); } else { text.Text = "OFF"; border.Background = Brush.Parse(diagnostics.GrayColor); diagnostics.hvoOutPuts.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.GrayColor); } } // LVO foreach (var item in diagnostics.lvoOutPuts.OfType().ToList()) { var text = item.Children[1] as TextBlock; var border = item.Children[2] as Border; if (lvo[int.Parse(item.Tag.ToString())]) { text.Text = "ON"; border.Background = Brush.Parse(diagnostics.PinkColor); diagnostics.lvoOutPuts.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.GreenColor); } else { text.Text = "OFF"; border.Background = Brush.Parse(diagnostics.GrayColor); diagnostics.lvoOutPuts.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.GrayColor); } } foreach (var item in diagnostics.lvoOutPuts.OfType().ToList()) { var text = item.Children[1] as TextBlock; var border = item.Children[2] as Border; if (lvo[int.Parse(item.Tag.ToString())]) { text.Text = "ON"; border.Background = Brush.Parse(diagnostics.PinkColor); diagnostics.lvoOutPuts.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.GreenColor); } else { text.Text = "OFF"; border.Background = Brush.Parse(diagnostics.GrayColor); diagnostics.lvoOutPuts.OfType().ToList().Find(x => x.Tag.ToString() == item.Tag.ToString()).Fill = Brush.Parse(diagnostics.GrayColor); } } } }); await Task.Delay(100); } } catch (Exception e) { } } } } catch { } finally { } } } } public bool ConnectToSerialPort() { screenData = _screeen.ReadScreens()?[0]; try { if (_port != null && _port.IsOpen) { _port.Close(); } _port = null; _port = new SerialPort(screenData.port, screenData.boundRate); switch (screenData.parity) { case 0: _port.Parity = Parity.None; break; case 1: _port.Parity = Parity.Odd; break; case 2: _port.Parity = Parity.Even; break; case 3: _port.Parity = Parity.Mark; break; case 4: _port.Parity = Parity.Space; break; default: _port.Parity = Parity.None; break; } switch (screenData.stopBits) { case 2: _port.StopBits = StopBits.Two; break; default: _port.StopBits = StopBits.One; break; } _port.DataBits = 8; _port.Handshake = Handshake.None; _port.DtrEnable = true; // Open the serial port _port.Open(); return true; } catch (Exception ex) { Console.WriteLine($"Error connecting to port {screenData.port}: {ex.Message}"); _port = null; return false; } } public void closeConnection() { isRunning = false; monitorThread.Abort(); } private async void OnClosing1(object? sender, CancelEventArgs e) { closeConnection(); } private void OnClosingWindow(object? sender, WindowClosingEventArgs e) { if (_port != null && _port.IsOpen) { _port.Close(); //_port = null; } // Example: Cancel the close if needed // e.Cancel = true; } //Main Window Functions private void errorLogoClick(object? sender, RoutedEventArgs e) { errorPopupOverlay.IsVisible = true; //errorTitel.Text = errorLogo.Tag.ToString(); errorMsg.Text = errorMsg.Text.Trim(); } private void warningLogoClick(object? sender, RoutedEventArgs e) { warningPopupOverlay.IsVisible = true; warningTitel.Text = warningLogo.Tag.ToString(); warningMsg.Text = warningMsg.Text.Trim(); } private void HomeTraclBtn(object? sender, RoutedEventArgs e) { if (ContentArea.Content is Settings result) { result.DeletePopupOverlay.IsVisible = true; result.DeletePopupOverlay.Tag = "home"; } else if (ContentArea.Content is Diagnostics diagnostics) { restBoard = true; this.UserName.Content = "Select User"; footerMsg.Text = ""; ContentArea.Content = new Home(this); } else { this.UserName.Content = "Select User"; footerMsg.Text = ""; ContentArea.Content = new Home(this); } } private void DiagnosticsBtn(object? sender, RoutedEventArgs e) { if (ContentArea.Content is AdvanceSettings advanceSettings) { footerMsg.Text = ""; ContentArea.Content = new Diagnostics(this, true); } else if (ContentArea.Content is ManualControl) { footerMsg.Text = ""; ContentArea.Content = new Diagnostics(this, false, true); } else { footerMsg.Text = ""; ContentArea.Content = new Diagnostics(this); } } private void ChefManualBtn(object? sender, RoutedEventArgs e) { footerMsg.Text = ""; ContentArea.Content = new ManualControl(this); } public void AdvanceSettingsView(object? sender, RoutedEventArgs e) { if (ContentArea.Content is Diagnostics) { ContentArea.Content = new AdvanceSettings(this, true, false); } else if (ContentArea.Content is Software) { ContentArea.Content = new AdvanceSettings(this, false, true); } else { ContentArea.Content = new AdvanceSettings(this); } } private void RecipeSelTrackBtn(object? sender, RoutedEventArgs e) { if (ContentArea.Content is Settings result) { result.DeletePopupOverlay.IsVisible = true; result.DeletePopupOverlay.Tag = "recipeSel"; } else { footerMsg.Text = ""; ContentArea.Content = new Recipe(this, Program.currentUser); } } private void SettingTrackBtn(object? sender, RoutedEventArgs e) { if (ContentArea.Content is Diagnostics diagnostics) { restBoard = true; } footerMsg.Text = ""; ContentArea.Content = new Admin(this, Program.currentUser); } private void SoftwareBtn(object? sender, RoutedEventArgs e) { if (ContentArea.Content is AdvanceSettings) { footerMsg.Text = ""; ContentArea.Content = new Software(this, true); } else if (ContentArea.Content is ManualControl) { footerMsg.Text = ""; ContentArea.Content = new Software(this, false, true); } else { footerMsg.Text = ""; ContentArea.Content = new Software(this); } } private async void PreHeatingClick(object? sender, RoutedEventArgs e) { if (ContentArea.Content is Settings result) { if (result.recipeSettings.IsEnabled) { startPreHeating = 1; writingMaxTemp = 1; //if (!isMixerMotorOn) //{ // result.mixerBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); //} //if (!isFountainMotorOn) //{ // result.fountainBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); //} } else { startPreHeating = 0; writingMaxTemp = 0; isFlashPreHeating = false; } } } //Mixer public async void MotorClick(object? sender, RoutedEventArgs e) { if (!isMixerMotorOn) { isMixerMotorOn = true; checkMixerTWT_HWTH = true; setMixerTimerOnce = true; } else { isMixerMotorOn = false; checkMixerTWT_HWTH = false; startMixerMotorFlashing = 0; sendComMixerMotor = 0; Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { settings.mixerDelayTxt.IsVisible = false; settings.mixerDelayCounter.IsVisible = false; } }); if (mixerTimer != null) { mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); mixerTimer = null; } } } private void MixerTimer(object state) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { if (comTankTemp >= recipeCoolingGoal - 3) { mixerSeconds--; } else { //mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); } if (mixerSeconds <= 0) { // Stop the timer after 15 seconds settings.mixerDelayTxt.IsVisible = false; settings.mixerDelayCounter.IsVisible = false; if (mixerTimer != null) { mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); mixerTimer = null; } startMixerMotorFlashing = 0; sendComMixerMotor = 1; return; } if (mixerSeconds <= _machine.MixerDelay) { settings.mixerDelayTxt.IsVisible = true; settings.mixerDelayCounter.Text = mixerSeconds.ToString(); settings.mixerDelayCounter.IsVisible = true; } } }); } private void StopMixerTimer(object state) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { if (comTankTemp <= recipeCoolingGoal - 3) { stopMixerSecondes--; } else { //mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); } if (stopMixerSecondes <= 0) { sendComMixerMotor = 0; stopMixerTimer.Change(Timeout.Infinite, Timeout.Infinite); stopMixerTimer = null; return; } } }); } //Fountain public async void FountainClick(object? sender, RoutedEventArgs e) { // Reset pedal auto mode when user manually controls fountain if (isPedalAutoMode) { isPedalAutoMode = false; // Stop pedal timers when user takes manual control if (pedalOnTimer != null) { pedalOnTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOnTimer.Dispose(); pedalOnTimer = null; } if (pedalOffTimer != null) { pedalOffTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOffTimer.Dispose(); pedalOffTimer = null; } pedalMotor = -1; // Reset to manual mode } if (!isFountainMotorOn) { isFountainMotorOn = true; checkFountainTMT_PMT = true; setFountainTimerOnce = true; } else { isFountainMotorOn = false; checkFountainTMT_PMT = false; startFountainMotorFlashing = 0; sendComFountainMotor = 0; Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { settings.fountainDelayTxt.IsVisible = false; settings.fountainDelayCounter.IsVisible = false; settings.fountainTargetTxt.IsVisible = false; settings.fountainTagetTemp.IsVisible = false; } }); if (fountainTimer != null) { fountainTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainTimer = null; } } } private void FountainTimer(object state) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { if (comPumpTemp >= recipeCoolingGoal - 3) { fountainSeconds--; } else { //fountainTimer.Change(Timeout.Infinite, Timeout.Infinite); } if (fountainSeconds <= 0) { // Stop the timer after 15 seconds settings.fountainDelayTxt.IsVisible = false; settings.fountainDelayCounter.IsVisible = false; settings.fountainTargetTxt.IsVisible = false; settings.fountainTagetTemp.IsVisible = false; if (fountainTimer != null) { fountainTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainTimer = null; } startFountainMotorFlashing = 0; sendComFountainMotor = 1; Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { if (startRecipe == 1 && !isPaused) { PumbOn = 1; } if (result._recipeTable.Pedal.Value) { pedalMotor = 0; } else { pedalMotor = 1; pedalState = 0; setPedalTimerOnce = 1; } } }); return; } if (fountainSeconds <= _machine.PumbDelay) { settings.fountainDelayTxt.Text = "Chocolate Delay: "; settings.fountainDelayTxt.IsVisible = true; settings.fountainDelayCounter.Text = fountainSeconds.ToString(); settings.fountainDelayCounter.IsVisible = true; settings.fountainTagetTemp.IsVisible = false; settings.fountainTargetTxt.IsVisible = false; } } }); } private void StopFountainTimer(object state) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { if (comPumpTemp <= recipeCoolingGoal - 3) { stopFountainSecondes--; } else { //mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); } if (stopFountainSecondes <= 0) { sendComFountainMotor = 0; stopFountainTimer.Change(Timeout.Infinite, Timeout.Infinite); stopFountainTimer = null; return; } } }); } private void FountainPauseTimer(object state) { Dispatcher.UIThread.Post(() => { var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (allBitsOn != fount.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0)) { allBitsOn = fount.BitNumbers.All(bit => (holdingRegister.motor & (1 << bit)) != 0); } if (fountainPauseSeconds <= 0) { pause = true; if (fountainPauseTimer != null) { fountainPauseTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainPauseTimer = null; } return; } if (!allBitsOn) { //motor is off //increase the counter fountainPauseSeconds--; //footerMsg.Text ="Fountain is off recipe will pause after: "+ fountainPauseSeconds.ToString(); } else { //motor is on //cancel the timer if (fountainPauseTimer != null) { fountainPauseTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainPauseTimer = null; } } //if the counter greater than 10 then pause the recipe }); } private void NoChoiceChoosenTimer(object state) { Dispatcher.UIThread.Post(() => { //increase the coiunter if (ContentArea.Content is Settings settings) { if (settings.tempErrorPopupOverlay.IsVisible) { noChoiceChoosenSeconds++; //footerMsg.Text = "sec" + noChoiceChoosenSeconds.ToString(); } else { //stop the timer if (noChoiceChoosenTimer != null) { noChoiceChoosenTimer.Change(Timeout.Infinite, Timeout.Infinite); noChoiceChoosenTimer = null; } } if (noChoiceChoosenSeconds >= 180)//change to 3 min { //if it reach the 3 min then stop the recipe startRecipe = 0; Heating = 0; cooling = 0; pouring = 0; PumbOn = -1; pedalMotor = -1; if (heatingTimer != null) { heatingTimer.Change(Timeout.Infinite, Timeout.Infinite); heatingSeconds = _machine.HeatingDelay; heatingTimer = null; } if (coolingTimer != null) { coolingTimer.Change(Timeout.Infinite, Timeout.Infinite); coolingSeconds = _machine.CoolingDelay; coolingTimer = null; } if (pouringTimer != null) { pouringTimer.Change(Timeout.Infinite, Timeout.Infinite); pouringSeconds = _machine.PouringDelay; pouringTimer = null; } if (pedalOffTimer != null) { pedalOffTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOffSeconds = 0; } if (pedalOnTimer != null) { pedalOnTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOnSeconds = 0; } if (fountainPauseTimer != null) { fountainPauseTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainPauseSeconds = 10; fountainPauseTimer = null; } if (fountainTimer != null) { fountainTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainTimer = null; } if (mixerTimer != null) { mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); mixerTimer = null; } var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (fount != null) { if (fount.BitNumbers.Count > 0) { foreach (var bit in fount.BitNumbers) { holdingRegister.motor &= (ushort)~(1 << bit); } } } startPreHeating = 0; writingMaxTemp = 0; isFlashPreHeating = false; holdingRegister.setTemp1 = -10000; holdingRegister.setTemp2 = -10000; holdingRegister.setTemp3 = -10000; holdingRegister.setTemp4 = -10000; settings.tempErrorPopupOverlay.IsVisible = false; Dispatcher.UIThread.Post(async () => { await WriteToSerialAsync("NoChoiceChoosenTimer"); recipeStartBtn.Foreground = Avalonia.Media.Brushes.White; recipeStartBtn.Background = Brush.Parse("#008000"); footerMsg.Text = "waitting for too long... Recipe Stoped"; recipeStartBtn.Content = "START RECIPE"; PreHeatingBtn.IsEnabled = true; recipeStartBtn.IsEnabled = true; }); } } }); } /// /// Enhanced heating timer with improved goal checking and phase transition logic /// private void HeatingTimer(object state) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { if (fountainPauseTimer == null) { if (!pauseTimer) { // Continue heating delay countdown if (heatingSeconds > 0) { heatingSeconds--; } Heating = 1; warningMessage = ""; // Clear any previous warnings } // Clear warning message during heating phase warningMessage = ""; // Check if heating phase is complete if (heatingSeconds <= 0) { // Heating phase complete - stop heating timer first warningMessage = ""; heatingTimer?.Change(Timeout.Infinite, Timeout.Infinite); heatingTimer?.Dispose(); heatingTimer = null; pauseTimer = false; pauseTempTracking = false; Heating = -1; // Check if chocolate temperature equals cooling goal (within tolerance) // Using a small tolerance (0.5°C) for floating point comparison bool chocolateAtCoolingTemp = Math.Abs(comFountainTemp - recipeCoolingGoal) <= 0.5; if (chocolateAtCoolingTemp) { // Chocolate is already at cooling temperature - show cooling delay Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { // Hide cooling delay display settings.coolingDelayTxt.IsVisible = false; settings.coolingDelayCounter.IsVisible = false; settings.coolingDelayCounter.Text = _machine.CoolingDelay.ToString(); UpdateFooterMessage(RecipePhase.CoolingDelay, $"Cooling delay: {_machine.CoolingDelay} seconds"); } }); // Start cooling timer for delay countdown cooling = 1; sendComCooling = 1; setCoolingTimerOnce = 1; coolingSeconds = _machine.CoolingDelay; // Set flag to indicate we're in cooling delay mode isCoolingDelayMode = true; if (coolingTimer == null) { coolingTimer = new Timer(CoolingTimer, null, 1000, 1000); } else { // Ensure the timer is running at the correct interval coolingTimer.Change(1000, 1000); } } else { // Chocolate needs to cool down - start cooling phase UpdateFooterMessage(RecipePhase.CoolingPhase, "Cooling phase"); cooling = 1; sendComCooling = 1; setCoolingTimerOnce = 1; coolingSeconds = 0; // No countdown during cooling phase // Set flag to indicate we're in cooling phase mode (not delay mode) isCoolingDelayMode = false; if (coolingTimer == null) { coolingTimer = new Timer(CoolingTimer, null, 1000, 1000); } else { // Ensure the timer is running at the correct interval coolingTimer.Change(1000, 1000); } } } else if (heatingSeconds <= _machine.HeatingDelay && !pauseTimer) { // Show heating delay countdown UpdateFooterMessage(RecipePhase.HeatingDelay, $"Heating delay: {heatingSeconds} seconds"); } else if (heatingSeconds > _machine.HeatingDelay) { // Show heating phase status with current temperature footerMsg.Text = $"Heating phase"; } } } }); } /// /// Enhanced cooling timer with improved goal checking and conditional phase transitions /// Timer runs every 1000ms (1 second) to count down seconds properly /// private void CoolingTimer(object state) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { if (fountainPauseTimer == null) { // Check if we're in cooling phase and chocolate has reached cooling goal // Using same tolerance as heating phase for consistency if (!isCoolingDelayMode && Math.Abs(comFountainTemp - recipeCoolingGoal) <= 0.5) { // Chocolate reached cooling goal during cooling phase - switch to cooling delay isCoolingDelayMode = true; coolingSeconds = _machine.CoolingDelay; // Reset timer for cooling delay // Hide cooling delay display settings.coolingDelayTxt.IsVisible = false; settings.coolingDelayCounter.IsVisible = false; settings.coolingDelayCounter.Text = coolingSeconds.ToString(); UpdateFooterMessage(RecipePhase.CoolingDelay, $"Cooling delay: {coolingSeconds} seconds"); } if (!pauseTimer && isCoolingDelayMode && coolingSeconds > 0) { // Only count down during cooling DELAY, not during cooling PHASE coolingSeconds--; warningMessage = ""; // Clear any previous warnings // Update the display to show the current countdown settings.coolingDelayCounter.Text = coolingSeconds.ToString(); } else if (!pauseTimer && !isCoolingDelayMode) { // During cooling PHASE, just monitor temperature, don't count down warningMessage = ""; // Clear any previous warnings } // Check for warning conditions - only warn if temperature is too high bool tempTooHighForWarning = comFountainTemp > (recipeCoolingGoal + screenData.warningLimit); if (tempTooHighForWarning) { // Show warning when temperature is approaching the upper limit if (warningMessage != "Cooling temperature approaching maximum") { warningMessage = "Cooling temperature approaching maximum"; } } else { warningMessage = ""; } // Check if cooling phase is complete (only when in delay mode) if (isCoolingDelayMode && coolingSeconds <= 0) { // Cooling phase complete - transition to pouring phase warningMessage = ""; // Safely stop the cooling timer if (coolingTimer != null) { coolingTimer.Change(Timeout.Infinite, Timeout.Infinite); coolingTimer.Dispose(); coolingTimer = null; } pauseTimer = false; pauseTempTracking = false; cooling = -1; // Check if chocolate temperature equals pouring goal bool chocolateAtPouringTemp = Math.Abs(comFountainTemp - recipePouringGoal) <= 0.5; if (chocolateAtPouringTemp) { // Chocolate is already at pouring temperature - show pouring delay pouring = 1; sendComPouring = 1; setPouringTimerOnce = 1; pouringSeconds = _machine.PouringDelay; isPouringDelayMode = true; // Start pouring timer if (pouringTimer == null) { pouringTimer = new Timer(PouringTimer, null, 1000, 1000); } else { // Ensure the timer is running at the correct interval pouringTimer.Change(1000, 1000); } UpdateFooterMessage(RecipePhase.PouringPhase, $"Pouring delay: {pouringSeconds} seconds"); } else { // Chocolate needs to reach pouring temperature - start pouring phase pouring = 1; sendComPouring = 1; setPouringTimerOnce = 1; pouringSeconds = 0; // No countdown during pouring phase isPouringDelayMode = false; // Start pouring timer if (pouringTimer == null) { pouringTimer = new Timer(PouringTimer, null, 1000, 1000); } else { // Ensure the timer is running at the correct interval pouringTimer.Change(1000, 1000); } UpdateFooterMessage(RecipePhase.PouringPhase, "Pouring phase"); } coolingTimer = null; // Hide cooling delay labels when cooling phase completes settings.coolingDelayTxt.IsVisible = false; settings.coolingDelayCounter.IsVisible = false; isCoolingDelayMode = false; // Reset the flag } else if (isCoolingDelayMode && !pauseTimer) { // Show cooling delay countdown when chocolate is already at cooling temperature // Hide cooling delay display settings.coolingDelayTxt.IsVisible = false; settings.coolingDelayCounter.IsVisible = false; settings.coolingDelayCounter.Text = coolingSeconds.ToString(); UpdateFooterMessage(RecipePhase.CoolingDelay, $"Cooling delay: {coolingSeconds} seconds"); } else if (!isCoolingDelayMode && !pauseTimer) { // Show cooling phase status when chocolate needs to cool down settings.coolingDelayTxt.IsVisible = false; settings.coolingDelayCounter.IsVisible = false; UpdateFooterMessage(RecipePhase.CoolingPhase, "Cooling phase"); } } } }); } /// /// Enhanced pouring timer with improved goal checking and recipe completion logic /// Timer runs every 1000ms (1 second) to count down seconds properly /// private void PouringTimer(object state) { Dispatcher.UIThread.Post(() => { try { if (ContentArea.Content is Settings settings) { if (fountainPauseTimer == null) { // For pouring phase: Check if temperature is within acceptable range around the goal // Both too high and too low are problematic for pouring bool tempInRange = (comFountainTemp >= (recipePouringGoal - screenData.errorLimit)) && (comFountainTemp <= (recipePouringGoal + screenData.errorLimit)); // Check if we're in pouring phase and chocolate has reached pouring goal if (!isPouringDelayMode && Math.Abs(comFountainTemp - recipePouringGoal) <= 0.5) { // Chocolate reached pouring goal during pouring phase - switch to pouring delay isPouringDelayMode = true; pouringSeconds = _machine.PouringDelay; // Reset timer for pouring delay UpdateFooterMessage(RecipePhase.PouringPhase, $"Pouring delay: {pouringSeconds} seconds"); } if (!pauseTimer && isPouringDelayMode && pouringSeconds > 0) { // Only count down during pouring DELAY pouringSeconds--; warningMessage = ""; // Clear any previous warnings // Update the display to show the current countdown UpdateFooterMessage(RecipePhase.PouringPhase, $"Pouring delay: {pouringSeconds} seconds"); } else if (!pauseTimer && !isPouringDelayMode) { // During pouring PHASE, just monitor temperature warningMessage = ""; // Clear any previous warnings } // Check for warning conditions - warn if approaching limits bool tempInWarningRange = (comFountainTemp >= (recipePouringGoal - screenData.warningLimit)) && (comFountainTemp <= (recipePouringGoal + screenData.warningLimit)); if (!tempInWarningRange) { // Show warning when temperature is approaching limits if (warningMessage != "Pouring temperature approaching limits") { warningMessage = "Pouring temperature approaching limits"; } } else { warningMessage = ""; } // Check if pouring phase is complete (only when in delay mode) if (isPouringDelayMode && pouringSeconds <= 0) { // Pouring phase complete - recipe finished warningMessage = ""; // Safely stop the pouring timer if (pouringTimer != null) { pouringTimer.Change(Timeout.Infinite, Timeout.Infinite); pouringTimer.Dispose(); pouringTimer = null; } pauseTimer = false; pauseTempTracking = false; pouring = -1; isPouringDelayMode = false; // Reset the flag // Recipe completed successfully Dispatcher.UIThread.Post(async () => { if (ContentArea.Content is Settings result) { // Handle pedal control based on recipe settings if (result._recipeTable.Pedal.Value) { pedalMotor = 0; // Manual mode } else { pedalMotor = 1; // Auto mode pedalState = 0; setPedalTimerOnce = 1; } // Fountain control is now handled directly by pedal state in auto mode UpdateFooterMessage(RecipePhase.Completed, "Ready for pouring"); startRecipe = 0; // Mark recipe as completed } }); } else if (isPouringDelayMode && !pauseTimer) { // Show pouring delay countdown UpdateFooterMessage(RecipePhase.PouringPhase, $"Pouring delay: {pouringSeconds} seconds"); } else if (!isPouringDelayMode && !pauseTimer) { // Show pouring phase status UpdateFooterMessage(RecipePhase.PouringPhase, "Pouring phase"); } } } } catch (Exception ex) { footerMsg.Text = $"Error: {ex.Message}"; } }); } /// /// Pedal OFF timer - counts down the OFF time and then switches to ON mode /// Timer runs every 1000ms (1 second) to count down seconds properly /// private void PedalOffTimer(object state) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { pedalOffSeconds++; // Show countdown during OFF time if (pedalOffSeconds <= result._recipeTable.PedalOffTime) { result.pedalDelayTxt.Text = "Pedal OFF: "; result.pedalDelayCounter.Text = (result._recipeTable.PedalOffTime - pedalOffSeconds + 1).ToString(); result.pedalDelayTxt.IsVisible = true; result.pedalDelayCounter.IsVisible = true; } // When OFF time is complete, switch to ON mode if (pedalOffSeconds >= result._recipeTable.PedalOffTime) { pedalOffSeconds = 0; PumbOn = 1; pedalMotor = 1; pedalState = 1; // Set to ON state // Stop the OFF timer if (pedalOffTimer != null) { pedalOffTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOffTimer.Dispose(); pedalOffTimer = null; } // If still in auto mode, continue the cycle by starting the ON timer if (isPedalAutoMode) { setPedalTimerOnce = 0; } } } }); } /// /// Pedal ON timer - counts down the ON time and then switches to OFF mode /// Timer runs every 1000ms (1 second) to count down seconds properly /// private void PedalOnTimer(object state) { Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { pedalOnSeconds++; // Show countdown during ON time if (pedalOnSeconds <= result._recipeTable.PedalOnTime) { result.pedalDelayTxt.Text = "Pedal ON: "; result.pedalDelayCounter.Text = (result._recipeTable.PedalOnTime - pedalOnSeconds + 1).ToString(); result.pedalDelayTxt.IsVisible = true; result.pedalDelayCounter.IsVisible = true; } // When ON time is complete, switch to OFF mode if (pedalOnSeconds >= result._recipeTable.PedalOnTime) { pedalOnSeconds = 0; PumbOn = 1; pedalMotor = 1; pedalState = 0; // Set to OFF state // Stop the ON timer if (pedalOnTimer != null) { pedalOnTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOnTimer.Dispose(); pedalOnTimer = null; } // If still in auto mode, continue the cycle by starting the OFF timer if (isPedalAutoMode) { setPedalTimerOnce = 1; } } } }); } //Recipe Start public async void RecipeStartBtn(object? sender, RoutedEventArgs e) { if (sender is Button button) { if (startRecipe == 0) { // Initialize and validate recipe parameters if (!await InitializeAndValidateRecipe()) { return; // Exit if validation fails } startRecipe = 1; stopRecipeFlag = false; tempWarningAccepted = false; // Reset temperature warning acceptance for new recipe currentRecipePhase = RecipePhase.PreHeating; // Start with pre-heating phase isPedalAutoMode = false; // Reset pedal auto mode for new recipe if (ContentArea.Content is Settings result) { // Retrieve recipe goals from settings recipeHeatingGoal = result._recipeTable.HeatingGoal; recipeCoolingGoal = result._recipeTable.CoolingGoal; recipePouringGoal = result._recipeTable.PouringGoal; // Check if pre-heating is required if (comPumpTemp * 10 < _machine.PumbMaxHeat * 10 || comTankTemp * 10 < _machine.TankMaxHeat * 10) { startPreHeating = 1; writingMaxTemp = 1; footerMsg.Text = "Pre-heating in progress..."; } // Ensure motors are running if needed if (!isMixerMotorOn) { result.mixerBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); } if (!isFountainMotorOn) { result.fountainBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); } // Start goal monitoring and phase transitions await StartRecipePhaseMonitoring(); // Update UI to indicate recipe is running Dispatcher.UIThread.Post(() => { result.mixerBtn.IsEnabled = false; result.fountainBtn.IsEnabled = false; PreHeatingBtn.IsEnabled = false; recipeStartBtn.IsEnabled = true; button.Background = Avalonia.Media.Brushes.Red; button.Content = "STOP RECIPE"; }); } } else if (startRecipe == 1) { // Stop recipe execution await StopRecipeExecution(button); } } } /// /// Initialize and validate recipe parameters before starting /// private async Task InitializeAndValidateRecipe() { try { if (ContentArea.Content is Settings result) { // Validate recipe data exists if (result._recipeTable == null) { footerMsg.Text = "Error: No recipe selected"; return false; } // Validate temperature goals are reasonable if (result._recipeTable.HeatingGoal <= 0 || result._recipeTable.CoolingGoal <= 0 || result._recipeTable.PouringGoal <= 0) { footerMsg.Text = "Error: Invalid temperature goals in recipe"; return false; } // Validate cooling goal is less than heating goal if (result._recipeTable.CoolingGoal >= result._recipeTable.HeatingGoal) { footerMsg.Text = "Error: Cooling goal must be less than heating goal"; return false; } footerMsg.Text = "Ready"; return true; } footerMsg.Text = "Error: Settings not available"; return false; } catch (Exception ex) { footerMsg.Text = $"Error initializing recipe: {ex.Message}"; return false; } } /// /// Start monitoring recipe phases and handle goal checking /// private async Task StartRecipePhaseMonitoring() { try { // Start continuous monitoring of temperature goals await Task.Run(async () => { while (startRecipe == 1 && !stopRecipeFlag) { await CheckAndHandlePhaseTransitions(); await Task.Delay(1000); // Check every second } }); } catch (Exception ex) { // Log error silently - don't show in UI since recipe is working // Console.WriteLine($"Phase monitoring error: {ex.Message}"); } } /// /// Check current temperatures and handle phase transitions based on goals /// private async Task CheckAndHandlePhaseTransitions() { try { if (ContentArea.Content is Settings settings) { // Check if heating goals are met for both mixer and chocolate bool mixerHeatingGoalMet = await CheckMixerHeatingGoal(); bool chocolateHeatingGoalMet = await CheckChocolateHeatingGoal(); // If both heating goals are met, start timers if (mixerHeatingGoalMet && chocolateHeatingGoalMet) { await StartHeatingPhaseTimers(); } // Check chocolate temperature for cooling phase transition await HandleCoolingPhaseTransition(settings); } } catch (Exception ex) { footerMsg.Text = $"Error in phase transition: {ex.Message}"; } } /// /// Check if mixer heating goal is reached /// private async Task CheckMixerHeatingGoal() { // Check if tank temperature (mixer) has reached heating goal // For heating: temperature should be at or above the goal bool goalMet = comTankTemp >= recipeHeatingGoal; if (goalMet && Heating == -1) { Dispatcher.UIThread.Post(() => { footerMsg.Text = $"Mixer heating goal reached: {comTankTemp:F1}°C (Target: {recipeHeatingGoal}°C)"; }); } return goalMet; } /// /// Check if chocolate heating goal is reached /// private async Task CheckChocolateHeatingGoal() { // Check if fountain temperature (chocolate) has reached heating goal // For heating: temperature should be at or above the goal bool goalMet = comFountainTemp >= recipeHeatingGoal; if (goalMet && Heating == -1) { } return goalMet; } /// /// Start heating phase timers when goals are met /// private async Task StartHeatingPhaseTimers() { if (Heating == -1 && setHeatingTimerOnce == -1) { Heating = 1; sendComHeating = 1; setHeatingTimerOnce = 1; heatingSeconds = _machine.HeatingDelay; // Start heating timer if (heatingTimer == null) { heatingTimer = new Timer(HeatingTimer, null, 1000, 1000); } Dispatcher.UIThread.Post(() => { footerMsg.Text = "Heating phase started - timers initiated"; }); } } /// /// Handle cooling phase transition based on chocolate temperature /// private async Task HandleCoolingPhaseTransition(Settings settings) { // Check if chocolate temperature has reached cooling threshold // For cooling: temperature should be at or below the cooling goal bool chocolateAtCoolingTemp = comFountainTemp <= recipeCoolingGoal; if (chocolateAtCoolingTemp && cooling == -1 && Heating == -1) { // Chocolate temperature equals or is below cooling threshold - show cooling delay await ShowCoolingDelay(settings); } else if (!chocolateAtCoolingTemp && cooling == -1 && Heating == -1) { // Chocolate temperature not at cooling threshold - initiate cooling phase await InitiateCoolingPhase(settings); } } /// /// Show cooling delay when chocolate temperature equals cooling goal /// private async Task ShowCoolingDelay(Settings settings) { Dispatcher.UIThread.Post(() => { settings.coolingDelayTxt.IsVisible = true; settings.coolingDelayCounter.IsVisible = true; settings.coolingDelayCounter.Text = "0"; footerMsg.Text = $"Cooling delay: Chocolate at target temperature ({comFountainTemp:F1}°C)"; }); } /// /// Initiate cooling phase when chocolate temperature is not at cooling goal /// private async Task InitiateCoolingPhase(Settings settings) { if (setCoolingTimerOnce == -1) { cooling = 1; sendComCooling = 1; setCoolingTimerOnce = 1; coolingSeconds = _machine.CoolingDelay; // Start cooling timer if (coolingTimer == null) { coolingTimer = new Timer(CoolingTimer, null, 1000, 1000); } else { // Ensure the timer is running at the correct interval coolingTimer.Change(1000, 1000); } Dispatcher.UIThread.Post(() => { settings.coolingDelayTxt.IsVisible = false; settings.coolingDelayCounter.IsVisible = false; footerMsg.Text = $"Cooling phase initiated - Target: {recipeCoolingGoal}°C, Current: {comFountainTemp:F1}°C"; }); } } /// /// Stop recipe execution and clean up resources /// private async Task StopRecipeExecution(Button button) { startRecipe = 0; stopRecipeFlag = true; Heating = -1; cooling = -1; pouring = -1; PumbOn = -1; pedalMotor = -1; isCoolingDelayMode = false; // Reset the cooling delay mode flag isPouringDelayMode = false; // Reset the pouring delay mode flag tempWarningAccepted = false; // Reset temperature warning acceptance currentRecipePhase = RecipePhase.None; // Reset recipe phase lastFooterMessage = ""; // Reset last footer message isPedalAutoMode = false; // Reset pedal auto mode // Stop all timers await StopAllRecipeTimers(); // Reset UI elements await ResetRecipeUI(button); // Reset temperature settings holdingRegister.setTemp1 = -10000; holdingRegister.setTemp2 = -10000; holdingRegister.setTemp3 = -10000; holdingRegister.setTemp4 = -10000; holdingRegister.motor = 0; await WriteToSerialAsync("RecipeStop"); footerMsg.Text = "Recipe Stopped"; } /// /// Stop all recipe-related timers /// private async Task StopAllRecipeTimers() { // Stop heating timer if (heatingTimer != null) { heatingTimer.Change(Timeout.Infinite, Timeout.Infinite); heatingSeconds = _machine.HeatingDelay; heatingTimer = null; } // Stop cooling timer if (coolingTimer != null) { coolingTimer.Change(Timeout.Infinite, Timeout.Infinite); coolingSeconds = _machine.CoolingDelay; coolingTimer = null; } // Stop pouring timer if (pouringTimer != null) { pouringTimer.Change(Timeout.Infinite, Timeout.Infinite); pouringSeconds = _machine.PouringDelay; pouringTimer = null; } // Stop pedal timers if (pedalOffTimer != null) { pedalOffTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOffSeconds = 0; } if (pedalOnTimer != null) { pedalOnTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOffSeconds = 0; } // Stop fountain timers if (fountainPauseTimer != null) { fountainPauseTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainPauseSeconds = 10; fountainPauseTimer = null; } if (fountainTimer != null) { fountainTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainTimer = null; } // Stop mixer timer if (mixerTimer != null) { mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); mixerTimer = null; } } /// /// Reset UI elements when recipe is stopped /// private async Task ResetRecipeUI(Button button) { startPreHeating = 0; writingMaxTemp = 0; isFlashPreHeating = false; Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings settings) { // Turn off motors if they were on if (isMixerMotorOn) { settings.mixerBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); } if (isFountainMotorOn) { settings.fountainBtn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); } // Hide delay indicators settings.mixerDelayTxt.IsVisible = false; settings.mixerDelayCounter.IsVisible = false; settings.fountainDelayTxt.IsVisible = false; settings.fountainDelayCounter.IsVisible = false; settings.fountainTargetTxt.IsVisible = false; settings.fountainTagetTemp.IsVisible = false; settings.coolingDelayTxt.IsVisible = false; settings.coolingDelayCounter.IsVisible = false; settings.pedalDelayTxt.IsVisible = false; settings.pedalDelayCounter.IsVisible = false; // Re-enable buttons settings.mixerBtn.IsEnabled = true; settings.fountainBtn.IsEnabled = true; } // Reset recipe start button button.Content = "START RECIPE"; PreHeatingBtn.IsEnabled = true; recipeStartBtn.IsEnabled = true; recipeStartBtn.Foreground = Avalonia.Media.Brushes.White; recipeStartBtn.Background = Brush.Parse("#008000"); }); } public void resetAll() { if (heatingTimer != null) { heatingTimer.Change(Timeout.Infinite, Timeout.Infinite); heatingSeconds = _machine.HeatingDelay; heatingTimer = null; } if (coolingTimer != null) { coolingTimer.Change(Timeout.Infinite, Timeout.Infinite); coolingSeconds = _machine.CoolingDelay; coolingTimer = null; } if (pouringTimer != null) { pouringTimer.Change(Timeout.Infinite, Timeout.Infinite); pouringSeconds = _machine.PouringDelay; pouringTimer = null; } if (pedalOffTimer != null) { pedalOffTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOffSeconds = 0; } if (pedalOnTimer != null) { pedalOnTimer.Change(Timeout.Infinite, Timeout.Infinite); pedalOnSeconds = 0; } if (fountainPauseTimer != null) { fountainPauseTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainPauseSeconds = 10; fountainPauseTimer = null; } if (fountainTimer != null) { fountainTimer.Change(Timeout.Infinite, Timeout.Infinite); fountainTimer = null; } if (mixerTimer != null) { mixerTimer.Change(Timeout.Infinite, Timeout.Infinite); mixerTimer = null; } pedalState = -1; pedalStateChanged = -1; recipeHeatingGoal = 0; recipeCoolingGoal = 0; recipePouringGoal = 0; //pre Heating isFlashPreHeating = false; startPreHeating = -1; writingMaxTemp = -1; // mixer mixerSeconds = 1; setMixerTimerOnce = false; checkMixerTWT_HWTH = false; isMixerMotorOn = false; startMixerMotor = -1; startMixerMotorFlashing = -1; sendComMixerMotor = -1; //Fountain Motor fountainSeconds = 1; setFountainTimerOnce = false; checkFountainTMT_PMT = false; isFountainMotorOn = false; startFountainMotor = -1; startFountainMotorFlashing = -1; sendComFountainMotor = -1; //MOLD HEATER(off:0,on:1) , VIBRATION(off:0,on:1) , VIB. HEATER(off:0,on:1) moldHeaterMotor = -1; vibrationMotor = -1; vibHeaterMotor = -1; //Pedal(manual=0,auto=1) pedalMotor = -1; //Recipe Start startRecipe = 0; sendComTankTemp = -1; //phase 1 heating Heating = -1; sendComHeating = -1; setHeatingTimerOnce = -1; heatingSeconds = 0; //phase 2 cooling cooling = -1; sendComCooling = -1; setCoolingTimerOnce = -1; coolingSeconds = 0; //phase 3 pouring pouring = -1; sendComPouring = -1; setPouringTimerOnce = -1; pouringSeconds = 0; //start the pumb PumbOn = -1; pedalOffSeconds = 0; pedalOnSeconds = 0; setPedalTimerOnce = -1; Dispatcher.UIThread.Post(() => { recipeStartBtn.Foreground = Avalonia.Media.Brushes.White; recipeStartBtn.Background = Brush.Parse("#008000"); recipeStartBtn.Content = "START RECIPE"; PreHeatingBtn.IsEnabled = true; recipeStartBtn.IsEnabled = true; }); } private async void ResetErrors(object? sender, RoutedEventArgs e) { holdingRegister.resetError = (ushort)(1 << 0); await WriteToSerialAsync("ResetErrors"); } private async void OnWarningPopupOverlayPointerPressed(object? sender, RoutedEventArgs e) { warningPopupOverlay.IsVisible = false; } private async void OnErrorPopupOverlayPointerPressed(object? sender, RoutedEventArgs e) { errorPopupOverlay.IsVisible = false; } public static class MessageBox { public static async Task Show(Window owner, string message, string title) { var dialog = new Window { Title = title, Width = 300, Height = 150, WindowStartupLocation = WindowStartupLocation.CenterOwner, Topmost = false, Content = new StackPanel { Children = { new TextBlock { Text = message, Margin = new Thickness(10), HorizontalAlignment = HorizontalAlignment.Center }, new Button { Content = "OK", Margin = new Thickness(10), HorizontalAlignment = HorizontalAlignment.Center } } } }; var button = (Button)((StackPanel)dialog.Content).Children[1]; button.Click += (s, e) => dialog.Close(); owner.Topmost = false; await dialog.ShowDialog(owner); owner.Topmost = true; owner.Activate(); // Restart keyboard to bring it on top var (fileName, args) = GetKeyboardCommand(); if (!string.IsNullOrEmpty(fileName)) { try { Process.Start(fileName, args); } catch { // Handle exceptions if needed } } } private static (string? fileName, string args) GetKeyboardCommand() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return ("osk.exe", ""); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return ("onboard", ""); // or "florence", "matchbox-keyboard" return (null, ""); } } /// /// Handles automatic fountain control after pouring phase completion based on recipe settings /// private async Task HandleAutomaticFountainControlAfterPouring(Settings settings) { try { // Only proceed if pedal is in Auto mode if (!settings._recipeTable.Pedal.Value) // Pedal.Value = false means Auto mode { // Set flag to indicate automatic fountain control is active isAutomaticFountainControlActive = true; // Check the second control box (RecipeTable.Fountain) to determine fountain state if (settings._recipeTable.Fountain.Value) { // Second box is checked/enabled - Turn ON the fountain await TurnOnFountainAutomatically(); } else { // Second box is unchecked/disabled - Keep fountain OFF await TurnOffFountainAutomatically(); } Debug.WriteLine($"Automatic fountain control activated after pouring phase. Fountain state: {(settings._recipeTable.Fountain.Value ? "ON" : "OFF")}"); } } catch (Exception ex) { Debug.WriteLine($"Error in HandleAutomaticFountainControlAfterPouring: {ex.Message}"); } } /// /// Automatically turns on the fountain motor /// private async Task TurnOnFountainAutomatically() { try { // Set fountain motor state to ON isFountainMotorOn = true; startFountainMotor = 1; startFountainMotorFlashing = 0; sendComFountainMotor = 1; // Actually send the command to turn ON the fountain motor var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (fount != null && fount.BitNumbers.Count > 0) { foreach (var bit in fount.BitNumbers) { holdingRegister.motor |= (ushort)(1 << bit); } await WriteToSerialAsync("Automatic Fountain On"); Debug.WriteLine("Fountain motor command sent to hardware - ON"); } // Update UI to show fountain is ON Dispatcher.UIThread.Post(() => { if (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; fountainLable.Content = "ON"; fountainLable.Foreground = Brush.Parse("#ff231f20"); fountainRectangel.Fill = Brush.Parse(ActiveColor); } }); Debug.WriteLine("Fountain automatically turned ON after pouring phase completion"); } catch (Exception ex) { Debug.WriteLine($"Error turning on fountain automatically: {ex.Message}"); } } /// /// Automatically turns off the fountain motor /// private async Task TurnOffFountainAutomatically() { try { // Set fountain motor state to OFF isFountainMotorOn = false; startFountainMotor = 0; startFountainMotorFlashing = -1; // Prevent flashing in automatic mode sendComFountainMotor = 0; // Actually send the command to turn OFF the fountain motor var fount = _mapping.Find(x => x.Name.ToLower() == "Helix".ToLower()); if (fount != null && fount.BitNumbers.Count > 0) { foreach (var bit in fount.BitNumbers) { holdingRegister.motor &= (ushort)~(1 << bit); } await WriteToSerialAsync("Automatic Fountain Off"); Debug.WriteLine("Fountain motor command sent to hardware - OFF"); } // Update UI to show fountain is OFF Dispatcher.UIThread.Post(() => { if (ContentArea.Content is Settings result) { var fountainLable = result.FountainSP.Children[1] as Avalonia.Controls.Label; var fontainRectangel = result.FountainSP.Children[2] as Avalonia.Controls.Shapes.Rectangle; fountainLable.Content = "OFF"; fountainLable.Foreground = Brush.Parse("#ff231f20"); fontainRectangel.Fill = Brush.Parse(PassiveColor); } }); Debug.WriteLine("Fountain automatically turned OFF after pouring phase completion"); } catch (Exception ex) { Debug.WriteLine($"Error turning off fountain automatically: {ex.Message}"); } } /// /// Resets the automatic fountain control flag to allow normal fountain control /// public void ResetAutomaticFountainControl() { isAutomaticFountainControlActive = false; Debug.WriteLine("Automatic fountain control flag reset - normal fountain control restored"); } }