def _test_temperature(): """Tests the temperature probe""" for i in range(5): temperature, res = interfaces.read_temperature() interfaces.lcd_out("Temperature: {0:0.3f}C".format(temperature), 1) interfaces.lcd_out("Res: {0:0.3f} Ohms".format(res), 2) interfaces.delay(0.5)
def drive_step_stick(self, cycles, direction): """ cycles and direction are integers Communicates with arduino to add HCl through pump :param cycles: number of rising edges for the pump :param direction: direction of pump """ if cycles == 0: return 0 interfaces.delay(0.01) if self.serial.writable(): self.serial.write(cycles.to_bytes(4, "little")) self.serial.write(direction.to_bytes(1, "little")) self.serial.flush() wait_time = cycles / 1000 + 0.5 print("wait_time = ", wait_time) interfaces.delay(wait_time) temp = self.serial.readline() if temp == b"DONE\r\n" or temp == b"": return 0 else: return int(temp) else: interfaces.lcd_out("Arduino Unavailable", 4, constants.LCD_CENT_JUST)
def degas(seconds): interfaces.lcd_clear() interfaces.lcd_out("Degassing {0:.0f}".format(seconds), line=1) interfaces.lcd_out("seconds", line=2) interfaces.stir_speed_fast() interfaces.delay(seconds, countdown=True) interfaces.stir_speed_slow()
def drive_pump(self, volume, direction): """Converts volume to cycles and ensures and checks pump level and values""" if direction == 0: space_in_pump = self.max_pump_capacity - self.volume_in_pump if volume > space_in_pump: interfaces.lcd_out("Filling Error", line=4) else: interfaces.lcd_out("Filling {0:1.2f} ml".format(volume), line=4) cycles = analysis.determine_pump_cycles(volume) self.drive_step_stick(cycles, direction) self.volume_in_pump += volume elif direction == 1: if volume > self.volume_in_pump: interfaces.lcd_out("Pumping Error", line=4) else: interfaces.lcd_out("Pumping {0:1.2f} ml".format(volume), line=4) cycles = analysis.determine_pump_cycles(volume) offset = self.drive_step_stick(cycles, direction) # offset is what is returned from drive_step_stick which originally is returned from the arduino if offset != 0: self.drive_step_stick(offset, 0) self.drive_step_stick(offset, 1) self.set_volume_in_pump(self.volume_in_pump - volume) interfaces.lcd_out("Pump Vol: {0:1.2f} ml".format(self.volume_in_pump), line=4)
def test_routines_test_mode_pump(monkeypatch, capsys): monkeypatch.setattr("sys.stdin", io.StringIO("0\n*\n5\nA\n0\nA\n")) routines.test_mode_pump() _ = capsys.readouterr() interfaces.lcd_out("", 1) captured = capsys.readouterr() assert captured.out == ("*====================*\n" + "| |\n" + "| |\n" + "| |\n" + "|Pump Vol: 0.50 ml |\n" + "*====================*\n")
def pump_volume(self, volume, direction): """ Moves volume of solution through pump :param volume: amount of volume to move (float) :param direction: 0 to pull solution in, 1 to pump out """ volume_to_add = volume # pull in solution if direction == 0: # if volume_to_add is greater than space in the pump space_in_pump = self.max_pump_capacity - self.volume_in_pump if volume_to_add > space_in_pump: volume_to_add = self.max_pump_capacity - self.volume_in_pump self.drive_pump(volume_to_add, direction) # pump out solution elif direction == 1: # volume greater than max capacity of pump if volume_to_add > self.max_pump_capacity: interfaces.lcd_out( "Volume > pumpable", style=constants.LCD_CENT_JUST, line=4 ) # pump out all current volume next_volume = self.volume_in_pump self.drive_pump(next_volume, 1) # calculate new volume to add volume_to_add = volume_to_add - next_volume # keep pumping until full volume_to_add is met while volume_to_add > 0: next_volume = min(volume_to_add, self.max_pump_capacity) self.drive_pump(next_volume, 0) self.drive_pump(next_volume, 1) volume_to_add -= next_volume # volume greater than volume in pump elif volume_to_add > self.volume_in_pump: next_volume = self.volume_in_pump self.drive_pump(next_volume, 1) # calculate remaining volume to add volume_to_add -= next_volume self.drive_pump(volume_to_add, 0) self.drive_pump(volume_to_add, 1) else: # volume less than volume in pump self.drive_pump(volume_to_add, direction)
def test_interfaces_lcd(capsys): # flush stdout _ = capsys.readouterr() interfaces.lcd_out("Test string", 1, constants.LCD_LEFT_JUST) captured = capsys.readouterr() assert captured.out == ("*====================*\n" + "|Test string |\n" + "| |\n" + "| |\n" + "| |\n" + "*====================*\n")
def prime_pump(): """ Primes pump by drawing in and pushing out solution. Depends on limit switches installed """ interfaces.lcd_out("How many pumps?", 1) selection = interfaces.read_user_input() sel = int(selection) while sel > 0: while sel > 0: interfaces.drive_step_stick(10000, 0) interfaces.drive_step_stick(10000, 1) sel = sel - 1 interfaces.lcd_out("How many more?", 1) selection = interfaces.read_user_input() sel = int(selection)
def pump_volume(self, volume, direction): volume_to_add = volume # pull in solution if direction == 0: # check if volume to add is greater than space left space_in_pump = self.max_pump_capacity - self.volume_in_pump if volume_to_add > space_in_pump: volume_to_add = self.max_pump_capacity - self.volume_in_pump self.drive_pump(volume_to_add, direction) # pump out solution elif direction == 1: # volume greater than max capacity of pump if volume_to_add > self.max_pump_capacity: interfaces.lcd_out("Volume > pumpable", style=constants.LCD_CENT_JUST, line=4) # pump out all current volume next_volume = self.volume_in_pump self.drive_pump(next_volume, 1) # calculate new volume to add volume_to_add = volume_to_add - next_volume # keep pumping until full volume_to_add is met while volume_to_add > 0: next_volume = min(volume_to_add, self.max_pump_capacity) self.drive_pump(next_volume, 0) self.drive_pump(next_volume, 1) volume_to_add -= next_volume # volume greater than volume in pump elif volume_to_add > self.volume_in_pump: next_volume = self.volume_in_pump self.drive_pump(next_volume, 1) # calculate remaining volume to add volume_to_add -= next_volume self.drive_pump(volume_to_add, 0) self.drive_pump(volume_to_add, 1) else: # volume less than volume in pump self.drive_pump(volume_to_add, direction)
def test_mode_read_values(numVals=60, timestep=0.5): numVals = numVals timestep = timestep timeVals = np.zeros(numVals) tempVals = np.zeros(numVals) resVals = np.zeros(numVals) pHVals = np.zeros(numVals) voltVals = np.zeros(numVals) for i in range(numVals): temp, res = interfaces.read_temperature() pH_reading, pH_volts = interfaces.read_pH() interfaces.lcd_out("Temp: {0:>4.3f} C".format(temp), line=1) interfaces.lcd_out("Res: {0:>4.3f} Ohms".format(res), line=2) interfaces.lcd_out("pH: {0:>4.5f} pH".format(pH_reading), line=3) interfaces.lcd_out("pH V: {0:>3.4f} mV".format(pH_volts * 1000), line=4) interfaces.lcd_out("Reading: {}".format(i), 1, console=True) timeVals[i] = timestep * i tempVals[i] = temp resVals[i] = res pHVals[i] = pH_reading voltVals[i] = pH_volts interfaces.delay(timestep)
def test_mode_read_volume(): interfaces.lcd_clear() interfaces.lcd_out("Pump Vol: ", line=1) interfaces.lcd_out( "{0:1.2f}".format(constants.volume_in_pump), style=constants.LCD_CENT_JUST, line=2, ) interfaces.lcd_out("Press any to cont.", line=3) interfaces.read_user_input()
def titration(pH_target, solution_increment_amount, data, total_sol_added, degas_time=0): """ Incrementally adds HCl depending on the input parameters, until target pH is reached :param pH_target: target pH for the titration :param solution_increment_amount: amount of HCl to add to solution. Units of mL :param data: list of recorded temperature, pH, and solution volume data so far :param total_sol_added: total amount of HCl added to the solution so far :param degas_time: optional parameter defining the de-gas time for the solution after the target pH has been reached :return: total solution added so far """ interfaces.lcd_out( "Titrating to {} pH".format(str(pH_target)), style=constants.LCD_CENT_JUST, line=4, ) # total HCl added total_sol = total_sol_added current_pH = wait_pH_stable(total_sol, data) while current_pH - pH_target > constants.PH_ACCURACY: interfaces.pump_volume(solution_increment_amount, 1) if constants.volume_in_pump < 0.05: # pump in 1 mL interfaces.pump_volume(1.0, 0) total_sol += solution_increment_amount # TESTING SETTLING interfaces.lcd_out("Mixing...", style=constants.LCD_CENT_JUST, line=4) interfaces.delay(10) # allow it to mix before taking measurements current_pH = wait_pH_stable(total_sol, data) interfaces.temperature_controller.update() interfaces.lcd_clear() interfaces.lcd_out("pH value {} reached".format(current_pH), line=1) interfaces.lcd_out("Degassing " + str(degas_time) + " seconds", line=2) interfaces.delay(degas_time, countdown=True) return total_sol
def wait_pH_stable(total_sol, data): """ Continually polls probes until pH values are stable :param total_sol: total amount of HCl added to the solution so far :param data: list of recorded temperature, pH, and solution volume data so far :return: mean stable pH value of last 10 values """ # keep track of 10 most recent pH values to ensure pH is stable pH_values = [0] * 10 # a counter used for updating values in pH_values pH_list_counter = 0 # flag to ensure at least 10 pH readings have been made before adding solution valid_num_values_tested = False while True: pH_reading, pH_volts = interfaces.read_pH() temperature_reading = interfaces.read_temperature()[0] interfaces.lcd_out("pH: {0:>4.5f} pH".format(pH_reading), line=1) interfaces.lcd_out("pH V: {0:>3.4f} mV".format(pH_volts * 1000), line=2) interfaces.lcd_out("Temp: {0:>4.3f} C".format(temperature_reading), line=3) pH_values[pH_list_counter] = pH_reading if pH_list_counter == 9: valid_num_values_tested = True # Check that the temperature of the solution is within bounds if (abs(temperature_reading - constants.TARGET_TEMPERATURE) > constants.TEMPERATURE_ACCURACY): # interfaces.lcd_out("TEMPERATURE OUT OF BOUNDS") # TODO output to error log pass # Record data point (temperature, pH volts, total HCl) data.append( (temperature_reading, pH_volts, total_sol, None, None, None, None)) pH_list_counter = 0 if pH_list_counter >= 9 else pH_list_counter + 1 if (valid_num_values_tested and analysis.std_deviation(pH_values) < constants.TARGET_STD_DEVIATION): return pH_reading interfaces.delay(constants.TITRATION_WAIT_TIME)
def test_mode_toggle_test_mode(): constants.IS_TEST = not constants.IS_TEST interfaces.lcd_clear() interfaces.lcd_out("Testing: {}".format(constants.IS_TEST), line=1) interfaces.lcd_out("Press any to cont.", line=3) interfaces.read_user_input()
def _calibrate_pH(): """Routine for calibrating pH sensor.""" # get first buffer pH buffer1_actual_pH = interfaces.read_user_value("Enter buffer pH:") interfaces.lcd_out("Put sensor in buffer", style=constants.LCD_CENT_JUST, line=1) interfaces.lcd_out("", line=2) interfaces.lcd_out("Press 1 to", style=constants.LCD_CENT_JUST, line=3) interfaces.lcd_out("record value", style=constants.LCD_CENT_JUST, line=4) # Waits for user to press enter interfaces.read_user_input() buffer1_measured_volts = float(interfaces.read_raw_pH()) interfaces.lcd_clear() interfaces.lcd_out("Recorded pH and volts:", line=1) interfaces.lcd_out( "{0:>2.5f} pH, {1:>3.4f} V".format(buffer1_actual_pH, buffer1_measured_volts), line=2, ) interfaces.lcd_out("Press any button", style=constants.LCD_CENT_JUST, line=3) interfaces.lcd_out("to continue", style=constants.LCD_CENT_JUST, line=4) interfaces.read_user_input() # set calibration constants constants.PH_REF_VOLTAGE = buffer1_measured_volts constants.PH_REF_PH = buffer1_actual_pH
def run(): """Main driver for the program. Initializes components and queries the user for next steps""" try: # initialize components initialize_components() # output prompt to LCD screen routine_selection = "0" page = 1 while routine_selection != "6" or routine_selection != constants.KEY_6: if routine_selection is constants.KEY_STAR: if page == 1: page = 2 else: page = 1 if page == 1: interfaces.display_list(constants.ROUTINE_OPTIONS_1) else: interfaces.display_list(constants.ROUTINE_OPTIONS_2) # wait for user input to select which routine (polling should be fine here) routine_selection = interfaces.read_user_input() routines.run_routine(routine_selection) analysis.save_calibration_data() interfaces.temperature_controller.deactivate() interfaces.lcd_clear() interfaces.ui_lcd.lcd_backlight(False) except (BaseException, Exception): # Deactivate the SSR if any crash occurs if interfaces.temperature_controller is not None: interfaces.temperature_controller.deactivate() print("\n************************\nDeactivated SSR\n************************\n") try: interfaces.lcd_clear() except BaseException: pass try: interfaces.lcd_out("Deactivated SSR", 1) except BaseException: pass # Attempt to save calibration data, this will save syringe position # and any recent calibrations try: analysis.save_calibration_data() print( "\n************************\nCalibration Saved\n************************\n" ) try: interfaces.lcd_out("Calibration Saved", 2) except Exception: pass except Exception: print( "\n!!!!!!!!!!!!!!!!!!!!!!!!\nUnable to save calibration data\n!!!!!!!!!!!!!!!!!!!!!!!!\n" ) try: interfaces.lcd_out("Calibration UNSAVED", 2) except Exception: pass print(sys.exc_info()[0]) traceback.print_exc()
def edit_settings(): """Resets calibration constants to default""" interfaces.lcd_out("Reset calibration", line=1) interfaces.lcd_out("settings to default?", line=2) interfaces.lcd_out("1: Yes", line=3) interfaces.lcd_out("2: No", line=4) selection = interfaces.read_user_input() if selection != "n" or selection != "N": analysis.reset_calibration() analysis.save_calibration_data() interfaces.lcd_clear() interfaces.lcd_out("Default constants restored", 1) interfaces.lcd_out("Press any to cont.", 3) interfaces.read_user_input() interfaces.lcd_out("Set volume in pump? (Y/n)", 1) selection = interfaces.read_user_input() if selection != "n" or selection != "N": vol_in_pump = interfaces.read_user_value("Volume in pump: ") constants.volume_in_pump = vol_in_pump analysis.save_calibration_data() interfaces.lcd_out("Volume in pump set", 1) interfaces.lcd_out("Press any to cont.", 3) interfaces.read_user_input()
def _calibrate_temperature(): """Routine for calibrating the temperature probe.""" expected_temperature = interfaces.read_user_value( "Ref solution temperature?") interfaces.lcd_out("Put probe in sol", style=constants.LCD_CENT_JUST, line=1) interfaces.lcd_out("", line=2) interfaces.lcd_out("Press 1 to", style=constants.LCD_CENT_JUST, line=3) interfaces.lcd_out("record value", style=constants.LCD_CENT_JUST, line=4) # Waits for user to press enter interfaces.read_user_input() expected_resistance = analysis.calculate_expected_resistance( expected_temperature) actual_temperature, actual_resistance = interfaces.read_temperature() interfaces.lcd_clear() interfaces.lcd_out( "Recorded temperature: {0:0.3f}".format(actual_temperature), line=1) diff = expected_resistance - actual_resistance new_ref_resistance = ( constants.TEMPERATURE_REF_RESISTANCE + diff * constants.TEMPERATURE_REF_RESISTANCE / expected_resistance) constants.TEMPERATURE_REF_RESISTANCE = float(new_ref_resistance) # reinitialize sensors with calibrated values interfaces.lcd_out("{}".format(new_ref_resistance), line=2) interfaces.setup_interfaces()
def total_alkalinity_titration(): """Runs through the full titration routine to find total alkalinity""" # pull in 1 ml of solution into pump for use in titration if constants.volume_in_pump < 1.0: p_volume = 1.0 - constants.volume_in_pump interfaces.pump_volume(float(p_volume), 0) # data object to hold recorded data data = [( "temperature", "pH V", "solution volume", "weight", "salinity", "Buffer pH", "Buffer pH V", )] # query user for initial solution weight initial_weight = interfaces.read_user_value("Sol. weight (g):") salinity = interfaces.read_user_value("Sol. salinity (ppt):") buffer_ph = constants.PH_REF_PH buffer_v = constants.PH_REF_VOLTAGE interfaces.lcd_clear() interfaces.lcd_out("Calibrate pH probe?", line=1) interfaces.lcd_out("Yes: 1", line=2) interfaces.lcd_out("No (use old): 0", line=3) interfaces.lcd_out("{0:>2.3f} pH: {1:>2.4f} V".format(buffer_ph, buffer_v), line=4) selection = interfaces.read_user_input() if selection == constants.KEY_1: _calibrate_pH() data.append( (None, None, None, initial_weight, salinity, buffer_ph, buffer_v)) # while not initial_weight.replace('.', '').isdigit(): # interfaces.lcd_out("Please enter a numeric value.", console=True) # initial_weight = interfaces.read_user_input() # initial titration (bring solution close to 3.5) # todo set stir speed slow total_sol = 0 interfaces.lcd_out("Bring pH to 3.5:", line=1) interfaces.lcd_out("Manual: 1", line=2) interfaces.lcd_out("Automatic: 2", line=3) interfaces.lcd_out("Stir speed: slow", line=4) user_choice = interfaces.read_user_input() # wait until solution is up to temperature interfaces.temperature_controller.activate() interfaces.lcd_clear() interfaces.lcd_out("Heating to 30 C...", line=1) interfaces.lcd_out("Please wait...", style=constants.LCD_CENT_JUST, line=3) if user_choice == "1": interfaces.lcd_out("MANUAL SELECTED", style=constants.LCD_CENT_JUST, line=4) else: interfaces.lcd_out("AUTO SELECTED", style=constants.LCD_CENT_JUST, line=4) while not interfaces.temperature_controller.at_temperature(): interfaces.temperature_controller.update() temperature = interfaces.temperature_controller.get_last_temperature() interfaces.lcd_out( "Temp: {0:>4.3f} C".format(temperature), style=constants.LCD_CENT_JUST, line=2, ) if user_choice == "1": # Manual while user_choice == "1": p_volume = interfaces.read_user_value("Volume: ") interfaces.lcd_clear() interfaces.lcd_out("Direction (0/1): ", line=1) p_direction = interfaces.read_user_input() p_volume = float(p_volume) p_direction = int(p_direction) if p_direction == 1: total_sol += p_volume if p_direction == 0 or p_direction == 1: interfaces.pump_volume(p_volume, p_direction) current_pH = wait_pH_stable(total_sol, data) interfaces.lcd_out("Current pH: {0:>4.5f}".format(current_pH), line=1) interfaces.lcd_out("Continue adding solution?", line=2) interfaces.lcd_out("(0 - No, 1 - Yes)", line=3) interfaces.lcd_out("", line=4) user_choice = interfaces.read_user_input() else: # Automatic total_sol = titration(constants.TARGET_PH_INIT, constants.INCREMENT_AMOUNT_INIT, data, 0, 0) total_sol = titration( constants.TARGET_PH_MID, constants.INCREMENT_AMOUNT_MID, data, total_sol, 600, ) # 3.5 -> 3.0 # todo set stir speed fast interfaces.lcd_out("Stir speed: fast", line=4) titration(constants.TARGET_PH_FINAL, constants.INCREMENT_AMOUNT_FINAL, data, total_sol) # save data to csv analysis.write_titration_data(data) interfaces.temperature_controller.deactivate()