def start_thread_worker_send(self, priority=QtCore.QThread.InheritPriority): """Start running the event loop of the worker thread. Args: priority (PyQt5.QtCore.QThread.Priority, optional, default= QtCore.QThread.InheritPriority): By default, the 'worker_send' thread runs in the operating system at the same thread priority as the main/GUI thread. You can change to higher priority by setting 'priority' to, e.g., 'QtCore.QThread.TimeCriticalPriority'. Be aware that this is resource heavy, so use sparingly. Returns True when successful, False otherwise. """ if hasattr(self, 'thread_send'): if self.thread_send is not None: self.thread_send.start(priority) return True else: print("Worker_send %s: Can't start thread because device is " "not alive." % self.dev.name) return False else: pft("Worker_send %s: Can't start thread because it does not exist. " "Did you forget to call 'create_worker_send' first?" % self.dev.name) return False
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 alt_process_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 query_ascii_values(self, msg_str="", separator='\t'): """Send a message to the serial device and subsequently read the reply. Expects a reply from the Arduino in the form of an ASCII string containing a list of numeric values. These values will be parsed into a list of floats and returned. Returns: success (bool): True if successful, False otherwise. ans_floats (list): Reply received from the device and parsed into a list of floats. [None] if unsuccessful. """ [success, ans_str] = self.query(msg_str) if success and not (ans_str == ''): try: ans_floats = list(map(float, ans_str.split(separator))) except ValueError as err: # Print error and struggle on pft(err, 3) except Exception as err: pft(err, 3) sys.exit(1) else: return [True, ans_floats] return [False, []]
def create_log(self, start_time, path_log: Path, mode='a'): """Open new log file and keep file handle open. Args: start_time: Timestamp of the start of recording, usefull to keep track of the elapsed time while recording. path_log [pathlib.Path]: Location of the file to write to. mode: Mode in which the file is openend, see 'open()' for more details. Defaults to 'a'. Most common options: 'w': Open for writing, truncating the file first 'a': Open for writing, appending to the end of the file if it exists Returns: True if successful, False otherwise. """ self.path_log = path_log self.start_time = start_time self.starting = False self.stopping = False try: self.f_log = open(path_log, mode) except Exception as err: pft(err, 3) self.is_recording = False return False else: self.is_recording = True return True
def attach_device(self, dev): """Attach a reference to a 'device' instance with I/O methods. """ if type(self.dev) == self.NoAttachedDevice: self.dev = dev self.dev.mutex = QtCore.QMutex() else: pft("Device can be attached only once. Already attached to '%s'." % self.dev.name)
def query(self, msg_str, timeout_warning_style=1): """Send a message to the serial device and subsequently read the reply. Args: msg_str (str): Message to be sent to the serial device. timeout_warning_style (int, optional): Work-around for the Serial library not throwing an exception when read has timed out. 1 (default): Will print a traceback error message on screen and continue. 2: Will raise the exception again. Returns: success (bool): True if successful, False otherwise. ans_str (str): Reply received from the device. [None] if unsuccessful. """ success = False ans_str = None if self.write(msg_str, timeout_warning_style): try: ans_bytes = self.ser.read_until(self.read_term_char.encode()) except (serial.SerialTimeoutException, serial.SerialException) as err: # Note though: The Serial library does not throw an # exception when it actually times out! We will check for # zero received bytes as indication for timeout, later. pft(err, 3) except Exception as err: pft(err, 3) sys.exit(1) else: if (len(ans_bytes) == 0): # Received 0 bytes, probably due to a timeout. if timeout_warning_style == 1: pft("Received 0 bytes. Read probably timed out.", 3) elif timeout_warning_style == 2: raise (serial.SerialTimeoutException) else: try: ans_str = ans_bytes.decode('utf8').strip() except UnicodeDecodeError as err: # Print error and struggle on pft(err, 3) except Exception as err: pft(err, 3) sys.exit(1) else: success = True return [success, ans_str]
def write(self, data): """ Returns: True if successful, False otherwise. """ try: self.f_log.write(data) except Exception as err: pft(err, 3) return False else: return True
def my_Arduino_DAQ_update(): # Date-time keeping global cur_date_time, str_cur_date, str_cur_time cur_date_time = QDateTime.currentDateTime() str_cur_date = cur_date_time.toString("dd-MM-yyyy") str_cur_time = cur_date_time.toString("HH:mm:ss") # Query the Arduino for its state [success, tmp_state] = ard.query_ascii_values("?", separator='\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, state.yaw, state.pitch, state.roll] = tmp_state except Exception as err: pft(err, 3) dprint("'%s' reports IOError @ %s %s" % (ard.name, str_cur_date, str_cur_time)) return False # Use Arduino time or PC time? # Arduino time is more accurate, but rolls over ~49 days for a 32 bit timer. use_PC_time = True if use_PC_time: state.time = cur_date_time.toMSecsSinceEpoch() # Add readings to chart histories window.CH_1.add_new_reading(state.time, state.reading_1 - state.tare_value) window.CH_yaw.add_new_reading(state.time, state.yaw) window.CH_pitch.add_new_reading(state.time, state.pitch) window.CH_roll.add_new_reading(state.time, state.roll) # Logging to file if file_logger.starting: fn_log = cur_date_time.toString("yyMMdd_HHmmss") + ".txt" if file_logger.create_log(state.time, fn_log, mode='w'): file_logger.signal_set_recording_text.emit("Recording to file: " + fn_log) file_logger.write("elapsed [s]\treading_1\n") if file_logger.stopping: file_logger.signal_set_recording_text.emit( "Click to start recording to file") file_logger.close_log() if file_logger.is_recording: log_elapsed_time = (state.time - file_logger.start_time) / 1e3 # [sec] file_logger.write("%.3f\t%.4f\n" % (log_elapsed_time, state.reading_1)) return True
def alt_process_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) self.dev.wait_for_OPC() except Exception as err: pft(err)
def run(self): if self.DEBUG: dprint( "Worker_send %s run : thread %s" % (self.dev.name, curThreadName()), self.DEBUG_color) while self.running: locker_worker = QtCore.QMutexLocker(self.mutex) if self.DEBUG: dprint( "Worker_send %s: waiting for trigger" % self.dev.name, self.DEBUG_color) self.qwc.wait(self.mutex) if self.DEBUG: dprint("Worker_send %s: trigger received" % self.dev.name, self.DEBUG_color) """Process all jobs until the queue is empty. We must iterate 2 times because we use a sentinel in a FIFO queue. First iter removes the old sentinel. Second iter processes the remaining queue items and will put back a new sentinel again. """ for i in range(2): for job in iter(self.queue.get_nowait, self.sentinel): ard = job[0] func = ard.write args = job[1:] if self.DEBUG: dprint( "Worker_send %s: %s %s" % (ard.name, func.__name__, args), self.DEBUG_color) # Send I/O operation to the device locker = QtCore.QMutexLocker(ard.mutex) try: func(*args) except Exception as err: pft(err) locker.unlock() # Put sentinel back in self.queue.put(self.sentinel) locker_worker.unlock() if self.DEBUG: dprint("Worker_send %s: done running" % self.dev.name, self.DEBUG_color)
def alt_process_jobs_function(self, func, args): if (func == "signal_GUI_alarm_values_update"): # Special instruction self.signal_GUI_alarm_values_update.emit() elif (func == "signal_GUI_PID_values_update"): # Special instruction self.signal_GUI_PID_values_update.emit() else: # Default job processing: # Send I/O operation to the device try: func(*args) except Exception as err: pft(err)
def query(self, msg_str): """Send a command to the serial device and subsequently read the reply. Args: msg_str (str): Message to be sent to the serial device. Returns: success (bool): True if successful, False otherwise. ans_str (str) : Reply received from the device. [None] if unsuccessful. """ success = False ans_str = None if not self.is_alive: pft("Device is not connected yet or already closed.", 3) else: try: # Send command string to the device as bytes self.ser.write((msg_str + TERM_CHAR).encode()) except (serial.SerialTimeoutException, serial.SerialException) as err: # Print error and struggle on pft(err, 3) except Exception as err: pft(err, 3) sys.exit(0) else: try: ans_bytes = self.ser.read_until(TERM_CHAR.encode()) except (serial.SerialTimeoutException, serial.SerialException) as err: pft(err, 3) except Exception as err: pft(err, 3) sys.exit(0) else: ans_str = ans_bytes.decode('utf8').strip() if ans_str[0] == '>': # Successfull operation without meaningfull reply success = True elif ans_str[0] == '!': # Error reply print("COMPAX3 COMMUNICATION ERROR: " + ans_str) else: # Successfull operation with meaningfull reply success = True return [success, ans_str]
def query(self, msg_str): """Send a command to the serial device and subsequently read the reply. Args: msg_str (str): Message to be sent to the serial device. Returns: success (bool): True if successful, False otherwise. ans_str (str): Reply received from the device. None if unsuccessful. """ success = False ans_str = None if not self.is_alive: print("ERROR: Device is not connected yet or already closed.") else: try: # Send command string to the device as bytes self.ser.write(msg_str.replace(' ', '').encode()) except (serial.SerialTimeoutException, serial.SerialException) as err: # Print error and struggle on pft(err, 3) except: raise sys.exit(0) else: try: # Read all bytes in the line that is terminated with a # newline character or until time-out has occured ans_bytes = self.ser.readline() except (serial.SerialTimeoutException, serial.SerialException) as err: pft(err, 3) except: raise sys.exit(0) else: # Convert bytes into string and remove termination chars and # spaces ans_str = ans_bytes.decode().strip() success = True return [success, ans_str]
def alt_process_jobs_function(self, func, args): # Send I/O operation to the device try: func(*args) except Exception as err: 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 write(self, msg_str, timeout_warning_style=1): """Send a message to the serial device. Args: msg_str (str): String to be sent to the serial device. timeout_warning_style (int, optional): 1 (default): Will print a traceback error message on screen and continue. 2: Will raise the exception again. Returns: True if successful, False otherwise. """ success = False if not self.is_alive: pft("Device is not connected yet or already closed.", 3) else: try: self.ser.write((msg_str + self.write_term_char).encode()) except (serial.SerialTimeoutException, serial.SerialException) as err: if timeout_warning_style == 1: pft(err, 3) elif timeout_warning_style == 2: raise (err) except Exception as err: pft(err, 3) sys.exit(1) else: success = True return success
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_traverse=None, trav_vert: Compax3_traverse=None, parent=None, **kwargs): super().__init__(parent, **kwargs) if not (isinstance(trav_horz, Compax3_traverse) or trav_horz is None): pft("Argument 'trav_horz' is of a wrong type.", 3) sys.exit(1) if not (isinstance(trav_vert, Compax3_traverse) 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 query(self, msg_bytes): """Send a command to the serial device and subsequently read the reply. Args: msg_bytes (bytes): Message to be sent to the serial device. Returns: success (bool): True if successful, False otherwise. ans_bytes (bytes): Reply received from the device. [numpy.nan] if unsuccessful. TO DO: force ser.flush after ser.write TO DO: implement ser.readline instead of sleep() & inWaiting """ success = False ans_bytes = np.nan if not self.is_alive: pft("Device is not connected yet or already closed.", 3) return [success, ans_bytes] try: # Send command string to the device as bytes self.ser.write(msg_bytes) except (serial.SerialTimeoutException, serial.SerialException) as err: pft(err, 3) except Exception as err: pft(err, 3) sys.exit(0) else: # Wait for the incoming buffer to fill with the device's complete # reply. Note: The chiller does not use an EOL character to signal # the end of line, hence we can't use ser.readline(). time.sleep(RS232_SLEEP) try: # Read all the bytes waiting in the in-buffer ans_bytes = self.ser.read(self.ser.inWaiting()) #print_as_hex(ans_bytes) # debug info except (serial.SerialTimeoutException, serial.SerialException) as err: pft(err, 3) except Exception as err: pft(err, 3) sys.exit(0) else: # Check for errors reported by a possibly connected ThermoFlex # chiller if (len(ans_bytes) >= 4) and ans_bytes[3] == 0x0f: # Error reported by chiller if ans_bytes[5] == 1: pft("Bad command received by chiller", 3) elif ans_bytes[5] == 2: pft("Bad data received by chiller", 3) elif ans_bytes[5] == 3: pft("Bad checksum received by chiller", 3) else: # We got a reply back from /a/ device, not necessarily a # ThermoFlex chiller. success = True return [success, ans_bytes]
def run(self): if self.DEBUG: dprint( "Worker_send %s run : thread %s" % (self.dev.name, curThreadName()), self.DEBUG_color) while self.running: locker_wait = QtCore.QMutexLocker(self.mutex_wait) if self.DEBUG: dprint( "Worker_send %s: waiting for trigger" % self.dev.name, self.DEBUG_color) self.qwc.wait(self.mutex_wait) locker = QtCore.QMutexLocker(self.dev.mutex) self.update_counter += 1 if self.DEBUG: dprint( "Worker_send %s: lock %i" % (self.dev.name, self.update_counter), self.DEBUG_color) """Process all jobs until the queue is empty. We must iterate 2 times because we use a sentinel in a FIFO queue. First iter removes the old sentinel. Second iter processes the remaining queue items and will put back a new sentinel again. """ for i in range(2): for job in iter(self.queue.get_nowait, self.sentinel): func = job[0] args = job[1:] if self.DEBUG: if type(func) == str: dprint( "Worker_send %s: %s %s" % (self.dev.name, func, args), self.DEBUG_color) else: dprint( "Worker_send %s: %s %s" % (self.dev.name, func.__name__, args), self.DEBUG_color) if self.alt_process_jobs_function is None: # Default job processing: # Send I/O operation to the device try: func(*args) except Exception as err: pft(err) else: # User-supplied job processing self.alt_process_jobs_function(func, args) # Put sentinel back in self.queue.put(self.sentinel) if self.DEBUG: dprint("Worker_send %s: unlocked" % self.dev.name, self.DEBUG_color) locker.unlock() locker_wait.unlock() if self.DEBUG: dprint("Worker_send %s: done running" % self.dev.name, self.DEBUG_color)