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)
Beispiel #9
0
    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)
Beispiel #10
0
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()
Beispiel #12
0
    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