def test_Worker_DAQ___rate():
    print_title("Worker_DAQ - INTERNAL_TIMER - DAQ rate")

    def DAQ_function():
        # Must return True when successful, False otherwise
        reply = dev.fake_query_1()
        dprint(" " * 50 + "%.1f Hz" % qdev.obtained_DAQ_rate_Hz)
        return reply[-4:] == "0101"

    app = create_QApplication()
    dev = FakeDevice()
    qdev = QDeviceIO(dev)
    # fmt: off
    qdev.create_worker_DAQ(DAQ_trigger=DAQ_TRIGGER.INTERNAL_TIMER,
                           DAQ_function=DAQ_function,
                           DAQ_interval_ms=10,
                           critical_not_alive_count=1,
                           debug=DEBUG)
    # fmt: on
    assert qdev.start() == True

    # Simulate device runtime
    start_time = time.perf_counter()
    while time.perf_counter() - start_time < 1.51:
        app.processEvents()
        time.sleep(0.001)  # Do not hog the CPU

    tprint("About to quit")
    app.processEvents()
    assert qdev.quit() == True
    app.quit()

    assert 9 <= qdev.obtained_DAQ_interval_ms <= 11
    assert 99 <= qdev.obtained_DAQ_rate_Hz <= 101
def test_Worker_DAQ___quit_without_start():
    print_title("Worker_DAQ - quit without start")

    app = create_QApplication()
    qdev = QDeviceIO(FakeDevice())
    qdev.create_worker_DAQ()

    tprint("About to quit")
    app.processEvents()
    assert qdev.quit() == True
    app.quit()
def test_Worker_DAQ___no_device_attached():
    print_title("Worker_DAQ - no device attached")
    import pytest

    qdev = QDeviceIO()

    with pytest.raises(SystemExit) as pytest_wrapped_e:
        qdev.create_worker_DAQ()
    assert pytest_wrapped_e.type == SystemExit
    dprint("Exit code: %i" % pytest_wrapped_e.value.code)
    assert pytest_wrapped_e.value.code == 99
def test_Worker_DAQ___CONTINUOUS(start_alive=True):
    print_title("Worker_DAQ - CONTINUOUS" +
                ("" if start_alive else " - start dead"))

    def DAQ_function():
        # Must return True when successful, False otherwise
        time.sleep(0.1)  # Simulate blocking processing time on the device
        reply = dev.fake_query_1()
        return reply[-4:] == "0101"

    app = create_QApplication()
    dev = FakeDevice(start_alive=start_alive)
    qdev = QDeviceIO(dev)
    # fmt: off
    qdev.create_worker_DAQ(
        DAQ_trigger=DAQ_TRIGGER.CONTINUOUS,
        DAQ_function=DAQ_function,
        critical_not_alive_count=1,
        debug=DEBUG,
    )
    # fmt: on

    qdev.signal_DAQ_updated.connect(process_DAQ_updated)
    qdev.signal_DAQ_paused.connect(process_DAQ_paused)

    assert qdev.start() == start_alive

    # Immediately fire a call to test if the worker is ready for it
    qdev.unpause_DAQ()

    # Simulate device runtime
    start_time = time.perf_counter()
    QtCore.QTimer.singleShot(300, qdev.pause_DAQ)
    QtCore.QTimer.singleShot(600, qdev.unpause_DAQ)
    QtCore.QTimer.singleShot(900, qdev.pause_DAQ)
    QtCore.QTimer.singleShot(1200, qdev.unpause_DAQ)
    while time.perf_counter() - start_time < 1.6:
        app.processEvents()
        if dev.count_commands == 12:
            break
        time.sleep(0.001)  # Do not hog the CPU

    tprint("About to quit")
    app.processEvents()
    assert qdev.quit() == True
    app.quit()

    if start_alive:
        assert dev.count_commands >= 10
        assert dev.count_replies >= 10
        assert (cnt_DAQ_updated >= 9
                )  # Last signal is not always received before thread is quit
        assert cnt_DAQ_paused == 3
