def __init__(self, DAQ_update_interval_ms, DAQ_function_to_run_each_update=None, DAQ_critical_not_alive_count=1, DAQ_timer_type=QtCore.Qt.CoarseTimer, DAQ_trigger_by=DAQ_trigger.INTERNAL_TIMER, DEBUG=False): super().__init__(None) self.DEBUG = DEBUG self.DEBUG_color = ANSI.CYAN self.dev = self.outer.dev self.update_interval_ms = DAQ_update_interval_ms self.function_to_run_each_update = DAQ_function_to_run_each_update self.critical_not_alive_count = DAQ_critical_not_alive_count self.timer_type = DAQ_timer_type self.trigger_by = DAQ_trigger_by if self.trigger_by == DAQ_trigger.EXTERNAL_WAKE_UP_CALL: self.qwc = QtCore.QWaitCondition() self.mutex_wait = QtCore.QMutex() self.running = True self.calc_DAQ_rate_every_N_iter = max( round(1e3 / self.update_interval_ms), 1) self.prev_tick_DAQ_update = 0 self.prev_tick_DAQ_rate = 0 if self.DEBUG: dprint( "Worker_DAQ %s init: thread %s" % (self.dev.name, curThreadName()), self.DEBUG_color)
def __init__(self, DAQ_update_interval_ms, DAQ_function_to_run_each_update=None, DAQ_critical_not_alive_count=3, DAQ_timer_type=QtCore.Qt.CoarseTimer, DEBUG=False): super().__init__(None) self.DEBUG = DEBUG self.DEBUG_color = ANSI.CYAN self.dev = self.outer.dev self.ard1 = self.outer.ard1 self.ard2 = self.outer.ard2 self.update_interval_ms = DAQ_update_interval_ms self.function_to_run_each_update = DAQ_function_to_run_each_update self.critical_not_alive_count = DAQ_critical_not_alive_count self.timer_type = DAQ_timer_type self.calc_DAQ_rate_every_N_iter = round(1e3 / self.update_interval_ms) self.prev_tick_DAQ_update = 0 self.prev_tick_DAQ_rate = 0 if self.DEBUG: dprint( "Worker_DAQ %s init: thread %s" % (self.dev.name, curThreadName()), self.DEBUG_color)
def run(self): if self.DEBUG: dprint( "Worker_DAQ %s run : thread %s" % (self.dev.name, curThreadName()), self.DEBUG_color) self.timer = QtCore.QTimer() self.timer.setInterval(self.update_interval_ms) self.timer.timeout.connect(self.update) self.timer.setTimerType(self.timer_type) self.timer.start()
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 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 update_chart(): if DEBUG: tick = QDateTime.currentDateTime() window.CH_1.update_curve() #if self.qpbt_wave_sawtooth.isChecked() == True: # print("hej") window.CH_yaw.update_curve() window.CH_pitch.update_curve() window.CH_roll.update_curve() if DEBUG: tack = QDateTime.currentDateTime() dprint(" update_curve done in %d ms" % tick.msecsTo(tack))
def run(self): if self.DEBUG: dprint( "Worker_DAQ %s run : thread %s" % (self.dev.name, curThreadName()), self.DEBUG_color) # INTERNAL TIMER if self.trigger_by == DAQ_trigger.INTERNAL_TIMER: self.timer = QtCore.QTimer() self.timer.setInterval(self.update_interval_ms) self.timer.timeout.connect(self.update) self.timer.setTimerType(self.timer_type) self.timer.start() # EXTERNAL WAKE UP elif self.trigger_by == DAQ_trigger.EXTERNAL_WAKE_UP_CALL: while self.running: locker_wait = QtCore.QMutexLocker(self.mutex_wait) if self.DEBUG: dprint( "Worker_DAQ %s: waiting for trigger" % self.dev.name, self.DEBUG_color) self.qwc.wait(self.mutex_wait) self.update() locker_wait.unlock() if self.DEBUG: dprint("Worker_DAQ %s: done running" % self.dev.name, self.DEBUG_color)
def __init__(self, DEBUG=False): super().__init__(None) self.DEBUG = DEBUG self.DEBUG_color = ANSI.YELLOW self.dev = self.outer.dev self.ard1 = self.outer.ard1 self.ard2 = self.outer.ard2 self.running = True self.mutex = QtCore.QMutex() self.qwc = QtCore.QWaitCondition() # Use a 'sentinel' value to signal the start and end of the queue # to ensure proper multithreaded operation. self.sentinel = None self.queue = queue.Queue() self.queue.put(self.sentinel) if self.DEBUG: dprint( "Worker_send %s init: thread %s" % (self.dev.name, curThreadName()), self.DEBUG_color)
def initialize(self, current_input, current_output): """ Does all the things that need to happen to ensure a bumpless transfer from manual to automatic mode. """ self.iTerm = current_output self.last_input = current_input if DEBUG: dprint("PID init") if (self.iTerm < self.output_limit_min): dprint("@PID init: iTerm < output_limit_min: integral windup") elif (self.iTerm > self.output_limit_max): dprint("@PID init: iTerm > output_limit_max: integral windup") self.iTerm = np.clip(self.iTerm, self.output_limit_min, self.output_limit_max)
def trigger_update_psus(): if DEBUG: dprint("timer_psus: wake all DAQ") for psu_pyqt in psus_pyqt: psu_pyqt.worker_DAQ.wake_up()
def update(self): self.outer.DAQ_update_counter += 1 locker1 = QtCore.QMutexLocker(self.ard1.mutex) locker2 = QtCore.QMutexLocker(self.ard2.mutex) if self.DEBUG: dprint( "Worker_DAQ %s: iter %i" % (self.dev.name, self.outer.DAQ_update_counter), self.DEBUG_color) # Keep track of the obtained DAQ update interval now = QtCore.QDateTime.currentMSecsSinceEpoch() self.outer.obtained_DAQ_update_interval_ms = ( now - self.prev_tick_DAQ_update) self.prev_tick_DAQ_update = now # Keep track of the obtained DAQ rate # Start at iteration 5 to ensure we have stabilized if self.outer.DAQ_update_counter == 5: self.prev_tick_DAQ_rate = now elif (self.outer.DAQ_update_counter % self.calc_DAQ_rate_every_N_iter == 5): self.outer.obtained_DAQ_rate_Hz = ( self.calc_DAQ_rate_every_N_iter / (now - self.prev_tick_DAQ_rate) * 1e3) self.prev_tick_DAQ_rate = now # Check the alive counters if (self.outer.DAQ_ard1_not_alive_counter >= self.critical_not_alive_count): dprint("\nWorker_DAQ determined Arduino '%s' is not alive." % self.ard1.name) self.ard1.is_alive = False locker1.unlock() locker2.unlock() self.timer.stop() self.outer.signal_DAQ_updated.emit() self.outer.signal_connection_lost.emit() return if (self.outer.DAQ_ard2_not_alive_counter >= self.critical_not_alive_count): dprint("\nWorker_DAQ determined Arduino '%s' is not alive." % self.ard2.name) self.ard2.is_alive = False locker1.unlock() locker2.unlock() self.timer.stop() self.outer.signal_DAQ_updated.emit() self.outer.signal_connection_lost.emit() return # ------------------------ # External code # ------------------------ if not (self.function_to_run_each_update is None): [success1, success2] = self.function_to_run_each_update() if not success1: self.outer.DAQ_ard1_not_alive_counter += 1 if not success2: self.outer.DAQ_ard2_not_alive_counter += 1 # ------------------------ # End external code # ------------------------ locker1.unlock() locker2.unlock() if self.DEBUG: dprint("Worker_DAQ %s: unlocked" % self.dev.name, self.DEBUG_color) self.outer.signal_DAQ_updated.emit()
def compute(self, current_input): """ Compute new PID output. This function should be called repeatedly, preferably at a fixed time interval. Returns True when the output is computed, false when nothing has been done. """ now = time.time() # [s] time_step = (now - self.last_time) if ((not self.in_auto) or np.isnan(self.setpoint)): self.last_time = now; return False _input = current_input; error = self.setpoint - _input; # Proportional term self.pTerm = self.kp * error # Integral term #self.iTerm = self.iTerm + (self.ki * error) self.iTerm = self.iTerm + (self.ki * time_step * error) if DEBUG: if (self.iTerm < self.output_limit_min): dprint("iTerm < output_limit_min: integral windup") elif (self.iTerm > self.output_limit_max): dprint("iTerm > output_limit_max: integral windup") # Prevent integral windup self.iTerm = np.clip(self.iTerm, self.output_limit_min, self.output_limit_max) # Derivative term # Prevent derivative kick: really good to do! #self.dTerm = -self.kd * (_input - self.last_input) self.dTerm = -self.kd / time_step * (_input - self.last_input) # Compute PID Output self.output = self.pTerm + self.iTerm + self.dTerm if DEBUG: dprint("%i" % (time_step * 1000)) dprint("%.1f %.1f %.1f" % (self.pTerm, self.iTerm, self.dTerm)) dprint((" "*14 + "%.2f") % self.output) if (self.output < self.output_limit_min): dprint("output < output_limit_min: output clamped") elif (self.output > self.output_limit_max): dprint("output > output_limit_max: output clamped") # Clamp the output to its limits self.output = np.clip(self.output, self.output_limit_min, self.output_limit_max) # Remember some variables for next time self.last_input = _input self.last_time = now; return True
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)
def update(self): locker = QtCore.QMutexLocker(self.dev.mutex) self.outer.DAQ_update_counter += 1 if self.DEBUG: dprint( "Worker_DAQ %s: lock %i" % (self.dev.name, self.outer.DAQ_update_counter), self.DEBUG_color) # Keep track of the obtained DAQ update interval now = QtCore.QDateTime.currentMSecsSinceEpoch() if self.outer.DAQ_update_counter > 1: self.outer.obtained_DAQ_update_interval_ms = ( now - self.prev_tick_DAQ_update) self.prev_tick_DAQ_update = now # Keep track of the obtained DAQ rate # Start at iteration 5 to ensure we have stabilized if self.outer.DAQ_update_counter == 5: self.prev_tick_DAQ_rate = now elif (self.outer.DAQ_update_counter % self.calc_DAQ_rate_every_N_iter == 5): self.outer.obtained_DAQ_rate_Hz = ( self.calc_DAQ_rate_every_N_iter / (now - self.prev_tick_DAQ_rate) * 1e3) self.prev_tick_DAQ_rate = now # Check the not alive counter if (self.outer.DAQ_not_alive_counter >= self.critical_not_alive_count): dprint("\nWorker_DAQ %s: Determined device is not alive " "anymore." % self.dev.name) self.dev.is_alive = False locker.unlock() if self.trigger_by == DAQ_trigger.INTERNAL_TIMER: self.timer.stop() elif self.trigger_by == DAQ_trigger.EXTERNAL_WAKE_UP_CALL: self.stop() self.outer.signal_DAQ_updated.emit() self.outer.signal_connection_lost.emit() return # ---------------------------------- # User-supplied DAQ function # ---------------------------------- if not (self.function_to_run_each_update is None): if not (self.function_to_run_each_update()): self.outer.DAQ_not_alive_counter += 1 # ---------------------------------- # End user-supplied DAQ function # ---------------------------------- if self.DEBUG: dprint("Worker_DAQ %s: unlocked" % self.dev.name, self.DEBUG_color) locker.unlock() self.outer.signal_DAQ_updated.emit()
def DAQ_update(self): DEBUG_local = False if DEBUG_local: tick = get_tick() # Clear input and output buffers of the device. Seems to resolve # intermittent communication time-outs. self.dev.device.clear() # Finish all operations at the device first if not self.dev.wait_for_OPC(): return False if not self.dev.query_V_meas(): return False if not self.dev.query_I_meas(): return False # -------------------- # Heater power PID # -------------------- # PID controllers work best when the process and control variables have # a linear relationship. # Here: # Process var: V (voltage) # Control var: P (power) # Relation : P = R / V^2 # # Hence, we transform P into P_star # Control var: P_star = sqrt(P) # Relation : P_star = sqrt(R) / V # When we assume R remains constant (which is not the case as the # resistance is a function of the heater temperature, but the dependence # is expected to be insignificant in our small temperature range of 20 # to 100 deg C), we now have linearized the PID feedback relation. self.dev.PID_power.set_mode( (self.dev.state.ENA_output and self.dev.state.ENA_PID), self.dev.state.P_meas, self.dev.state.V_source) self.dev.PID_power.setpoint = np.sqrt(self.dev.state.P_source) if self.dev.PID_power.compute(np.sqrt(self.dev.state.P_meas)): # New PID output got computed -> send new voltage to PSU if self.dev.PID_power.output < 1: # PSU does not regulate well below 1 V, hence clamp to 0 self.dev.PID_power.output = 0 if not self.dev.set_V_source(self.dev.PID_power.output): return False # Wait for the set_V_source operation to finish. # Takes ~ 300 ms to complete with wait_for_OPC. if not self.dev.wait_for_OPC(): return False if not self.dev.query_ENA_OCP(): return False if not self.dev.query_status_OC(): return False if not self.dev.query_status_QC(): return False if not self.dev.query_ENA_output(): return False # Explicitly force the output state to off when the output got disabled # on a hardware level by a triggered protection or fault. if self.dev.state.ENA_output & ( self.dev.state.status_QC_OV | self.dev.state.status_QC_OC | self.dev.state.status_QC_PF | self.dev.state.status_QC_OT | self.dev.state.status_QC_INH): self.dev.state.ENA_output = False self.dev.set_ENA_output(False) if DEBUG_local: tock = get_tick() dprint("%s: done in %i" % (self.dev.name, tock - tick)) # Check if there are errors in the device queue and retrieve all # if any and append these to 'dev.state.all_errors'. if DEBUG_local: dprint("%s: query errors" % self.dev.name) tick = get_tick() self.dev.query_all_errors_in_queue() if DEBUG_local: tock = get_tick() dprint("%s: stb done in %i" % (self.dev.name, tock - tick)) return True
def DAQ_update(self): tick = get_tick() # Clear input and output buffers of the device. Seems to resolve # intermittent communication time-outs. self.dev.device.clear() success = True if self.is_MUX_scanning: success &= self.dev.init_scan() # Init scan if success: self.dev.wait_for_OPC() # Wait for OPC if self.worker_DAQ.DEBUG: tock = get_tick() dprint("opc? in: %i" % (tock - tick)) tick = tock success &= self.dev.fetch_scan() # Fetch scan if self.worker_DAQ.DEBUG: tock = get_tick() dprint("fetc in: %i" % (tock - tick)) tick = tock if success: self.dev.wait_for_OPC() # Wait for OPC if self.worker_DAQ.DEBUG: tock = get_tick() dprint("opc? in: %i" % (tock - tick)) tick = tock if success: # Do not throw additional timeout exceptions when .init_scan() # might have already failed. Hence this check for no success. self.dev.query_all_errors_in_queue() # Query errors if self.worker_DAQ.DEBUG: tock = get_tick() dprint("err? in: %i" % (tock - tick)) tick = tock # The next statement seems to trigger timeout, but very # intermittently (~once per 20 minutes). After this timeout, # everything times out. #self.dev.wait_for_OPC() #if self.worker_DAQ.DEBUG: # tock = get_tick() # dprint("opc? in: %i" % (tock - tick)) # tick = tock # NOTE: Another work-around to intermittent time-outs might # be sending self.dev.clear() every iter to clear the input and # output buffers. This is now done at the start of this function. # Optional user-supplied function to run. You can use this function to, # e.g., parse out the scan readings into separate variables and # post-process this data or log it. if not (self.DAQ_postprocess_MUX_scan_function is None): self.DAQ_postprocess_MUX_scan_function() if self.worker_DAQ.DEBUG: tock = get_tick() dprint("extf in: %i" % (tock - tick)) tick = tock return success