public static BackgroundWorker InitializeFanController2(ArduinoDriver.ArduinoDriver driver, Dispatcher MainDispatcher) { //Turns fan set 2 on, off, up and down //The idea here is to start the fan at half speed, see if and see it has any noticable effect on temperature -- if the temperature drops maintain the speed, if it rises increase the speed, if it drops lower the speed FanController2 = new BackgroundWorker(); FanController2.WorkerSupportsCancellation = true; FanController2.DoWork += new DoWorkEventHandler((state, args) => { int LastTemp = Main.CondensorTemp; byte FanSpeed = 125; do { if (Main.Run != true || FanController2.CancellationPending == true) { break; } try { System.Threading.Thread.Sleep(10000); if (Main.CondensorTemp > LastTemp) { FanSpeed = Convert.ToByte((int)FanSpeed + 5); MainDispatcher.Invoke(new Action(() => { driver.Send(new AnalogWriteRequest(SystemProperties.FanController1, FanSpeed)); })); } else if (Main.CondensorTemp < LastTemp) { FanSpeed = Convert.ToByte((int)FanSpeed - 5); MainDispatcher.Invoke(new Action(() => { driver.Send(new AnalogWriteRequest(SystemProperties.FanController1, FanSpeed)); })); } } catch { MainDispatcher.Invoke(new Action(() => { driver = Periphrials.InitializeArduinoDriver(); })); } } while (true); }); return(FanController2); }
public static BackgroundWorker InitializeFanController1(ArduinoDriver.ArduinoDriver driver, Dispatcher MainDispatcher) { //Turns fan set 1 on, off, up and down //The target temperature should be just under the distillation plateau temp so it doesnt slow down the distillation process but keeps the column cool enough that only the target substance can come over FanController1 = new BackgroundWorker(); FanController1.WorkerSupportsCancellation = true; FanController1.DoWork += new DoWorkEventHandler((state, args) => { int TargetTemp = Main.PlateauTemp - 1; byte FanSpeed = 50; do { if (Main.Run != true || FanController1.CancellationPending == true) { break; } try { System.Threading.Thread.Sleep(10000); if (Main.RefluxTemp > TargetTemp) { FanSpeed = Convert.ToByte((int)FanSpeed + 5); MainDispatcher.Invoke(new Action(() => { driver.Send(new AnalogWriteRequest(SystemProperties.FanController1, FanSpeed)); })); } else if (Main.RefluxTemp < TargetTemp) { FanSpeed = Convert.ToByte((int)FanSpeed - 5); MainDispatcher.Invoke(new Action(() => { driver.Send(new AnalogWriteRequest(SystemProperties.FanController1, FanSpeed)); })); } } catch { MainDispatcher.Invoke(new Action(() => { driver = Periphrials.InitializeArduinoDriver(); })); } } while (true); }); return(FanController1); }
public static BackgroundWorker InitializePressureWorker(ArduinoDriver.ArduinoDriver driver, Dispatcher MainDispatcher) { PressureWorker = new BackgroundWorker(); PressureWorker.WorkerSupportsCancellation = true; PressureWorker.DoWork += new DoWorkEventHandler((state, args) => { //Make sure the pump is off MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOff(driver, SystemProperties.VacuumPump); })); Main.VacuumPumpOn = false; do { try { if (Main.Run != true || PressureWorker.CancellationPending == true) { break; } System.Threading.Thread.Sleep(1000); if (Convert.ToDouble(Main.Pressure) > SystemProperties.TargetPressure && Main.VacuumPumpOn == false) { //Turn the vacuum pump on MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOn(driver, SystemProperties.VacuumPump); })); Main.VacuumPumpOn = true; //Refresh the pressure has changed every second -- Note that the pressure is set in the still monitor background worker do { System.Threading.Thread.Sleep(1000); }while (Convert.ToDouble(Main.Pressure) > (SystemProperties.TargetPressure - SystemProperties.TgtPresHysteresisBuffer) && PressureWorker.CancellationPending == false); //Once the pressure has reached its target turn the pump off MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOff(driver, SystemProperties.VacuumPump); })); Main.VacuumPumpOn = false; } } catch { MainDispatcher.Invoke(new Action(() => { driver = Periphrials.InitializeArduinoDriver(); })); } } while (true); }); return(PressureWorker); }
private static BackgroundWorker FanController2; //Turns the fan set for the condensor on, off, up and down depending on target temperature and distillation speed //Background worker to monitor all sensor valuess and switch states on the still and keep global variables and the UI updated //The idea here is to imtermittently check all variables and write to a local variable in memory to minimize commands sent to the arduino //This is also convienent as it minimizes the amount of long of code required to message the arduino in the control loop public static BackgroundWorker InitializeSystemMonitor(ArduinoDriver.ArduinoDriver driver, Dispatcher MainDispatcher) { SystemMonitor = new BackgroundWorker(); SystemMonitor.WorkerSupportsCancellation = true; SystemMonitor.DoWork += new DoWorkEventHandler((state, args) => { do { if (Main.Run != true || driver == null) //The periphrials class returns null if no arduino is found on any of the com ports { break; } System.Threading.Thread.Sleep(1000); //Check Temperature bool success = false; while (success == false) { try { MainDispatcher.Invoke(new Action(() => { Main.ColumnTemp = DriverFunctions.GetTemperature(driver, SystemProperties.SensorColumnTemp).ToString(); Main.RefluxTemp = DriverFunctions.GetTemperature(driver, SystemProperties.SensorCoolantTemp1); Main.CondensorTemp = DriverFunctions.GetTemperature(driver, SystemProperties.SensorCoolantTemp2); Main.ElementAmperage = DriverFunctions.GetAmperage(driver, SystemProperties.SensorColumnTemp); })); success = true; } catch { MainDispatcher.Invoke(new Action(() => { driver.Dispose(); driver = Periphrials.InitializeArduinoDriver(); })); } } //Check the low level switch -- a value of low means the lower still switch is open MainDispatcher.Invoke(new Action(() => { if (driver.Send(new DigitalReadRequest(SystemProperties.StillLowSwitch)).PinValue == DigitalValue.Low) { Main.StillEmpty = true; } else { Main.StillEmpty = false; } })); //Check the high level switch -- a value of high means the upper still switch is closed MainDispatcher.Invoke(new Action(() => { if (driver.Send(new DigitalReadRequest(SystemProperties.StillHighSwitch)).PinValue == DigitalValue.High) { Main.StillFull = true; } else { Main.StillFull = false; } })); //Check the recieving vessel low level switch -- a value of high means the upper rv switch is closed MainDispatcher.Invoke(new Action(() => { if (driver.Send(new DigitalReadRequest(SystemProperties.RVEmptySwitch)).PinValue == DigitalValue.High) { Main.RVEmpty = true; } else { Main.RVEmpty = false; } })); //Check the recieving vessel high level switch -- a value of high means the upper rv switch is closed MainDispatcher.Invoke(new Action(() => { if (driver.Send(new DigitalReadRequest(SystemProperties.RVFullSwitch)).PinValue == DigitalValue.Low) { Main.RVFull = true; } else { Main.RVFull = false; } })); //Check the pressure (1024 is the resolution of the ADC on the arduino, 45.1 is the approximate pressure range in PSI that the sensor is capable of reading, the -15 makes sure that STP = 0 PSI/kPa) MainDispatcher.Invoke(new Action(() => { Main.Pressure = Math.Round((((Convert.ToDouble(driver.Send(new AnalogReadRequest(SystemProperties.SensorPressure)).PinValue.ToString()) / 1024) * 45.1) - 16.5) * ((SystemProperties.Units == "Metric") ? 6.895 : 1), 2).ToString(); })); if (Main.Phase == -1) { Main.Phase = 0; } } while (true); }); return(SystemMonitor); }
public void StillLoop() { //Dispatcher to accept commands from the various background workers Dispatcher MainDispatcher = Dispatcher.CurrentDispatcher; //Instanciate the periphrial class and start up the arduino ArduinoDriver.ArduinoDriver driver = Periphrials.InitializeArduinoDriver(); if (driver == null) { lblStatus.Text = "No controller found"; btnRescan.Visible = true; } else { btnRescan.Visible = false; lblStatus.Text = "Starting"; } //Declare the background workers SystemMonitor = BackGroundWorkers.InitializeSystemMonitor(driver, MainDispatcher); PressureRegulator = BackGroundWorkers.InitializePressureWorker(driver, MainDispatcher); //StillController; FanController1 = BackGroundWorkers.InitializeFanController1(driver, MainDispatcher); FanController2 = BackGroundWorkers.InitializeFanController2(driver, MainDispatcher); //Datatable for statistics and calculating when to turn the element off DataTable StillStats = Statistics.InitializeTable(); chartRun.DataSource = StillStats; StillController = new BackgroundWorker(); StillController.WorkerSupportsCancellation = true; StillController.DoWork += new DoWorkEventHandler((state, args) => { do { try { DateTime RunStart = DateTime.Now; StillStats.Clear(); //Run unless a stop condition is hit if (Run != true || driver == null) { break; } while (Phase == -1)//Wait for initial values to be collected before starting { System.Threading.Thread.Sleep(250); } //Check to see if the still is full, if not fill it. This ensures there is no product wasted if the previous batch was stopped half way if (StillFull == false && Phase < 2) { { //Open the inlet valve and turn the inlet pump on MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOn(driver, SystemProperties.StillFillValve); })); StillValveOpen = true; //Wait 5 seconds for the valve to open System.Threading.Thread.Sleep(3000); MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOn(driver, SystemProperties.StillFluidPump); })); MainDispatcher.Invoke(new Action(() => { lblStatus.Text = "Filling Still"; })); StillPumpOn = true; //Check once a second to see if the still is full now -- note that StillFull is updated by the monitor worker while (StillFull == false) { System.Threading.Thread.Sleep(1000); } //Close the valve and turn off the pump MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOff(driver, SystemProperties.StillFillValve); })); StillValveOpen = false; MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOff(driver, SystemProperties.StillFluidPump); })); MainDispatcher.Invoke(new Action(() => { lblStatus.Text = "Filling Complete"; })); StillPumpOn = false; //If this line is reached that means the still has liquid in it and is ready to start distilling Phase = 1; } } //Make sure the first loop was passed and the still didnt magically empty itself if (Phase < 2 && StillFull == true) { //Turn on the element and vacuum pump DriverFunctions.TurnOn(driver, SystemProperties.StillElement); ElementOn = true; PressureRegulator.RunWorkerAsync(); MainDispatcher.Invoke(new Action(() => { lblStatus.Text = "Heating"; })); //Set up variables for calculating when to turn off the element int CurrentTemp = Convert.ToInt16(ColumnTemp); int CurrentDelta = 0; int Counter = 0; PlateauTemp = 0; string StartTempRaw = null; while (StartTempRaw == null) { try { StartTempRaw = driver.Send(new AnalogReadRequest(SystemProperties.SensorColumnTemp)).PinValue.ToString(); } catch { } } double StartTemp = Convert.ToInt64((((Convert.ToDouble(StartTempRaw) * (5.0 / 1023.0)) - 1.25) / 0.005)); double Temp1 = 0.0; double Temp2 = 0.0; double AverageDelta = 0.0; double TotalDelta = 0.0; DataRow row; row = StillStats.NewRow(); row["Time"] = DateTime.Now; row["Temperature"] = CurrentTemp; row["TemperatureDelta"] = 0; row["Pressure"] = Convert.ToDecimal(Pressure); row["Phase"] = Phase; row["Amperage"] = ElementAmperage; row["RefluxTemperature"] = RefluxTemp; row["CondensorTemperature"] = CondensorTemp; StillStats.Rows.Add(row); //Get the last written row for collecting temperature rise statistics DataRow LastRow = StillStats.Rows[0]; //Get two rows from two points in time 2.5 minutes apart so an average temperature change can be obtained from the given time span DataRow Delta1; DataRow Delta2; //Start both fan controllers to maintain the temperature of the coolant in Reflux column and the Condensor //Note that these are started here because they are auto-regulating and will shut off by themselves when not necessary //FanController1.RunWorkerAsync(); //FanController2.RunWorkerAsync(); //Keep the element on and keep collecting data every 10 seconds until the first plateau is reached then go to the next loop //note thate the total delta is there incase it takes longer than 10 minutes to start seeing a temperature rise at the sensor while ((StillEmpty == false && AverageDelta >= 0.02) || TotalDelta < 0.25) { //Change this back to 10 seconds System.Threading.Thread.Sleep(250); //Once the element has been on for 10 minutes start checking for the plateau if (Counter < 60) { Counter = Counter + 1; } else { Delta1 = StillStats.Rows[StillStats.Rows.Count - 19]; Delta2 = StillStats.Rows[StillStats.Rows.Count - 1]; Temp1 = Delta1.Field <Int32>("Temperature"); Temp2 = Delta2.Field <Int32>("Temperature"); AverageDelta = Temp2 != 0 ? ((Temp2 - Temp1) / Temp2) : 0; if (Temp2 > Temp1) { TotalDelta = Temp2 != 0 ? ((Temp2 - StartTemp) / Temp2) : 0; } } CurrentTemp = Convert.ToInt32(ColumnTemp); CurrentDelta = CurrentTemp - LastRow.Field <Int32>("Temperature"); row = StillStats.NewRow(); row["Time"] = DateTime.Now; row["Temperature"] = CurrentTemp; row["TemperatureDelta"] = CurrentDelta; row["Pressure"] = Convert.ToDecimal(Pressure); row["Phase"] = Phase; row["Amperage"] = ElementAmperage; row["RefluxTemperature"] = RefluxTemp; row["CondensorTemperature"] = CondensorTemp; StillStats.Rows.Add(row); LastRow = StillStats.Rows[StillStats.Rows.Count - 1]; MainDispatcher.Invoke(new Action(() => { chartRun.DataBind(); })); } //Prep variables related to the distillation phase and start the fan controller for the condensor Phase = 2; AverageDelta = 0; TotalDelta = 0; PlateauTemp = LastRow.Field <Int32>("Temperature"); MainDispatcher.Invoke(new Action(() => { lblStatus.Text = "Distilling"; })); //Once the first plateau is reached allowing for a 4 degree change at the most //or end the batch if the saftey limit switch is triggered also reset the Delta counters so the next step is not skipped while (StillEmpty == false && (Temp2 - PlateauTemp) < 5 && RVFull == false) { Delta1 = StillStats.Rows[StillStats.Rows.Count - 19]; Delta2 = StillStats.Rows[StillStats.Rows.Count - 1]; Temp1 = Delta1.Field <Int32>("Temperature"); Temp2 = Delta2.Field <Int32>("Temperature"); AverageDelta = Math.Abs(((Temp2 - Temp1) / Temp2)); System.Threading.Thread.Sleep(250); //Change this back to 10 seconds CurrentTemp = Convert.ToInt32(ColumnTemp); CurrentDelta = CurrentTemp - LastRow.Field <Int32>("Temperature"); row = StillStats.NewRow(); row["Time"] = DateTime.Now; row["Temperature"] = CurrentTemp; row["TemperatureDelta"] = CurrentDelta; row["Pressure"] = Convert.ToDecimal(Pressure); row["Phase"] = Phase; row["Amperage"] = ElementAmperage; row["RefluxTemperature"] = RefluxTemp; row["CondensorTemperature"] = CondensorTemp; StillStats.Rows.Add(row); LastRow = StillStats.Rows[StillStats.Rows.Count - 1]; MainDispatcher.Invoke(new Action(() => { chartRun.DataBind(); })); } //Batch complete! MainDispatcher.Invoke(new Action(() => { lblStatus.Text = "Batch Complete, Saving Run Data"; })); MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOff(driver, SystemProperties.StillElement); })); ElementOn = false; Phase = 3; } //If the run completed without issue then calculate the header info and write the data to a local sqldb //Note that this must be done sequentially as the records must relate to a header record //Once the table is succesfully written set the phase back to 0 and start another run if (Phase == 3) { Statistics.CreateHeader(RunStart, DateTime.Now, true, SystemProperties.Units); Statistics.SaveRun(StillStats, RunStart); //Turn off the vacuum pump and fan controllers synchronously with the main thread PressureRegulator.CancelAsync(); //FanController1.CancelAsync(); //FanController2.CancelAsync(); while (PressureRegulator.CancellationPending == true || FanController1.CancellationPending == true || FanController2.CancellationPending == true) { System.Threading.Thread.Sleep(100); } //Fill the system with air so it is at a neutral pressure before pumping any fluids -- note that the system will pull air from the drain valve //since it eventually vents somewhere that is at atmospheric pressure MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOn(driver, SystemProperties.StillDrainValve); })); MainDispatcher.Invoke(new Action(() => { lblStatus.Text = "Draining Still"; })); while (StillEmpty == false || Convert.ToDouble(Pressure) <= -0.2) { System.Threading.Thread.Sleep(1500); } MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOff(driver, SystemProperties.StillDrainValve); })); System.Threading.Thread.Sleep(3000); //Make sure that the switches are working then pump the Recieving vessels contents into a storage tank so the next run can begin MainDispatcher.Invoke(new Action(() => { lblStatus.Text = "Draining Distillate"; })); MainDispatcher.Invoke(new Action(() => { RVEmpty = (driver.Send(new DigitalReadRequest(SystemProperties.RVEmptySwitch)).PinValue == DigitalValue.Low) ? true : false; })); MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOn(driver, SystemProperties.RVDrainValve); })); System.Threading.Thread.Sleep(3000); //3 second delay so the valve has time to open MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOn(driver, SystemProperties.RVFluidPump); })); while (RVEmpty == false) { //MainDispatcher.Invoke(new Action(() => { RVEmpty = (driver.Send(new DigitalReadRequest(SystemProperties.RVEmptySwitch)).PinValue == DigitalValue.Low) ? true : false; })); System.Threading.Thread.Sleep(1500); } //Turn off the pump and shut the valves and give them 3 seconds to close MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOff(driver, SystemProperties.RVFluidPump); })); MainDispatcher.Invoke(new Action(() => { DriverFunctions.RelayOff(driver, SystemProperties.RVDrainValve); })); Phase = 0; } } //The arduino driver reference has a tendency to randomly throw null reference exceptions, for now I will handle it by just restarting the arduino //the code is designed to pick up where it left off if it errors since the phase is still in memory catch (NullReferenceException) { MainDispatcher.Invoke(new Action(() => { driver = Periphrials.InitializeArduinoDriver(); })); } } while (true); }); UIUpdater = new BackgroundWorker(); UIUpdater.WorkerSupportsCancellation = true; UIUpdater.DoWork += new DoWorkEventHandler((state, args) => { do { System.Threading.Thread.Sleep(1000); MainDispatcher.Invoke(new Action(() => { lblPressure.Text = Main.Pressure + ((SystemProperties.Units == "Metric") ? "kPa" : "PSI"); })); MainDispatcher.Invoke(new Action(() => { lblTemp1.Text = Main.ColumnTemp + ((SystemProperties.Units == "Metric") ? "°C" : "°F"); })); }while (true); }); //Start the workers and pause for 2 seconds to allow for initial values to be collected SystemMonitor.RunWorkerAsync(); System.Threading.Thread.Sleep(2000); StillController.RunWorkerAsync(); UIUpdater.RunWorkerAsync(); }