def test_Worker_DAQ___lose_connection():
    print_title("Worker_DAQ - INTERNAL_TIMER - lose connection")

    def DAQ_function():
        # Must return True when successful, False otherwise
        if qdev.update_counter_DAQ == 10:
            dev.is_alive = False

        reply = dev.fake_query_1()
        return reply[-4:] == "0101"

    # NOTE: The global 'go' mechanism used here is a quick and dirty way to
    # pytest. In production, it should be implemented by an boolean external
    # class member.
    global go
    go = True

    @QtCore.pyqtSlot()
    def process_connection_lost():
        tprint("---> received: connection_lost")
        global go
        go = False

    app = create_QApplication()
    dev = FakeDevice()

    # Forcefully remove members as extra test
    del dev.name
    del dev.is_alive

    qdev = QDeviceIO(dev)
    # fmt: off
    qdev.create_worker_DAQ(DAQ_trigger=DAQ_TRIGGER.INTERNAL_TIMER,
                           DAQ_function=DAQ_function,
                           DAQ_interval_ms=20,
                           critical_not_alive_count=3,
                           debug=DEBUG)
    # fmt: on
    qdev.create_worker_jobs(debug=DEBUG)
    qdev.signal_connection_lost.connect(process_connection_lost)
    assert qdev.start() == True

    # Simulate device runtime
    while go:
        app.processEvents()
        time.sleep(0.001)  # Do not hog the CPU

    tprint("About to quit")
    app.processEvents()
    assert qdev.quit() == True
    assert qdev.quit() == True  # Twice, to check for msg 'already closed'.
    app.quit()
def test_Worker_DAQ___SINGLE_SHOT_WAKE_UP(start_alive=True):
    print_title("Worker_DAQ - SINGLE_SHOT_WAKE_UP" +
                ("" if start_alive else " - start dead"))

    def DAQ_function():
        # Must return True when successful, False otherwise
        reply = dev.fake_query_1()
        return reply[-4:] == "0101"

    app = create_QApplication()
    dev = FakeDevice(start_alive=start_alive)
    qdev = QDeviceIO(dev)
    # fmt: off
    qdev.create_worker_DAQ(DAQ_trigger=DAQ_TRIGGER.SINGLE_SHOT_WAKE_UP,
                           DAQ_function=DAQ_function,
                           critical_not_alive_count=1,
                           debug=DEBUG)
    # fmt: on
    qdev.signal_DAQ_updated.connect(process_DAQ_updated)
    assert qdev.start() == start_alive

    # Immediately fire a call to test if the worker is ready for it
    qdev.wake_up_DAQ()

    # Simulate device runtime
    start_time = time.perf_counter()
    QtCore.QTimer.singleShot(300, qdev.wake_up_DAQ)
    QtCore.QTimer.singleShot(600, qdev.wake_up_DAQ)
    while time.perf_counter() - start_time < 1:
        app.processEvents()
        time.sleep(0.001)  # Do not hog the CPU

    tprint("About to quit")
    app.processEvents()
    assert qdev.quit() == True
    app.quit()

    if start_alive:
        assert dev.count_commands == 3
        assert dev.count_replies == 3
        assert cnt_DAQ_updated == 3
def test_Worker_DAQ___INTERNAL_TIMER(start_alive=True):
    print_title("Worker_DAQ - INTERNAL_TIMER" +
                ("" if start_alive else " - start dead"))

    def DAQ_function():
        # Must return True when successful, False otherwise
        reply = dev.fake_query_1()
        return reply[-4:] == "0101"

    app = create_QApplication()
    dev = FakeDevice(start_alive=start_alive)
    qdev = QDeviceIO(dev)
    # fmt: off
    qdev.create_worker_DAQ(DAQ_trigger=DAQ_TRIGGER.INTERNAL_TIMER,
                           DAQ_function=DAQ_function,
                           DAQ_interval_ms=100,
                           critical_not_alive_count=10,
                           debug=DEBUG)
    # fmt: on
    qdev.signal_DAQ_updated.connect(process_DAQ_updated)
    assert qdev.start() == start_alive

    # Simulate device runtime
    start_time = time.perf_counter()
    while time.perf_counter() - start_time < 1:
        app.processEvents()
        if dev.count_commands == 3:
            break
        time.sleep(0.001)  # Do not hog the CPU

    tprint("About to quit")
    app.processEvents()
    assert qdev.quit() == True
    app.quit()

    if start_alive:
        assert dev.count_commands >= 3
        assert dev.count_replies >= 3
        assert (cnt_DAQ_updated >= 2
                )  # Last signal is not always received before thread is quit
