def query(self, msg_str): """Try to query the device. Args: msg_str (string): Message to be sent. Returns: success (bool): True if the message was sent and a reply was received successfully, False otherwise. ans_str (string): Reply received from the device. [numpy.nan] if unsuccessful. """ success = False ans_str = np.nan if not self.is_alive: print("ERROR: Device is not connected yet or already closed.") else: try: ans_str = self.device.query(msg_str) except visa.VisaIOError as err: # Print error and struggle on pft(err) except: raise else: ans_str = ans_str.strip() success = True return (success, ans_str)
def write(self, msg_str): """Try to write a command to the device. Args: msg_str (string): Message to be sent. Returns: True if the message was sent successfully, False otherwise. NOTE: It does not indicate whether the message made sense to the device. """ if not self.is_alive: print("ERROR: Device is not connected yet or already closed.") return False try: self.device.write(msg_str) except visa.VisaIOError as err: # Print error and struggle on pft(err) return False except: raise return True
def DAQ_function(): # Query the Arduino for its state success, tmp_state = ard.query_ascii_values("?", delimiter="\t") if not (success): dprint("'%s' reports IOError" % ard.name) return False # Parse readings into separate state variables try: state.time, state.reading_1 = tmp_state state.time /= 1000 except Exception as err: pft(err, 3) dprint("'%s' reports IOError" % ard.name) return False # Use Arduino time or PC time? USE_PC_TIME = True now = time.perf_counter() if USE_PC_TIME else state.time if qdev_ard.update_counter_DAQ == 1: state.time_0 = now state.time = 0 else: state.time = now - state.time_0 # For demo purposes: Quit automatically after N updates if qdev_ard.update_counter_DAQ > 1000: app.quit() return True
def query_status(self): """Query the status or error message of the Julabo and store it in the class member 'state'. Will be set to numpy.nan if unsuccessful. Returns: True if successful, False otherwise. """ success, reply = self.query_("STATUS") if success: self.state.status = reply try: status_number = int(self.state.status[:3]) except (TypeError, ValueError) as err: self.state.has_error = np.nan pft(err) else: if status_number < 0: self.state.has_error = True else: self.state.has_error = False return True self.state.status = np.nan self.state.has_error = np.nan return False
def DAQ_function(): # Date-time keeping str_cur_date, str_cur_time, str_cur_datetime = get_current_date_time() # Query the Arduino for its state success, tmp_state = ard.query_ascii_values("?", delimiter="\t") if not (success): dprint("'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time)) return False # Parse readings into separate state variables try: state.time, state.reading_1 = tmp_state state.time /= 1000 except Exception as err: pft(err, 3) dprint("'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time)) return False if USE_PC_TIME: state.time = time.perf_counter() # Add readings to chart history window.history_chart_curve.appendData(state.time, state.reading_1) # Logging to file log.update(filepath=str_cur_datetime + ".txt") # Return success return True
def jobs_function(self, func, args): # Send I/O operation to the device try: func(*args) self.dev.wait_for_OPC() # Wait for OPC except Exception as err: pft(err)
def DAQ_function(): # Date-time keeping str_cur_date, str_cur_time, str_cur_datetime = get_current_date_time() # Query the Arduino for its state success, tmp_state = ard.query_ascii_values("?", delimiter="\t") if not (success): dprint("'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time)) return False # Parse readings into separate state variables try: ( state.time, state.dht22_temp, state.dht22_humi, ds18b20_temp, ) = tmp_state state.time /= 1000 # Arduino time, [msec] to [s] except Exception as err: pft(err, 3) dprint("'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time)) return False # Optional extra sensor to register the heater surface temperature # print("%.2f" % ds18b20_temp) # We will use PC time instead state.time = time.perf_counter() # PID control pid.set_mode( mode=(psu.state.ENA_output and state.pid_enabled and not np.isnan(state.dht22_temp)), current_input=state.dht22_temp, current_output=psu.state.V_source, ) if pid.compute(current_input=state.dht22_temp): # New PID output got computed -> send new voltage to PSU qdev_psu.send(qdev_psu.dev.set_V_source, pid.output) # Print debug info to the terminal dprint("Tp=%7.3f Ti=%7.3f outp=%7.3f" % (pid.pTerm, pid.iTerm, pid.output)) # Add readings to chart histories window.tscurve_dht22_temp.appendData(state.time, state.dht22_temp) window.tscurve_dht22_humi.appendData(state.time, state.dht22_humi) window.tscurve_power.appendData(state.time, psu.state.P_meas) # Logging to file log.update(filepath=str_cur_datetime + ".txt", mode="w") # Return success return True
def test_pft_overshoot_callstack(): try: 0 / 0 except ZeroDivisionError as err: with mock.patch("sys.stdout", new=io.StringIO()) as fake_stdout: pft(err, 50) assert (fake_stdout.getvalue().split("\n")[-2] == "\x1b[1;31mZeroDivisionError: \x1b[1;37mdivision by zero")
def query_ascii_values( self, msg: str, delimiter="\t", raises_on_timeout: bool = False, ) -> Tuple[bool, list]: r"""Send a message to the serial device and subsequently read the reply. Expects a reply in the form of an ASCII string containing a list of numeric values, separated by a delimiter. These values will be parsed into a list and returned. Args: msg (:obj:`str`): ASCII string to be sent to the serial device. delimiter (:obj:`str`, optional): Delimiter used in the device's reply. Default: `"\\t"` raises_on_timeout (:obj:`bool`, optional): Should an exception be raised when a write or read timeout occurs? Default: :const:`False` Returns: :obj:`tuple`: success (:obj:`bool`): True if successful, False otherwise. reply_list (:obj:`list`): Reply received from the device and parsed into a list of separate values. The list is empty if unsuccessful. """ success, reply = self.query(msg, raises_on_timeout=raises_on_timeout, returns_ascii=True) if not success: return (False, ()) # --> leaving try: # NOTE: `ast.literal_eval` chokes when it receives 'nan' so we ditch # it and just interpret everything as `float` instead. # reply_list = list(map(literal_eval, reply.split(delimiter))) reply_list = list(map(float, reply.split(delimiter))) except ValueError as err: pft(err, 3) return (False, ()) # --> leaving except Exception as err: pft(err, 3) sys.exit(0) # --> leaving return (True, reply_list)
def jobs_function(self, func, args): if func == "signal_GUI_input_field_update": # Special instruction self.signal_GUI_input_field_update.emit(*args) else: # Default job processing: # Send I/O operation to the device try: func(*args) except Exception as err: pft(err)
def query_OCP_level(self, channel: int = 1) -> bool: """Returns: True if successful, False otherwise. """ success, reply = self.query("OCP%d?" % channel) if success: if reply[:3] == "CP%d" % channel: self.state.OCP_level = float(reply[4:]) return True else: pft("Received incorrect reply: %s" % reply) return False
def DAQ_function(): # Date-time keeping str_cur_date, str_cur_time, str_cur_datetime = get_current_date_time() # Query the Arduino for its state success, tmp_state = ard.query_ascii_values("?", delimiter="\t") if not (success): dprint( "'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time) ) return False # Parse readings into separate state variables try: ( state.time, state.ds_temp, state.bme_temp, state.bme_humi, state.bme_pres, ) = tmp_state state.time /= 1000 # Arduino time, [msec] to [s] state.bme_pres /= 100 # [Pa] to [mbar] except Exception as err: pft(err, 3) dprint( "'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time) ) return False # Catch very intermittent DS18B20 sensor errors if state.ds_temp <= -127.0: state.ds_temp = np.nan # We will use PC time instead state.time = time.perf_counter() # Add readings to chart histories window.tscurve_julabo_setp.appendData(state.time, julabo.state.setpoint) window.tscurve_julabo_bath.appendData(state.time, julabo.state.bath_temp) window.tscurve_ds_temp.appendData(state.time, state.ds_temp) window.tscurve_bme_temp.appendData(state.time, state.bme_temp) window.tscurve_bme_humi.appendData(state.time, state.bme_humi) window.tscurve_bme_pres.appendData(state.time, state.bme_pres) # Logging to file log.update(filepath=str_cur_datetime + ".txt", mode="w") # Return success return True
def query_ENA_output(self, channel: int = 1) -> bool: """Returns: True if the query was received successfully, False otherwise. """ success, reply = self.query("OP%d?" % channel) if success: try: self.state.ENA_output = bool(int(reply)) except Exception as err: pft("Received incorrect reply: %s" % reply) self.ser.flushOutput() self.ser.flushInput() return success
def DAQ_function(): # Date-time keeping str_cur_date, str_cur_time, str_cur_datetime = get_current_date_time() state.update_counter_DAQ += 1 # Keep track of the obtained DAQ rate if not state.QET_rate.isValid(): state.QET_rate.start() else: # Obtained DAQ rate state.rate_accumulator += 1 dT = state.QET_rate.elapsed() if dT >= 1000: # Evaluate every N elapsed milliseconds. Hard-coded. state.QET_rate.restart() try: state.obtained_DAQ_rate_Hz = state.rate_accumulator / dT * 1e3 except ZeroDivisionError: state.obtained_DAQ_rate_Hz = np.nan state.rate_accumulator = 0 # Query the Arduino for its state success, tmp_state = ard.query_ascii_values("?", delimiter="\t") if not (success): dprint("'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time)) sys.exit(0) # Parse readings into separate state variables try: state.time, state.reading_1 = tmp_state state.time /= 1000 except Exception as err: pft(err, 3) dprint("'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time)) sys.exit(0) if USE_PC_TIME: state.time = time.perf_counter() # Add readings to chart histories window.history_chart_curve.appendData(state.time, state.reading_1) # Logging to file log.update(filepath=str_cur_datetime + ".txt") # We update the GUI right now because this is a singlethread demo window.update_GUI()
def query_I_source(self, channel: int = 1) -> bool: """Returns: True if the query was received successfully, False otherwise. """ success, reply = self.query("I%d?" % channel) if success: if reply[:2] == "I%d" % channel: self.state.I_source = float(reply[3:]) return True pft("Received incorrect reply: %s" % reply) self.ser.flushOutput() self.ser.flushInput() return False
def query_I_meas(self, channel: int = 1) -> bool: """Returns: True if the query was received successfully, False otherwise. """ success, reply = self.query("I%dO?" % channel) if success: if reply[-1:] == "A": self.state.I_meas = float(reply[:-1]) self.state.P_meas = self.state.I_meas * self.state.V_meas return True pft("Received incorrect reply: %s" % reply) self.ser.flushOutput() self.ser.flushInput() return False
def wait_for_OPC(self): """'Operation complete' query, used for event synchronization. Will wait for all device operations to complete or until a timeout is triggered. Blocking. Returns: True if successful, False otherwise. """ # Returns an ASCII "1" when all pending overlapped operations have been # completed. success, reply = self.query("*opc?") if success and reply == "1": return True pft("Warning: *opc? timed out at device %s" % self.name) return False
def set_setpoint_3(self, value: float): """Set the temperature setpoint #3. Subsequently, the Julabo is queried for the obtained value, which might be different than the one requested. Returns: True if all communication was successful, False otherwise. """ try: value = float(value) except (TypeError, ValueError) as err: pft(err) return False if self.write_("OUT_SP_02 %.2f" % value): return self.query_setpoint_3() else: return False
def query_setpoint_3(self): """Query the temperature setpoint #3 and store it in the class member 'state'. Will be set to numpy.nan if unsuccessful. Returns: True if successful, False otherwise. """ success, reply = self.query_("IN_SP_02") if success: try: num = float(reply) except (TypeError, ValueError) as err: pft(err) else: self.state.setpoint_3 = num return True self.state.setpoint_3 = np.nan return False
def query_running(self): """Query if the Julabo is running and store it in the class member 'state'. Will be set to numpy.nan if unsuccessful. Returns: True if successful, False otherwise. """ success, reply = self.query_("IN_MODE_05") if success: try: ans = bool(int(reply)) except (TypeError, ValueError) as err: pft(err) else: self.state.running = ans return True self.state.running = np.nan return False
def query_over_temp(self): """Query the high-temperature warning limit and store it in the class member 'state'. Will be set to numpy.nan if unsuccessful. Returns: True if successful, False otherwise. """ success, reply = self.query_("IN_SP_03") if success: try: num = float(reply) except (TypeError, ValueError) as err: pft(err) else: self.state.over_temp = num return True self.state.over_temp = np.nan return False
def query_bath_temp(self): """Query the current bath temperature and store it in the class member 'state'. Will be set to numpy.nan if unsuccessful. Returns: True if successful, False otherwise. """ success, reply = self.query_("IN_PV_00") if success: try: num = float(reply) except (TypeError, ValueError) as err: pft(err) else: self.state.bath_temp = num return True self.state.bath_temp = np.nan return False
def set_setpoint_preset(self, n: int): """Instruct the Julabo to select another setpoint preset. Args: n (:obj:`int`): Setpoint to be used, either 1, 2 or 3. Returns: True if successful, False otherwise. """ if not (n == 1 or n == 2 or n == 3): pft("WARNING: Received illegal setpoint preset.\n" "Must be either 1, 2 or 3.") return False if self.write_("OUT_MODE_01 %i" % (n - 1)): self.state.setpoint_preset = n return True else: return False
def query_temp_unit(self): """Query the temperature unit used by the Julabo and store it in the class member 'state'. Will be set to numpy.nan if unsuccessful, else either "C" or "F". Returns: True if successful, False otherwise. """ success, reply = self.query_("IN_SP_06") if success: try: num = int(reply) except (TypeError, ValueError) as err: pft(err) else: self.state.temp_unit = "C" if num == 0 else "F" return True self.state.temp_unit = np.nan return False
def query_setpoint_preset(self): """Query the setpoint preset currently used by the Julabo (#1, #2 or #3) and store it in the class member 'state'. Will be set to numpy.nan if unsuccessful. Returns: True if successful, False otherwise. """ success, reply = self.query_("IN_MODE_01") if success: try: num = int(reply) except (TypeError, ValueError) as err: pft(err) else: self.state.setpoint_preset = num + 1 return True self.state.setpoint_preset = np.nan return False
def query( self, msg: Union[str, bytes], raises_on_timeout: bool = False, returns_ascii: bool = True, ) -> tuple: success, reply = super().query( msg, raises_on_timeout, returns_ascii=False # Binary I/O, not ASCII ) # The ThermoFlex is more complex in its replies than the average device. # Hence: if success: if (len(reply) >= 4) and reply[3] == 0x0F: # Error reported by chiller if reply[5] == 1: pft("Bad command received by chiller", 3) elif reply[5] == 2: pft("Bad data received by chiller", 3) elif reply[5] == 3: pft("Bad checksum received by chiller", 3) success = False else: # We got a reply back from /a/ device, not necessarily a # ThermoFlex chiller. success = True if reply is None: reply = np.nan return (success, reply)
def _jobs_function(self, func, args): # Send I/O operation to the device try: func(*args) except Exception as err: # pylint: disable=broad-except pft(err) # Check to signal auto open or close of an optional peripheral valve if func == self.dev.send_setpoint: if args[0] == 0: # Setpoint was set to 0 --> signal auto close self.dev.valve_auto_close_briefly_prevent = False self.signal_valve_auto_close.emit() else: # Flow enabled --> start deadtime on auto close # --> signal auto open self.dev.valve_auto_close_briefly_prevent = True self.dev.valve_auto_close_start_deadtime = ( QDateTime.currentDateTime() ) self.dev.state.prev_flow_rate = -1 # Necessary reset self.signal_valve_auto_open.emit()
def parse_data_bytes(self, ans_bytes): """Parse the data bytes. The manual states: data_bytes[0] : the qualifier byte b.0 to b.3 indicates unit of measure index b.4 to b.7 indicates precision of measurement data_bytes[1:]: value Returns: value (float): The decoded float value. [numpy.nan] if unsuccessful. uom (int): Unit of measure index. [numpy.nan] if unsuccessful. """ value = np.nan uom = np.nan try: nn = ans_bytes[4] # Number of data bytes to follow data_bytes = ans_bytes[5:5 + nn] pom = data_bytes[0] >> 4 # Precision of measurement uom = data_bytes[0] % 0x10 # Unit of measure index int_value = int.from_bytes(data_bytes[1:], byteorder="big", signed=False) except Exception as err: pft(err, 3) else: if pom == 0: value = int_value elif pom == 1: value = int_value * 0.1 elif pom == 2: value = int_value * 0.01 elif pom == 3: value = int_value * 0.001 elif pom == 4: value = int_value * 0.0001 return (value, uom)
def __init__(self, trav_horz: Compax3_servo = None, trav_vert: Compax3_servo = None, **kwargs): super().__init__(**kwargs) if not (isinstance(trav_horz, Compax3_servo) or trav_horz is None): pft("Argument 'trav_horz' is of a wrong type.", 3) sys.exit(1) if not (isinstance(trav_vert, Compax3_servo) or trav_vert is None): pft("Argument 'trav_vert' is of a wrong type.", 3) sys.exit(1) self.listening_to_arrow_keys = False self.trav_horz = trav_horz self.trav_vert = trav_vert self.horz_pos = np.nan # [mm] self.vert_pos = np.nan # [mm] self.step_size = np.nan # [mm] self.create_GUI() self.connect_signals_to_slots() self.process_editingFinished_qled_step_size()
def close(self, ignore_exceptions=False): """Cancel all pending serial operations and close the serial port. """ if self.ser is not None: try: self.ser.cancel_read() except: pass try: self.ser.cancel_write() except: pass try: self.ser.close() except Exception as err: if ignore_exceptions: pass else: pft(err, 3) sys.exit(0) self.is_alive = False