def test_Worker_DAQ___ILLEGAL_DAQ_FUNCTION():
    print_title("Worker_DAQ - ILLEGAL_DAQ_FUNCTION")

    def DAQ_function():
        # Must return True when successful, False otherwise

        if qdev.update_counter_DAQ == 2:
            0 / 0
        else:
            reply = dev.fake_query_1()
            return reply[-4:] == "0101"

    app = create_QApplication()
    dev = FakeDevice()
    qdev = QDeviceIO(dev)
    # fmt: off
    qdev.create_worker_DAQ(DAQ_trigger=DAQ_TRIGGER.INTERNAL_TIMER,
                           DAQ_function=DAQ_function,
                           DAQ_interval_ms=100,
                           debug=DEBUG)
    # fmt: on
    qdev.signal_DAQ_updated.connect(process_DAQ_updated)
    assert qdev.start() == True

    # Simulate device runtime
    start_time = time.perf_counter()
    while time.perf_counter() - start_time < 1:
        app.processEvents()
        if dev.count_commands == 3:
            break
        time.sleep(0.001)  # Do not hog the CPU

    tprint("About to quit")
    app.processEvents()
    assert qdev.quit() == True
    app.quit()
        lambda filepath: window.qpbt_record.setText("Recording to file: %s" %
                                                    filepath))
    log.signal_recording_stopped.connect(
        lambda: window.qpbt_record.setText("Click to start recording to file"))

    # --------------------------------------------------------------------------
    #   Set up multithreaded communication with the Arduino
    # --------------------------------------------------------------------------

    # Create QDeviceIO
    qdev_ard = QDeviceIO(ard)

    # Create workers
    qdev_ard.create_worker_DAQ(
        DAQ_function=DAQ_function,
        DAQ_interval_ms=DAQ_INTERVAL_MS,
        critical_not_alive_count=1,
        debug=DEBUG,
    )
    qdev_ard.create_worker_jobs(debug=DEBUG)

    # Connect signals to slots
    qdev_ard.signal_DAQ_updated.connect(window.update_GUI)
    qdev_ard.signal_connection_lost.connect(notify_connection_lost)

    # Hack. TODO: Implement start/stop in `QDeviceIO`
    def start_stop():
        qdev_ard.worker_DAQ.DAQ_function = (
            DAQ_function if window.qpbt_running.isChecked() else None)

    window.qpbt_running.clicked.connect(start_stop)
        def __init__(self, qdev):
            self.qdev = qdev
            self.name = "Sync"

        def tick(self):
            self.qdev.wake_up_DAQ()
            return True

    sync = MasterSync(qdev_ard)
    qdev_sync = QDeviceIO(sync)

    # Create workers
    # fmt: off
    qdev_ard.create_worker_DAQ(
        DAQ_trigger     = DAQ_TRIGGER.SINGLE_SHOT_WAKE_UP,
        DAQ_function    = DAQ_function,
        debug           = DEBUG,
    )
    qdev_sync.create_worker_DAQ(
        DAQ_trigger     = DAQ_TRIGGER.INTERNAL_TIMER,
        DAQ_function    = sync.tick,
        DAQ_interval_ms = DAQ_INTERVAL_MS,
        critical_not_alive_count = 1,
        debug           = DEBUG,
    )
    # fmt: on

    # Connect signals to slots
    qdev_ard.signal_DAQ_updated.connect(update_terminal)
    qdev_ard.signal_connection_lost.connect(notify_connection_lost)
#   Main
# ------------------------------------------------------------------------------

if __name__ == "__main__":
    app = QtWid.QApplication(sys.argv)
    app.aboutToQuit.connect(about_to_quit)

    window = MainWindow()

    # Fake a device that generates data at a sampling rate of `Fs` Hz. The
    # data gets generated and transferred to our HistoryChartCurve-instance from
    # out of a separate thread. This is taken care of by QDeviceIO.
    class FakeDevice:
        def __init__(self):
            self.name = "FakeDevice"
            self.is_alive = True

    qdev = QDeviceIO(dev=FakeDevice())
    qdev.create_worker_DAQ(DAQ_interval_ms=WORKER_DAQ_INTERVAL_MS,
                           DAQ_function=DAQ_function)
    qdev.signal_DAQ_updated.connect(window.update_GUI)
    qdev.start()

    # Chart refresh timer
    timer_chart = QtCore.QTimer(timerType=QtCore.Qt.PreciseTimer)
    timer_chart.timeout.connect(window.update_charts)
    timer_chart.start(CHART_DRAW_INTERVAL_MS)

    window.show()
    sys.exit(app.exec_())