diff --git a/examples/main.py b/examples/main.py index 902aa55c54300c74b7bd67c61a41b4e87afd0a17..963bcc6b233562022328cdd957b1f955df4f00ef 100644 --- a/examples/main.py +++ b/examples/main.py @@ -11,6 +11,9 @@ from rich.logging import RichHandler import CaptDeviceControl as CaptDevice + +#logging.disable(logging.INFO) + if __name__ == "__main__": def setup_logging(): @@ -35,7 +38,7 @@ if __name__ == "__main__": model = CaptDevice.Model(conf) - controller = CaptDevice.Controller(model) + controller = CaptDevice.Controller(model, None) window = CaptDevice.View(model, controller) window.show() diff --git a/src/CaptDeviceControl/CaptDeviceConfig.py b/src/CaptDeviceControl/CaptDeviceConfig.py index 273afe6d7326646355ec6e11e2345959f0a80e02..fdc0a46616cf6bad4c4c52ef7f3695641bade73f 100644 --- a/src/CaptDeviceControl/CaptDeviceConfig.py +++ b/src/CaptDeviceControl/CaptDeviceConfig.py @@ -11,7 +11,7 @@ class CaptDeviceConfig(cfg.ConfigNode): def __init__(self) -> None: super().__init__() - self.sample_rate = cfg.Field(50000, friendly_name="Sample rate", + self.sample_rate = cfg.Field(500, friendly_name="Sample rate", description="Sample rate of the device") self.streaming_rate = cfg.Field(500, friendly_name="Streaming rate", description="Streaming rate in Hz (should be below 1kHz)") diff --git a/src/CaptDeviceControl/__init__.py b/src/CaptDeviceControl/__init__.py index 65ab4a9424daabd37b081ffb246d53c7ce4a46a8..eb96e39a703665131ee83f15829b3b6f0cdb3b8b 100644 --- a/src/CaptDeviceControl/__init__.py +++ b/src/CaptDeviceControl/__init__.py @@ -6,6 +6,6 @@ Package Version: """ import sys from .CaptDeviceConfig import CaptDeviceConfig as Config -from .controller.AD2CaptDeviceController import AD2CaptDeviceController as Controller +from .controller.BaseAD2CaptDevice import BaseAD2CaptDevice as Controller from .model.AD2CaptDeviceModel import AD2CaptDeviceModel as Model from .view.AD2CaptDeviceView import ControlWindow as View \ No newline at end of file diff --git a/src/CaptDeviceControl/controller/AD2CaptDeviceController.py b/src/CaptDeviceControl/controller/AD2CaptDeviceController.py deleted file mode 100644 index 24bff829e27a76bb3f409dfb2c98f4404d8358e1..0000000000000000000000000000000000000000 --- a/src/CaptDeviceControl/controller/AD2CaptDeviceController.py +++ /dev/null @@ -1,290 +0,0 @@ -#!/.venv/Scripts/python - -from ctypes import c_int, byref, create_string_buffer, cdll, c_int32, c_uint, c_double - -from CaptDeviceControl.controller.BaseAD2CaptDevice import BaseAD2CaptDevice -from CaptDeviceControl.model.AD2CaptDeviceModel import AD2CaptDeviceModel -#from .controller.BaseAD2CaptDevice import BaseAD2CaptDevice -#from .model.AD2CaptDeviceModel import AD2CaptDeviceModel - -from CaptDeviceControl.constants.dwfconstants import enumfilterUSB, enumfilterType, enumfilterDemo -from CaptDeviceControl.controller.mp_AD2Capture.MPCaptDeviceControl import MPCaptDeviceControl - - -class AD2CaptDeviceController(BaseAD2CaptDevice): - - def __init__(self, ad2capt_model: AD2CaptDeviceModel): - self.dwf = cdll.dwf - super().__init__(ad2capt_model) - - - - # This is required for acquiring the data - - - - def read_hardware_config(self, iDevice): - hw_info_dict = {} - hdwf = c_int() - int0 = c_int() - int1 = c_int() - uint0 = c_uint() - dbl0 = c_double() - dbl1 = c_double() - dbl2 = c_double() - self.dwf.FDwfDeviceConfigOpen(c_int(iDevice), c_int(0), byref(hdwf)) - if hdwf.value == 0: - szerr = create_string_buffer(512) - self.dwf.FDwfGetLastErrorMsg(szerr) - raise Exception(str(szerr.value)) - - self.dwf.FDwfAnalogInChannelCount(hdwf, byref(int0)) - hw_info_dict["analog_in_channels"] = int(int0.value) - - self.dwf.FDwfAnalogIOChannelCount(hdwf, byref(int0)) - hw_info_dict["analog_io_channels"] = int(int0.value) - - self.dwf.FDwfAnalogInBufferSizeInfo(hdwf, 0, byref(int0)) - hw_info_dict["buffer_size"] = int(int0.value) - - self.dwf.FDwfAnalogInBitsInfo(hdwf, byref(int0)) - hw_info_dict["adc_bits"] = int(int0.value) - - self.dwf.FDwfAnalogInChannelRangeInfo(hdwf, byref(dbl0), byref(dbl1), byref(dbl2)) - hw_info_dict["range"] = (int(dbl0.value), int(dbl1.value), int(dbl2.value)) - - self.dwf.FDwfAnalogInChannelOffsetInfo(hdwf, byref(dbl0), byref(dbl1), byref(dbl2)) - hw_info_dict["offset"] = (int(dbl0.value), int(dbl1.value), int(dbl2.value)) - - return hw_info_dict - - - def discover_connected_devices(self): - pass - #self.mpcaptdevicecontrol.discover_connected_devices() - - - - # def _open_device(self, device_index): - # devicename = create_string_buffer(64) - # serialnum = create_string_buffer(16) - # - # self.dwf.FDwfEnumDeviceName(c_int(device_index), devicename) - # self.dwf.FDwfEnumSN(c_int(device_index), serialnum) - # - # self.model.device_name = devicename - # self.model.device_serial_number = serialnum - # # open device - # self.logger.info(f"[{self.pref} Task] Opening device #{device_index}...") - # - # # Opens a device identified by the enumeration index and retrieves a handle. To automatically - # # enumerate all connected devices and open the first discovered device, use index -1. - # self.dwf.FDwfDeviceOpen(c_int(device_index), byref(self.model.hdwf)) - # - # if self.model.hdwf.value == hdwfNone.value: - # szerr = create_string_buffer(512) - # self.dwf.FDwfGetLastErrorMsg(szerr) - # # print(str(szerr.value)) - # self.model.connected = False - # raise Exception(f"Failed to open device: {szerr.value}") - # else: - # self.model.connected = True - # self.get_analog_in_status() - # self.logger.info(f"[{self.pref} Task] Device connected!") - # - # def _setup_acquisition(self): - # # set up acquisition - # self.get_analog_in_status() - # self.logger.debug(f"[{self.pref} Task] Setup for acquisition. Wait 2 seconds for the offset to stabilize.") - # self.dwf.FDwfAnalogInChannelEnableSet(self.model.hdwf, c_int(self.model.analog_in_channel), c_int(1)) - # self.dwf.FDwfAnalogInChannelRangeSet(self.model.hdwf, c_int(self.model.analog_in_channel), c_double(5)) - # self.dwf.FDwfAnalogInAcquisitionModeSet(self.model.hdwf, acqmodeRecord) - # self.dwf.FDwfAnalogInFrequencySet(self.model.hdwf, c_double(self.model.hz_acquisition)) - # self.dwf.FDwfAnalogInRecordLengthSet(self.model.hdwf, 0) # -1 infinite record length - # self.get_analog_in_status() - # # wait at least 2 seconds for the offset to stabilize - # time.sleep(2) - # self.logger.info(f"[{self.pref} Task] Setup for acquisition done.") - # return True - # - # # # ================================================================================================================== - # # # Acquisition - # # # ================================================================================================================== - # # @Slot() - # # def start_capture(self, capture): - # # if not self.model.connected: - # # self.logger.warning(f"[{self.pref} Task] No device connected. Connecting to first device.") - # # self.connect_device(0) - # # return False - # # - # # if capture: - # # self.logger.info(f"[{self.pref} Task] Setting up device for capturing.") - # # self.gen_sine() - # # self._setup_acquisition() - # # # if self._setup_acquisition(): - # # # self.logger.info(f"[{self.pref} Task] Started capturing thread") - # # self.set_ad2_acq_status(True) - # # return self.thread_manager.start(self._capture) - # # else: - # # self.set_ad2_acq_status(False) - # # # return self._capture() - # # - # # def _capture(self): - # # self.model.capturing_finished = False - # # self.model.device_capturing = False - # # cAvailable = c_int() - # # cLost = c_int() - # # cCorrupted = c_int() - # # - # # self.logger.info(f"[{self.pref} Report] Capturing started. " - # # f"Waiting for start command: " - # # f"{self.model.start_recording}<->{self.model.stop_recording}") - # # - # # # Configures the instrument and start or stop the acquisition. To reset the Auto trigger timeout, set - # # # fReconfigure to TRUE. - # # # print("Starting oscilloscope") - # # self.dwf.FDwfAnalogInConfigure(self.model.hdwf, c_int(0), c_int(1)) - # # - # # t0 = -1 - # # - # # cSamples = 0 - # # self.model.device_ready = True - # # self.logger.info(f"[{self.pref} Report] Capturing device is ready.") - # # while True: - # # - # # if self.model.start_recording and not self.model.stop_recording: - # # if t0 < 0: - # # self.logger.info(f"[{self.pref} Report] Start command received.") - # # self.model.capturing_finished = False - # # self.model.device_capturing = True - # # self.model.current_recorded_samples = [] - # # timestamp = datetime.now() - # # t0 = time.time() - # # # print(f"Start ({cSamples})") - # # sts = self.get_analog_in_status() - # # - # # if cSamples == 0 and ( - # # sts == DwfStateConfig or - # # sts == DwfStatePrefill or - # # sts == DwfStateArmed): - # # print('idle') - # # continue # Acquisition not yet started. - # # - # # # Retrieves information about the recording process. The data loss occurs when the device acquisition - # # # is faster than the read process to PC. In this case, the device recording buffer is filled and data - # # # samples are overwritten. Corrupt samples indicate that the samples have been overwritten by the - # # # acquisition process during the previous read. In this case, try optimizing the loop process for faster - # # # execution or reduce the acquisition frequency or record length to be less than or equal to the device - # # # buffer size (record length <= buffer size/frequency). - # # self.dwf.FDwfAnalogInStatusRecord(self.model.hdwf, # Interface handle - # # byref(cAvailable), - # # byref(cLost), - # # byref(cCorrupted)) - # # - # # cSamples += cLost.value - # # - # # if cLost.value: - # # self.logger.warning(f"[{self.pref} Report] - Sample(s) lost ({cLost.value})") - # # self.model.fLost += int(cLost.value) - # # if cCorrupted.value: - # # self.logger.warning(f"[{self.pref} Report] - Samples(s) corrupted ({cCorrupted.value})") - # # self.model.fCorrupted += int(cCorrupted.value) - # # - # # # self.dwf.FDwfAnalogInStatusSamplesValid(self.hdwf, byref(self.cValid)) - # # if cAvailable.value == 0: - # # # print(f"Nothing available {cAvailable.value}") - # # continue - # # else: - # # # print(f"Available: {cAvailable.value}") - # # - # # # if cSamples + cAvailable.value > self.ad2capt_model.n_samples: - # # # cAvailable = c_int(self.ad2capt_model.n_samples - cSamples) - # # rgdSamples = (c_double * cAvailable.value)() - # # # Retrieves the acquired data samples from the specified idxChannel on the AnalogIn instrument. It - # # # copies the data samples to the provided buffer. - # # self.dwf.FDwfAnalogInStatusData(self.model.hdwf, - # # c_int(self.model.analog_in_channel), - # # byref(rgdSamples), - # # cAvailable) # get channel 1 data - # # for s in rgdSamples: - # # self.model.current_recorded_samples.append(float(s)) - # # - # # cSamples += cAvailable.value - # # - # # elif not self.model.start_recording and self.model.stop_recording: - # # t1 = time.time() - # # self.model.measurement_time = t1 - t0 - # # self.get_analog_in_status() - # # self.model.capturing_finished = True - # # self.model.device_capturing = False - # # self.logger.info(f"Finished Thread. Acquisition took {self.model.measurement_time} s. " - # # f"Process captured {len(self.model.current_recorded_samples)} samples.") - # # # 1. Assign the current captured samples to a dict - # # self.model.all_recorded_samples.append({'timestamp': timestamp, - # # 'measurement_time': self.model.measurement_time, - # # 'num_samples': len( - # # self.model.current_recorded_samples), - # # 'acqRate': self.model.hz_acquisition, - # # 'samples': self.model.current_recorded_samples}) - # # # Reset status bits - # # try: - # # time.sleep(1) - # # self.close_device() - # # except Exception as e: - # # print(e) - # # return - # # # return self.model.current_recorded_samples, self.model.measurement_time - # # else: - # # self.model.device_capturing = False - # - # # # ================================================================================================================== - # # # - # # # ================================================================================================================== - # # def gen_sine(self, channel=0, frequency=1): - # # self.logger.debug(f"[{self.pref} Task] Generating sine wave on output 0...") - # # self.dwf.FDwfAnalogOutNodeEnableSet(self.model.hdwf, c_int(channel), AnalogOutNodeCarrier, c_int(1)) - # # self.dwf.FDwfAnalogOutNodeFunctionSet(self.model.hdwf, c_int(channel), AnalogOutNodeCarrier, - # # funcTrapezium) # sine - # # self.dwf.FDwfAnalogOutNodeFrequencySet(self.model.hdwf, c_int(channel), AnalogOutNodeCarrier, - # # c_double(frequency)) # 1Hz - # # self.dwf.FDwfAnalogOutNodeAmplitudeSet(self.model.hdwf, c_int(channel), AnalogOutNodeCarrier, c_double(2)) - # # self.dwf.FDwfAnalogOutConfigure(self.model.hdwf, c_int(channel), c_int(1)) - # # return self.model.hdwf - # - # # ================================================================================================================== - # # - # # ================================================================================================================== - # def close_device(self): - # # Resets and configures (by default, having auto configure enabled) all AnalogOut instrument - # # parameters to default values for the specified channel. To reset instrument parameters across all - # # channels, set idxChannel to -1. - # self.model.fLost = 0 - # self.model.fCorrupted = 0 - # self.model.start_recording = True - # self.model.stop_recording = False - # self.model.capturing_finished = False - # self.model.device_capturing = False - # self.model.connected = False - # self.model.device_state = 0 - # self.model.dwf_version = "Unknown" - # self.model.device_serial_number = "Unknown" - # self.model.device_name = "Unknown" - # self.model.analog_in_channel = -1 - # - # self.dwf.FDwfAnalogOutReset(self.model.hdwf, c_int(self.model.analog_in_channel)) - # self.dwf.FDwfDeviceCloseAll() - # self.logger.info(f"[{self.pref} Task] Device closed.") - - # def get_analog_in_status(self): - # sts: c_byte = c_byte() - # # Checks the state of the acquisition. To read the data from the device, set fReadData to TRUE. For - # # single acquisition mode, the data will be read only when the acquisition is finished. - # self.dwf.FDwfAnalogInStatus(self.model.hdwf, # Interface handle. - # c_int(1), # True, if data should be read - # byref(sts)) # Variable to receive the acquisition state - # self.model.device_state = sts.value - # return sts - - # ================================================================================================================== - # - # ================================================================================================================== diff --git a/src/CaptDeviceControl/controller/BaseAD2CaptDevice.py b/src/CaptDeviceControl/controller/BaseAD2CaptDevice.py index 20cb325d7566c34ece9b1b15081ed978842952db..dfad80f216eb4822ec681df3616467047821ca05 100644 --- a/src/CaptDeviceControl/controller/BaseAD2CaptDevice.py +++ b/src/CaptDeviceControl/controller/BaseAD2CaptDevice.py @@ -1,9 +1,12 @@ import logging +import os import time from abc import abstractmethod from collections import deque from PySide6.QtCore import QObject, QThreadPool +from numpy import ndarray +from rich.logging import RichHandler from CaptDeviceControl.controller.mp_AD2Capture.AD2StateMPSetter import AD2State from CaptDeviceControl.controller.mp_AD2Capture.MPDeviceControl import mp_capture @@ -16,12 +19,19 @@ from CaptDeviceControl.controller.mp_AD2Capture.MPCaptDeviceControl import MPCap class BaseAD2CaptDevice(QObject): - def __init__(self, ad2capt_model: AD2CaptDeviceModel): + def __init__(self, ad2capt_model: AD2CaptDeviceModel, start_capture_flag: Value): super().__init__() self.model = ad2capt_model self.pref = "AD2CaptDev" - self.logger = logging.getLogger(f"AD2 Device") + + self.handler = RichHandler(rich_tracebacks=True) + self.logger = logging.getLogger(f"AD2Controller({os.getpid()})") + self.logger.handlers = [self.handler] + self.logger.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(name)s %(message)s') + self.handler.setFormatter(formatter) + self.signals = AD2CaptDeviceSignals() @@ -32,13 +42,17 @@ class BaseAD2CaptDevice(QObject): # self.thread_manager.setThreadPriority(QThread.HighestPriority) self.lock = Lock() - self.proc = None + #self.proc = None self.stream_data_queue = Queue() self.capture_data_queue = Queue() - self.state_queue = Queue() + #self.state_queue = Queue() - self.start_capture_flag = Value('i', 0, lock=self.lock) - self.end_process_flag = Value('i', False, lock=self.lock) + if start_capture_flag is None: + self.start_capture_flag = Value('i', 0, lock=self.lock) + else: + self.start_capture_flag = start_capture_flag + self.kill_capture_flag = Value('i', 0, lock=self.lock) + #self.end_process_flag = Value('i', False, lock=self.lock) # Number of sa self.streaming_data_dqueue: deque = None # a dqueue, initialize later @@ -50,31 +64,33 @@ class BaseAD2CaptDevice(QObject): self.stream_data_queue, self.capture_data_queue, self.start_capture_flag, - enable_logging=True) + self.kill_capture_flag, + enable_internal_logging=False) #self.mpcaptdevicecontrol.discover_connected_devices_finished.connect( # lambda x: type(self.model).connected_devices.fset(self.model, x)) - self.mpcaptdevicecontrol.connected_devices() - - def get_analog_in_informatio(self): - self.mpcaptdevicecontrol.ain_channels(self.model.selected_device) + #self.mpcaptdevicecontrol.connected_devices() + def device_selected_index_changed(self): + print(self.model.device_information.device_index) + self.mpcaptdevicecontrol.ain_channels(self.model.device_information.device_index) + #self.mpcaptdevicecontrol def connect_device(self, device_id): self.mpcaptdevicecontrol.open_device(device_id) self.mpcaptdevicecontrol.start_capture( self.model.sample_rate, - self.model.selected_ain_channel) + self.model.device_information.device_index) self.start_device_process() return True def close_device(self): self.mpcaptdevicecontrol.close_device() - @abstractmethod + def discover_connected_devices(self): - raise NotImplementedError + self.mpcaptdevicecontrol.connected_devices() @abstractmethod def update_device_information(self): @@ -169,35 +185,36 @@ class BaseAD2CaptDevice(QObject): #self.proc.start() # self.thread_manager.moveToThread(()) - #self.thread_manager.start(self.qt_consume_data) + self.thread_manager.start(self.qt_consume_data) self.thread_manager.start(self.qt_stream_data) #self.thread_manager.start(self.qt_get_state) def qt_consume_data(self): - """Consume data from the queue and plot it. This is a QThread.""" - while not self.kill_thread and not bool(self.end_process_flag.value): - while self.capture_data_queue.qsize() > 0: - self.model.unconsumed_capture_data = self.capture_data_queue.qsize() - d, s = self.capture_data_queue.get() - [self.model.recorded_samples.append(e) for e in d] - # self.model.samples_captured = len(self.model.recorded_samples) - self.status_dqueue.append(s) - #time.sleep(0.01) + while True: + try: + capt_data = self.capture_data_queue.get() + if isinstance(capt_data, ndarray): + print(f"Capt data queue size {self.capture_data_queue.qsize()}") + # for d in stream_data: + # self.model.recorded_samples.append(d) + except Exception as e: + self.logger.info(f"Error while consuming data {e}") self.logger.info("Capture Data consume thread ended") def qt_stream_data(self): - nth_cnt = 1 - nth = 2 - while not self.kill_thread and not bool(self.end_process_flag.value): - while True: - #self.stream_data_queue.qsize() > 0: - #self.model.unconsumed_stream_samples = self.stream_data_queue.qsize() - for d in self.stream_data_queue.get()[0]: - #if nth_cnt == nth: - self.streaming_data_dqueue.append(d) - # nth_cnt = 0 - #nth_cnt += 1 - #time.sleep(0.01) + while True: + t = time.time() + try: + stream_data = self.stream_data_queue.get(block=True) + if isinstance(stream_data, ndarray): + #print(f"Stream data queue size {self.stream_data_queue.qsize()}") + for d in stream_data: + self.streaming_data_dqueue.append(d) + t_end = time.time() + #print(f"Time to get data {t_end-t}") + except Exception as e: + self.logger.info(f"Timeout reached. No data in queue {self.stream_data_queue.qsize()} or" + f"{e}") self.logger.info("Streaming data consume thread ended") def qt_get_state(self): @@ -246,13 +263,13 @@ class BaseAD2CaptDevice(QObject): # Destructor # ================================================================================================================== def stop_process(self): - self.end_process_flag.value = True - + #self.end_process_flag.value = True + time_start = time.time() - while self.proc.is_alive(): - time.sleep(0.1) + #while self.proc.is_alive(): + # time.sleep(0.1) self.logger.warning(f"AD2 process exited after {time.time()-time_start}s") - self.kill_thread = True + self.kill_thread = True def __del__(self): self.logger.info("Exiting AD2 controller") diff --git a/src/CaptDeviceControl/controller/DeviceInformation/AnalogOutChannel.py b/src/CaptDeviceControl/controller/DeviceInformation/AnalogOutChannel.py new file mode 100644 index 0000000000000000000000000000000000000000..97f6cd2bc906a85821fdd1322ce21c3bc559aeed --- /dev/null +++ b/src/CaptDeviceControl/controller/DeviceInformation/AnalogOutChannel.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +""" +from ctypes import CDLL, c_int, byref + +from controller.DeviceInformation.HWDeviceBase import HWDeviceBase + + +class AnalogOutChannel(HWDeviceBase): + def __init__(self, dwf: CDLL, hwdf: c_int, device_idx, channel): + super().__init__(dwf, hwdf) + + self._channel: int = channel + self._node: int = 0 + self._buffer_size: int = 0 + self._amplitude: tuple = (0, 0) + self._offset: tuple = (0, 0) + self._frequency: tuple = (0, 0) + + def reinit(self, fields: dict): + for k, v in fields.items(): + setattr(self, k, v) + + @property + def channel(self) -> int: + """ + Returns the channel number of the AnalogOut channel. + :return: The channel number of the AnalogOut channel. + """ + return self._channel + + @property + def node(self) -> int: + """ + Returns the node number of the AnalogOut channel. + :return: The node number of the AnalogOut channel. + """ + return self._node + + @property + def buffer_size(self) -> int: + """ + Reads the number of AnalogIn channels of the device. The oscilloscope channel settings are + identical across all channels. + Calls WaveForms API Function 'FDwfAnalogInChannelCount(HDWF hdwf, int *pcChannel)' + :return: The number of analog in channels. + """ + self._check_device_connection() + int0 = c_int() + self.dwf.FDwfAnalogInChannelCount(self.hdwf, byref(int0)) + self._analog_in_channels = int(int0.value) + return self._analog_in_channels + + +class AnalogOutChannelSetter(): + pass \ No newline at end of file diff --git a/src/CaptDeviceControl/controller/DeviceInformation/HWConnectedDeviceInformation.py b/src/CaptDeviceControl/controller/DeviceInformation/HWConnectedDeviceInformation.py new file mode 100644 index 0000000000000000000000000000000000000000..39300949a20b859072a73c7323bd6b56478a8270 --- /dev/null +++ b/src/CaptDeviceControl/controller/DeviceInformation/HWConnectedDeviceInformation.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +""" +from ctypes import CDLL + +from controller.DeviceInformation.dataclasses.AnalogInChannelInfo import AnalogInChannels +from controller.DeviceInformation.AnalogOutChannel import AnalogOutChannels + + +class HWConnectedDeviceInformation: + """ Class for storing information about the connected device.""" + + def __init__(self, dwf: CDLL, device_idx, type="USB"): + self.dwf = dwf + self._type: str = type + + self._device_id: int = device_idx + self._device_name: str = "" + self._serial_number: str = "" + + self.analog_in_channels = list[AnalogInChannels] + self.analog_out_channels = list[AnalogOutChannels] + + @property + def type(self) -> str: + """ + Returns the type of the device (USB or Simulator) + :return: Type of the device + """ + return self._type + + @property + def device_id(self) -> int: + """ + The device id of the enumerated device. + :return: The device id of the enumerated device. + """ + return self._device_id + + @property + def device_name(self) -> str: + """ + The device name of the enumerated device. + :return: The device name of the enumerated device. + """ + return self._device_name + + @property + def serial_number(self) -> str: + """ + The serial number of the enumerated device. + :return: The serial number of the enumerated device. + """ + return self._serial_number + + def __repr__(self): + return f"HW({self._device_name}, {self._serial_number}, {self._type})" \ No newline at end of file diff --git a/src/CaptDeviceControl/controller/DeviceInformation/HWDeviceBase.py b/src/CaptDeviceControl/controller/DeviceInformation/HWDeviceBase.py new file mode 100644 index 0000000000000000000000000000000000000000..c1cfda7b0d2960fc55fb18de67aad4292a4493b1 --- /dev/null +++ b/src/CaptDeviceControl/controller/DeviceInformation/HWDeviceBase.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +""" +from ctypes import CDLL, c_int, create_string_buffer, c_int32, byref + +from constants.dwfconstants import enumfilterType, enumfilterUSB, enumfilterDemo + + +class HWDeviceBase: + """Base class for retrieving information from the hardware.""" + + def __int__(self, dwf: CDLL, hwdf: c_int): + self.dwf: CDLL = dwf + self.hdwf: hwdf = hwdf + + def _check_device_connection(self): + if self.hdwf.value == 0: + szerr = create_string_buffer(512) + self.dwf.FDwfGetLastErrorMsg(szerr) + raise Exception(str(szerr.value)) + + diff --git a/src/CaptDeviceControl/controller/DeviceInformation/HWDeviceInformation.py b/src/CaptDeviceControl/controller/DeviceInformation/HWDeviceInformation.py new file mode 100644 index 0000000000000000000000000000000000000000..59d679900dd55ff7590d97c9bdc95a889902e852 --- /dev/null +++ b/src/CaptDeviceControl/controller/DeviceInformation/HWDeviceInformation.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +""" +from ctypes import c_int, create_string_buffer, CDLL, byref + +from controller.DeviceInformation.WaveFormsAPI import WFAPIDeviceEnumeration, WFAPIDeviceControl, WFAPIChannels +from controller.DeviceInformation.dataclasses.AnalogInChannelInfo import AnalogInChannelInfo, AnalogInChannelRetriever + + +class HWDeviceInformation: + """ Class for storing information about the connected device.""" + + def __init__(self, device_idx, type="USB"): + self._type: str = type + + self._device_id: int = device_idx + self._device_name: str = "" + self._serial_number: str = "" + + self._analog_in_channels: list[AnalogInChannelInfo] = [] + self._analog_out_channels: list[AnalogInChannelInfo] = [] + + self._analog_in_channel_count: int = 0 + self._analog_out_channel_count: int = 0 + self._analog_io_channel_count: int = 0 + + self._digital_in_channel_count: int = 0 + self._digital_out_channel_count: int = 0 + self._digital_io_channel_count: int = 0 + + self._analog_in_buffer_size: int = 0 + self._analog_out_buffer_size: int = 0 + + self._digital_in_buffer_size: int = 0 + self._digital_out_buffer_size: int = 0 + # self._reload() + + @property + def type(self) -> str: + """ + Returns the type of the device (USB or Simulator) + :return: Type of the device + """ + return self._type + + @property + def device_id(self) -> int: + """ + The device id of the enumerated device. + :return: The device id of the enumerated device. + """ + return self._device_id + + @property + def device_name(self) -> str: + """ + The device name of the enumerated device. + :return: The device name of the enumerated device. + """ + return self._device_name + + @property + def serial_number(self) -> str: + """ + The serial number of the enumerated device. + :return: The serial number of the enumerated device. + """ + return self._serial_number + + @property + def analog_in_channels(self) -> list[AnalogInChannelInfo]: + """ + The analog in channels of the device without opening it. + :return: The analog in channels. + """ + return self._analog_in_channels + + @property + def analog_out_channels(self) -> list[AnalogInChannelInfo]: + """ + The analog out channels of the device without opening it. + :return: The analog out channels. + """ + return self._analog_out_channels + + @property + def analog_in_channel_count(self) -> int: + """ + The number of AnalogIn channels of the device without opening it. + :return: The number of analog in channels. + """ + return self._analog_in_channel_count + + @property + def analog_out_channel_count(self) -> int: + """ + The number of AnalogOut channels of the device without opening it. + :return: The number of analog out channels. + """ + return self._analog_out_channel_count + + @property + def analog_io_channel_count(self) -> int: + """ + The number of AnalogIO channels of the device without opening it. + :return: The number of analog io channels. + """ + return self._analog_io_channel_count + + @property + def digital_in_channel_count(self) -> int: + """ + The number of DigitalIn channels of the device without opening it. + :return: The number of digital in channels. + """ + return self._digital_in_channel_count + + @property + def digital_out_channel_count(self) -> int: + """ + The number of DigitalOut channels of the device without opening it. + :return: The number of digital out channels. + """ + return self._digital_out_channel_count + + @property + def digital_io_channel_count(self) -> int: + """ + The number of DigitalIO channels of the device without opening it. + :return: The number of digital io channels. + """ + return self._digital_io_channel_count + + @property + def analog_in_buffer_size(self) -> int: + """ + The buffer size of the AnalogIn channels of the device without opening it. + :return: The buffer size of the analog in channels. + """ + return self._analog_in_buffer_size + + @property + def analog_out_buffer_size(self) -> int: + """ + The buffer size of the AnalogOut channels of the device without opening it. + :return: The buffer size of the analog out channels. + """ + return self._analog_out_buffer_size + + @property + def digital_in_buffer_size(self) -> int: + """ + The buffer size of the DigitalIn channels of the device without opening it. + :return: The buffer size of the digital in channels. + """ + return self._digital_in_buffer_size + + @property + def digital_out_buffer_size(self) -> int: + """ + The buffer size of the DigitalOut channels of the device without opening it. + :return: The buffer size of the digital out channels. + """ + return self._digital_out_buffer_size + + def __repr__(self): + return f"HW({self._device_name}, {self._serial_number}, {self._type})" + + +class HWDeviceInformationRetriever(HWDeviceInformation): + def __init__(self, dwf: CDLL, device_idx, con_type="USB"): + super().__init__(device_idx, con_type) + self._device_name = WFAPIDeviceEnumeration.device_name(dwf, self.device_id) + self._serial_number = WFAPIDeviceEnumeration.serial_number(dwf, self.device_id) + + # Top get all settings, we need to open the device + try: + self.hwdf = WFAPIDeviceControl.device_open(dwf, self.device_id) + except Exception as ex: + print(f"Error opening device: {ex}") + self.hwdf = None + + if self.hwdf is not None: + # Get the number of ain channels + channel_count = WFAPIChannels().analog_in_channels_count(dwf, self.hwdf) + #print(channel_count) + # Create a list of channels + for channel in range(channel_count): + o = AnalogInChannelRetriever(dwf, self.hwdf, channel).simple() + self._analog_in_channels.append(o) + print(type(o)) + + self._analog_in_channel_count = len(self._analog_in_channels) + + + def simple(self): + return super() diff --git a/src/CaptDeviceControl/controller/DeviceInformation/WaveFormsAPI.py b/src/CaptDeviceControl/controller/DeviceInformation/WaveFormsAPI.py new file mode 100644 index 0000000000000000000000000000000000000000..02bb60588e5c1b2a038f083d5d61651b48870731 --- /dev/null +++ b/src/CaptDeviceControl/controller/DeviceInformation/WaveFormsAPI.py @@ -0,0 +1,509 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +""" +import logging +import sys +from abc import abstractmethod +from ctypes import c_int, byref, c_double, CDLL, create_string_buffer, cdll, c_int32 + +from constants.dwfconstants import enumfilterType, enumfilterDemo, enumfilterUSB + + +class WFAPI: + + @staticmethod + def _check_device_connection(dwf: CDLL, hwdf: c_int): + if hwdf.value == 0: + szerr = create_string_buffer(512) + dwf.FDwfGetLastErrorMsg(szerr) + raise Exception(str(szerr.value)) + + +class WFAPIChannels(WFAPI): + + def __init__(self): + super().__init__() + + @staticmethod + def analog_in_channels_count(dwf: CDLL, hdwf: c_int) -> int: + """ + Reads the number of AnalogIn channels of the device. The oscilloscope channel settings are + identical across all channels. + Calls WaveForms API Function 'FDwfAnalogInChannelCount(HDWF hdwf, int *pcChannel)' + :return: The number of analog in channels. + """ + WFAPI._check_device_connection(dwf, hdwf) + int0 = c_int() + dwf.FDwfAnalogInChannelCount(hdwf, byref(int0)) + _analog_in_channels = int(int0.value) + return _analog_in_channels + + @staticmethod + def analog_on_channel_enable_set(dwf: CDLL, hdwf: c_int, channel: int, enable: bool): + """ + Not implemented. + Enables or disables the specified AnalogIn channel. + Calls WaveForms API Function 'FDwfAnalogInChannelEnableSet(HDWF hdwf, int idxChannel, int fEnable)' + :param channel: The channel to enable or disable. + :param enable: True to enable the channel, False to disable it. + """ + WFAPI._check_device_connection(dwf, hdwf) + raise NotImplementedError() + + @staticmethod + def analog_in_channel_enable_get(dwf: CDLL, hdwf: c_int, channel: int) -> bool: + """ + Not implemented. + Gets the current enable/disable status of the specified AnalogIn channel. + Calls WaveForms API Function 'FDwfAnalogInChannelEnableGet(HDWF hdwf, int idxChannel, int *pfEnable)' + :param channel: Index of channel to get the enable/disable status of. + """ + WFAPI._check_device_connection(dwf, hdwf) + raise NotImplementedError() + + @staticmethod + def analog_in_channel_filter_info(dwf: CDLL, hdwf: c_int, filter_number: int): + """ + Not implemented. + Returns the supported sampling modes. They are returned (by reference) as a bit field. This bit field + can be parsed using the IsBitSet Macro. Individual bits are defined using the FILTER constants in dwf.h. + When the acquisition frequency (FDwfAnalogInFrequencySet) is less than the ADC frequency (maximum + acquisition frequency). + + Calls the WaveForms API Function 'FDwfAnalogInChannelFilterInfo(HDWF hdwf, int *pfsfilter)' + + :param filter_number: + - filterDecimate: 0 + Store every Nth ADC conversion, where N = ADC frequency /acquisition frequency. + - filterAverage: 1 + Store the average of N ADC conversions. + - filterMinMax: 2 + Store interleaved, the minimum and maximum values, of 2xN conversions. + - filterAverageFit: 3 + The stored samples match the specified range instead of the device input range options. + This can improve the vertical resolution of the samples. + """ + WFAPI._check_device_connection(dwf, hdwf) + raise NotImplementedError() + + @staticmethod + def analog_in_channel_filter_set(dwf: CDLL, hdwf: c_int, channel: int, filter_number: int): + """ + Not implemented. + Sets the acquisition filter for each AnalogIn channel. With channel index -1, each enabled AnalogIn + channel filter will be configured to use the same, new option. + + Calls the WaveForms API Function 'FDwfAnalogInChannelFilterSet(HDWF hdwf, int idxChannel, FILTER filter)' + + :param channel: Channel index + :param filter_number: Acquisition sample filter to set. + - filterDecimate: 0 + Store every Nth ADC conversion, where N = ADC frequency /acquisition frequency. + - filterAverage: 1 + Store the average of N ADC conversions. + - filterMinMax: 2 + Store interleaved, the minimum and maximum values, of 2xN conversions. + - filterAverageFit: 3 + The stored samples match the specified range instead of the device input range options. + This can improve the vertical resolution of the samples. + """ + WFAPI._check_device_connection(dwf, hdwf) + raise NotImplementedError() + + @staticmethod + def analog_in_channel_filter_get(dwf: CDLL, hdwf: c_int, channel: int) -> int: + """ + Not implemented. + Returns the configured acquisition filter. + + Calls the WaveForms API Function 'FDwfAnalogInChannelFilterGet(HDWF hdwf, int idxChannel, FILTER *pfilter)' + + :param channel: Channel index + :return: filter type (int) + - filterDecimate: 0 + Store every Nth ADC conversion, where N = ADC frequency /acquisition frequency. + - filterAverage: 1 + Store the average of N ADC conversions. + - filterMinMax: 2 + Store interleaved, the minimum and maximum values, of 2xN conversions. + - filterAverageFit: 3 + The stored samples match the specified range instead of the device input range options. + This can improve the vertical resolution of the samples. + """ + + @staticmethod + def analog_in_channel_range_info(dwf: CDLL, hdwf: c_int) -> tuple: + """ + Returns the minimum and maximum range, peak to peak values, and the number of adjustable steps. + The oscilloscope channel settings are identical across all channels. + Calls WaveForms API Function + 'FDwfAnalogInChannelRangeInfo(HDWF hdwf, double *pvoltsMin, double *pvoltsMax, double *pnSteps)' + :return: The minimum and maximum range, peak to peak values, and the number of adjustable steps as a tuple + (min, max, steps). + """ + WFAPI._check_device_connection(dwf, hdwf) + dbl0 = c_double() + dbl1 = c_double() + dbl2 = c_double() + dwf.FDwfAnalogInChannelRangeInfo(hdwf, byref(dbl0), byref(dbl1), byref(dbl2)) + _range = (int(dbl0.value), int(dbl1.value), int(dbl2.value)) + return _range + + @staticmethod + def analog_in_channel_range_steps(dwf: CDLL, hdwf: c_int): + """ + Not implemented. + Reads the range of steps supported by the device. For instance: 1, 2, 5, 10, etc. + + Calls WaveForms API Function + 'FDwfAnalogInChannelRangeSteps(HDWF hdwf, double rgVoltsStep[32], int *pnSteps)' + """ + WFAPI._check_device_connection(dwf, hdwf) + raise NotImplementedError() + + @staticmethod + def analog_in_channel_range_set(dwf: CDLL, hdwf: c_int, channel: int, range: float): + """ + Not implemented. + Configures the range for each channel. With channel index -1, each enabled Analog In channel range + will be configured to the same, new value. + + Calls WaveForms API Function + 'FDwfAnalogInChannelRangeSet(HDWF hdwf, int idxChannel, double voltsRange)' + :param channel: Channel index + :param range: The range to set. + """ + WFAPI._check_device_connection(dwf, hdwf) + raise NotImplementedError() + + @staticmethod + def analog_in_channel_range_get(dwf: CDLL, hdwf: c_int, channel: int) -> float: + """ + Not implemented. + Returns the real range value for the given channel. + + Calls WaveForms API Function + 'FDwfAnalogInChannelRangeGet(HDWF hdwf, int idxChannel, double *pvoltsRange)' + + :param channel: Channel index + :return: The real range value for the given channel. + """ + + @staticmethod + def analog_in_channel_offset_info(dwf: CDLL, hdwf: c_int) -> tuple: + """ + Returns the minimum and maximum offset levels supported, and the number of adjustable steps. The oscilloscope + channel settings are identical across all channels. + Calls WaveForms API Function + 'FDwfAnalogInChannelOffsetInfo(HDWF hdwf, double *pvoltsMin, double *pvoltsMax, double *pnSteps)' + :return: The minimum and maximum offset levels supported, and the number of adjustable steps as + a tuple (min, max, steps). + """ + WFAPI._check_device_connection(dwf, hdwf) + dbl0 = c_double() + dbl1 = c_double() + dbl2 = c_double() + dwf.FDwfAnalogInChannelOffsetInfo(hdwf, byref(dbl0), byref(dbl1), byref(dbl2)) + _offset = (int(dbl0.value), int(dbl1.value), int(dbl2.value)) + return _offset + + @staticmethod + def analog_in_buffer_size(dwf: CDLL, hdwf: c_int) -> tuple: + """ + Returns the minimum and maximum allowable buffer sizes for the instrument. The oscilloscope + channel settings are identical across all channels. + Calls WaveForms API Function 'FDwfAnalogInBufferSizeInfo(HDWF hdwf, int *pnSizeMin, int *pnSizeMax)' + :return: The minimum and maximum allowable buffer sizes for the instrument as a tuple (min, max). + """ + WFAPI._check_device_connection(dwf, hdwf) + int0 = c_int() + int1 = c_int() + dwf.FDwfAnalogInBufferSizeInfo(hdwf, byref(int0), byref(int1)) + _buffer_size = (int(int0.value), int(int0.value)) + return _buffer_size + + @staticmethod + def analog_in_bits(dwf: CDLL, hdwf: c_int) -> int: + """ + Retrieves the number bits used by the AnalogIn ADC. The oscilloscope channel settings are identical + across all channels. + Calls WaveForms API Function 'FDwfAnalogInBitsInfo(HDWF hdwf, int *pnBits)' + :return: The number bits used by the AnalogIn ADC. + """ + int0 = c_int() + WFAPI._check_device_connection(dwf, hdwf) + dwf.FDwfAnalogInBitsInfo(hdwf, byref(int0)) + _adc_bits = int(int0.value) + return _adc_bits + + +class WFAPIDeviceEnumeration(WFAPI): + + def __init__(self): + super().__init__() + + @staticmethod + def device_name(dwf: CDLL, device_id: int) -> str: + """ + Retrieves the device name of the enumerated device. + Calls WaveForms API Function 'FDwfEnumDeviceName(int idxDevice, char szDeviceName[32])' + :return: The device name of the enumerated device + """ + devicename = create_string_buffer(64) + dwf.FDwfEnumDeviceName(c_int(device_id), devicename) + return str(devicename.value.decode("utf-8")) + + @staticmethod + def serial_number(dwf: CDLL, device_id: int) -> str: + """ + Retrieves the serial number of the enumerated device. + :return: + """ + serialnum = create_string_buffer(16) + dwf.FDwfEnumSN(c_int(device_id), serialnum) + return str(serialnum.value.decode("utf-8")) + + @staticmethod + def analog_in_channel_count(dwf: CDLL, device_id: int) -> int: + """ + Returns the number of AnalogIn channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 1. + :return: The number of analog in channels. + """ + cInfo = c_int() + # c_int(1): DECIAnalogInChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(1), byref(cInfo)) + return int(cInfo.value) + + @staticmethod + def analog_out_channel_count(dwf: CDLL, device_id: int) -> int: + """ + Returns the number of AnalogOut channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 2. + :return: The number of analog out channels. + """ + cInfo = c_int() + # c_int(2): DECIAnalogOutChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(2), byref(cInfo)) + return int(cInfo.value) + + @staticmethod + def analog_io_channel_count(dwf: CDLL, device_id: int) -> int: + """ + Returns the number of AnalogIO channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 3. + :return: The number of analog io channels. + """ + cInfo = c_int() + # c_int(3): DECIAnalogIOChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(3), byref(cInfo)) + return int(cInfo.value) + + @staticmethod + def digital_in_channel_count(dwf: CDLL, device_id: int) -> int: + """ + Returns the number of DigitalIn channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 4. + :return: The number of digital in channels. + """ + cInfo = c_int() + # c_int(4): DECIDigitalInChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(4), byref(cInfo)) + + return int(cInfo.value) + + @staticmethod + def digital_out_channel_count(dwf: CDLL, device_id: int) -> int: + """ + Returns the number of DigitalOut channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 5. + :return: The number of digital out channels. + """ + cInfo = c_int() + # c_int(5): DECIDigitalOutChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(5), byref(cInfo)) + + return int(cInfo.value) + + @staticmethod + def digital_io_channel_count(dwf: CDLL, device_id: int) -> int: + """ + Returns the number of DigitalIO channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 6. + :return: The number of digital io channels. + """ + cInfo = c_int() + # c_int(6): DECIDigitalIOChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(6), byref(cInfo)) + return int(cInfo.value) + + @staticmethod + def analog_in_buffer_size(dwf: CDLL, device_id: int) -> int: + """ + Returns the buffer size of the AnalogIn channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 7. + :return: The buffer size of the analog in channels. + """ + cInfo = c_int() + # c_int(6): DECIDigitalIOChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(7), byref(cInfo)) + return int(cInfo.value) + + @staticmethod + def analog_out_buffer_size(dwf: CDLL, device_id: int) -> int: + """ + Returns the buffer size of the AnalogOut channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 8. + :return: The buffer size of the analog out channels. + """ + cInfo = c_int() + # c_int(6): DECIDigitalIOChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(8), byref(cInfo)) + return int(cInfo.value) + + @staticmethod + def digital_in_buffer_size(dwf: CDLL, device_id: int) -> int: + """ + Returns the buffer size of the DigitalIn channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 9. + :return: The buffer size of the digital in channels. + """ + cInfo = c_int() + # c_int(6): DECIDigitalIOChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(9), byref(cInfo)) + return int(cInfo.value) + + @staticmethod + def digital_out_buffer_size(dwf: CDLL, device_id: int) -> int: + """ + Returns the buffer size of the DigitalOut channels of the device without opening it. + Note: These are intended for preliminary information before opening a device. Further information are available + with various the FDwf#Info functions. + + Calls WaveForms API Function 'FDwfEnumConfigInfo(int idxConfig, DwfEnumConfigInfo info, int *pValue)' + with parameter info = 10. + :return: The buffer size of the digital out channels. + """ + cInfo = c_int() + # c_int(6): DECIDigitalIOChannelCount + dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(10), byref(cInfo)) + return int(cInfo.value) + + +class WFAPIDeviceControl(WFAPI): + + def __init__(self): + super().__init__() + + @staticmethod + def device_open(dwf: CDLL, device_id: int) -> c_int: + """ + Opens a device identified by the enumeration index and retrieves a handle. + To automatically enumerate all connected devices and open the first discovered device, use index -1. + + Calls WaveForms API Function 'FDwfDeviceOpen(int idxDevice, HDWF *phdwf)' + + :param dwf: Shared library handle. + :param device_id: The device id of the device to open. + :return: The handle (hdwf) of the opened device. + """ + hdwf = c_int() + dwf.FDwfDeviceOpen(c_int(device_id), byref(hdwf)) + return hdwf + + +class WaveFormsAPI: + + def __init__(self, parent_logger: logging.Logger): + self._logger = parent_logger + self.channels = WFAPIChannels() + self.device_enumeration = WFAPIDeviceEnumeration() + self.device_control = WFAPIDeviceControl() + + self._dwf_init = False + self._dwf = None + + def init_dwf(self) -> CDLL: + if not self._dwf_init: + if sys.platform.startswith("win"): + self._dwf = cdll.dwf + elif sys.platform.startswith("darwin"): + self._dwf = cdll.LoadLibrary("/Library/Frameworks/dwf.framework/dwf") + else: + self._dwf = cdll.LoadLibrary("libdwf.so") + self._dwf_init = True + return self._dwf + + + def get_connected_devices(self, dwf, discover_simulators: bool = False): + self._logger.info(f"Discovering connected devices...") + # enumerate connected devices + connected_devices = [] + # for filter_type in [(c_int32(enumfilterType.value | enumfilterUSB.value), 'USB'), + # (c_int32(enumfilterType.value | enumfilterNetwork.value), 'Network'), + # (c_int32(enumfilterType.value | enumfilterAXI.value), 'AXI'), + # (c_int32(enumfilterType.value | enumfilterRemote.value), 'Remote'), + # (c_int32(enumfilterType.value | enumfilterAudio.value), 'Audio'), + # (c_int32(enumfilterType.value | enumfilterDemo.value), 'Demo')]: + cDevice = c_int() + filter, type = (c_int32(enumfilterType.value | enumfilterDemo.value | enumfilterUSB.value), 'USB') + # filter, type = (c_int32(enumfilterType.value | enumfilterDemo.value), 'DEMO') + self._logger.debug(f"Filtering {type} devices...") + self.dwf.FDwfEnum(filter, byref(cDevice)) + num_of_connected_devices = cDevice + + devicename = create_string_buffer(64) + serialnum = create_string_buffer(16) + + for iDevice in range(0, cDevice.value): + self.dwf.FDwfEnumDeviceName(c_int(iDevice), devicename) + self.dwf.FDwfEnumSN(c_int(iDevice), serialnum) + connected_devices.append({ + 'type': type, + 'device_id': int(iDevice), + 'device_name': str(devicename.value.decode('UTF-8')), + 'serial_number': str(serialnum.value.decode('UTF-8')) + }) + # _mp_log_debug(f"Found {type} device: {devicename.value.decode('UTF-8')} ({serialnum.value.decode('UTF-8')})") + # print(connected_devices) + # print(f"Discoverd {len(self.model.connected_devices)} devices.") + return connected_devices diff --git a/src/CaptDeviceControl/controller/DeviceInformation/dataclasses/AnalogInChannelInfo.py b/src/CaptDeviceControl/controller/DeviceInformation/dataclasses/AnalogInChannelInfo.py new file mode 100644 index 0000000000000000000000000000000000000000..96d637bd65f7ea9337aded5de2e637575bcdedd8 --- /dev/null +++ b/src/CaptDeviceControl/controller/DeviceInformation/dataclasses/AnalogInChannelInfo.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +""" +from ctypes import CDLL, c_int, byref, c_double +from dataclasses import dataclass + +from controller.DeviceInformation.HWDeviceBase import HWDeviceBase +from controller.DeviceInformation.WaveFormsAPI import WFAPIChannels + + +@dataclass +class AnalogInChannelInfo: + """ Class for storing the information about the channels""" + + def __init__(self): + self._channel = 0 + self._buffer_size: tuple = (0, 0) + self._adc_bits: int = 0 + self._range: tuple = (0, 0, 0) + self._offset: tuple = (0, 0, 0) + + @property + def buffer_size(self) -> tuple: + """ Returns the minimum and maximum allowable buffer sizes for the instrument""" + return self._buffer_size + + @property + def adc_bits(self) -> int: + """ Returns the number bits used by the AnalogIn ADC""" + return self._adc_bits + + @property + def range(self) -> tuple: + """ Returns the minimum and maximum range, peak to peak values, and the number of adjustable steps""" + return self._range + + @property + def offset(self) -> tuple: + """ Returns the minimum and maximum offset levels supported, and the number of adjustable steps""" + return self._offset + + +class AnalogInChannelRetriever(AnalogInChannelInfo): + + def __init__(self, dwf: CDLL, hwdf: c_int, channel: int): + super().__init__() + self._channel = channel + self._buffer_size: tuple = WFAPIChannels().analog_in_buffer_size(dwf, hwdf) + self._adc_bits: int = WFAPIChannels().analog_in_bits(dwf, hwdf) + self._range: tuple = WFAPIChannels().analog_in_channel_range_info(dwf, hwdf) + self._offset: tuple = WFAPIChannels().analog_in_channel_offset_info(dwf, hwdf) + + def simple(self): + # TODO: Does nto work + return super(AnalogInChannelInfo, self) \ No newline at end of file diff --git a/src/CaptDeviceControl/controller/mp_AD2Capture/AD2StateMPSetter.py b/src/CaptDeviceControl/controller/mp_AD2Capture/AD2StateMPSetter.py index b92f8ddf457581f7361ffbe8d585792f8b1d4bdc..f1f0827cdfc2f5fe380b01e0fe847fe6cb281604 100644 --- a/src/CaptDeviceControl/controller/mp_AD2Capture/AD2StateMPSetter.py +++ b/src/CaptDeviceControl/controller/mp_AD2Capture/AD2StateMPSetter.py @@ -6,6 +6,7 @@ class AD2State: def __init__(self): # Multiprocessing Information self._pid = None + self._ppid = None # WaveForms Runtime (DWF) Information self._dwf_version: str = "Unknown" @@ -31,7 +32,11 @@ class AD2State: # Acquired Signal Information self._acquisition_state: int = AD2Constants.CapturingState.STOPPED() + + self._time_capture_started: float = -1 # The time the capturing has started + self._time_capture_ended: float = -1 # The time the capturing has ended self._recording_time: float = -1 + self._samples_captured: int = 0 self._samples_lost: int = -1 self._samples_corrupted: int = -1 @@ -50,6 +55,10 @@ class AD2State: def pid(self): return self._pid + @property + def ppid(self): + return self._ppid + # =========== WaveForms Runtime (DWF) Information @property def dwf_version(self): @@ -108,6 +117,16 @@ class AD2State: def acquisition_state(self): return self._acquisition_state + @property + def time_capture_started(self) -> float: + """The time the capturing has started""" + return self._time_capture_started + + @property + def time_capture_ended(self) -> float: + """The time the capturing has ended""" + return self._time_capture_ended + @property def recording_time(self): return self._recording_time @@ -146,6 +165,11 @@ class AD2StateMPSetter(AD2State): self._pid = value self._state_queue.put(self.to_simple_class()) + @AD2State.ppid.setter + def ppid(self, value): + self._ppid = value + self._state_queue.put(self.to_simple_class()) + # =========== WaveForms Runtime (DWF) Information @AD2State.dwf_version.setter def dwf_version(self, value): @@ -218,6 +242,17 @@ class AD2StateMPSetter(AD2State): self._acquisition_state = value self._state_queue.put(self.to_simple_class()) + @AD2State.time_capture_ended.setter + def time_capture_started(self, value: float): + """The time the capturing has started""" + self._time_capture_started = value + self._state_queue.put(self.to_simple_class()) + + @AD2State.time_capture_ended.setter + def time_capture_ended(self, value: float): + self._time_capture_ended = value + self._state_queue.put(self.to_simple_class()) + @AD2State.recording_time.setter def recording_time(self, value): self._recording_time = value diff --git a/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDevice.py b/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDevice.py index d88356356d06c97f9e8e5863a37cecd6673ae886..fa38f2e22c62df592a697951ddbd98ce3cbd8961 100644 --- a/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDevice.py +++ b/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDevice.py @@ -1,24 +1,44 @@ +import ctypes import os import sys import time -from ctypes import c_int, c_int32, byref, create_string_buffer, cdll, c_double, c_byte +from ctypes import c_int, c_int32, byref, create_string_buffer, cdll, c_double, c_byte, CDLL from multiprocessing import Queue, Value import cmp +import numpy as np from constants.dwfconstants import enumfilterType, enumfilterDemo, enumfilterUSB, acqmodeRecord, DwfStateConfig, \ DwfStatePrefill, DwfStateArmed class MPCaptDevice(cmp.CProcess): + @staticmethod + def timeit(func): + def wrapper(self, *args, **kwargs): + time_start = time.time() + res = func(self, *args, **kwargs) + time_stop = time.time() + print(f"Function {func.__name__} took {time_stop - time_start} seconds.") + return res #time_stop - time_start + + return wrapper + def __init__(self, state_queue, cmd_queue, - streaming_data_queue: Queue, - capture_data_queue: Queue, + streaming_data_queue: Queue, capture_data_queue: Queue, start_capture_flag: Value, - enable_logging): - super().__init__(state_queue, cmd_queue, enable_logging=enable_logging) + kill_capture_flag: Value, + enable_internal_logging): + super().__init__(state_queue, cmd_queue, enable_internal_logging=enable_internal_logging) + + self._c_samples = None + self._c_corrupted = None + self._c_lost = None + self._c_available = None self.start_capture_flag: Value = start_capture_flag + self.kill_capture_flag: Value = kill_capture_flag + self.stream_data_queue = streaming_data_queue self.capture_data_queue = capture_data_queue @@ -27,28 +47,49 @@ class MPCaptDevice(cmp.CProcess): self.dwf = None self.hdwf = None + # Capture data counters + self._dwf_version = None self._device_serial_number: str = "" - self._device_name: str = "None" - self._connected = self.connected() + self._device_name: str = "" + self._connected = False self._samples_lost = 0 self._samples_corrupted = 0 - - def postrun_init(self): self.logger, self.ha = self.create_new_logger(f"{self.__class__.__name__}/({os.getpid()})") + if sys.platform.startswith("win"): self.dwf = cdll.dwf elif sys.platform.startswith("darwin"): self.dwf = cdll.LoadLibrary("/Library/Frameworks/dwf.framework/dwf") else: self.dwf = cdll.LoadLibrary("libdwf.so") - + self._connected = self.connected() self.hdwf = c_int() self._ain_device_state: c_byte = c_byte() + self._c_available = c_int() + self._c_lost = c_int() + self._c_corrupted = c_int() + self._c_samples = 0 + + @cmp.CProcess.register_for_signal('_changed') + def device_capturing(self, capturing: bool): + self.logger.info(f"Device capturing: {capturing}") + return capturing + + @cmp.CProcess.register_for_signal('_changed') + def dwf_version(self): + self.logger.debug(f"Getting DWF version information...") + version = create_string_buffer(16) + self.dwf.FDwfGetVersion(version) + return version.value.decode("utf-8") + + # ================================================================================================================== + # Device Enumeration without connecting to the device + # ================================================================================================================== @cmp.CProcess.register_for_signal('_changed') def connected_devices(self): self.logger.info(f"Discovering connected devices...") @@ -66,30 +107,34 @@ class MPCaptDevice(cmp.CProcess): self.logger.debug(f"Filtering {type} devices...") self.dwf.FDwfEnum(filter, byref(cDevice)) num_of_connected_devices = cDevice - - devicename = create_string_buffer(64) - serialnum = create_string_buffer(16) + self.logger.debug(f"Found {cDevice.value} {type} devices.") for iDevice in range(0, cDevice.value): - self.dwf.FDwfEnumDeviceName(c_int(iDevice), devicename) - self.dwf.FDwfEnumSN(c_int(iDevice), serialnum) connected_devices.append({ 'type': type, 'device_id': int(iDevice), - 'device_name': str(devicename.value.decode('UTF-8')), - 'serial_number': str(serialnum.value.decode('UTF-8')) + 'device_name': self.device_name(iDevice), + 'serial_number': self.device_serial_number(iDevice) }) # _mp_log_debug(f"Found {type} device: {devicename.value.decode('UTF-8')} ({serialnum.value.decode('UTF-8')})") # print(connected_devices) # print(f"Discoverd {len(self.model.connected_devices)} devices.") + self.logger.info(f"Found {len(connected_devices)} devices.") return connected_devices @cmp.CProcess.register_for_signal('_changed') def ain_channels(self, device_id) -> list: - print(f">>> Reading available analog input channels for device {device_id}.") cInfo = c_int() self.dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(1), byref(cInfo)) - return list(range(0, cInfo.value)) + ain_channels = cInfo.value + if ain_channels == 0: + # Sometimes, the device reports a wrong number of ain channels + # so we can try to connect to the device first and retrieve the information + self.open_device(device_id) + ain_channels = self.analog_in_channels_count() + self.close_device() + self.logger.debug(f"Device {device_id} has {ain_channels} analog input channels.") + return list(range(0, ain_channels)) @cmp.CProcess.register_for_signal('_changed') def ain_buffer_size(self, device_id) -> int: @@ -97,69 +142,178 @@ class MPCaptDevice(cmp.CProcess): self.dwf.FDwfEnumConfigInfo(c_int(device_id), c_int(7), byref(cInfo)) return cInfo.value - @cmp.CProcess.register_for_signal('_changed') - def dwf_version(self): - self.logger.debug(f"Getting DWF version information...") - version = create_string_buffer(16) - self.dwf.FDwfGetVersion(version) - return version.value.decode("utf-8") - @cmp.CProcess.register_for_signal('_changed') def device_name(self, device_index: int) -> str: - devicename = create_string_buffer(64) - self.dwf.FDwfEnumDeviceName(c_int(device_index), devicename) - return str(devicename.value.decode("utf-8")) + try: + devicename = create_string_buffer(64) + self.dwf.FDwfEnumDeviceName(c_int(device_index), devicename) + return str(devicename.value.decode("utf-8")) + except Exception as e: + self.logger.error(f"Error while reading device name: {e}") + raise Exception(f"Error while reading device name: {e}") @cmp.CProcess.register_for_signal('_changed') def device_serial_number(self, device_index: int) -> str: - serialnum = create_string_buffer(16) - self.dwf.FDwfEnumSN(c_int(device_index), serialnum) - return str(serialnum.value.decode("utf-8")).replace("SN:", "") - + try: + serialnum = create_string_buffer(16) + self.dwf.FDwfEnumSN(c_int(device_index), serialnum) + return str(serialnum.value.decode("utf-8")).replace("SN:", "") + except Exception as e: + self.logger.error(f"Error while reading device serial number: {e}") + raise Exception(f"Error while reading device serial number: {e}") + + # ================================================================================================================== + # Device connection status + # ================================================================================================================== @cmp.CProcess.register_for_signal('_changed') def connected(self) -> bool: if self.hdwf is None or self.hdwf.value == 0: + szerr = create_string_buffer(512) + self.dwf.FDwfGetLastErrorMsg(szerr) + self.logger.error(str(szerr.value)) return False else: + self.logger.debug(f"Device connected: {self._device_name} ({self._device_serial_number})") return True + # ================================================================================================================== + # Analog Input Channel Information + # ================================================================================================================== + def analog_in_channels_count(self) -> int: + """ + Reads the number of AnalogIn channels of the device. The oscilloscope channel settings are + identical across all channels. + Calls WaveForms API Function 'FDwfAnalogInChannelCount(HDWF hdwf, int *pcChannel)' + :return: The number of analog in channels. + """ + if self.connected(): + try: + int0 = c_int() + self.dwf.FDwfAnalogInChannelCount(self.hdwf, byref(int0)) + _analog_in_channels = int(int0.value) + return _analog_in_channels + except Exception as e: + self.logger.error(f"Can not read the AnalogIn Channel Count. {e}") + raise Exception(f"Can not read the AnalogIn Channel Count. {e}") + else: + self.logger.error(f"Can not read the AnalogIn Channel Count. Device not connected.") + raise Exception(f"Can not read the AnalogIn Channel Count. Device not connected.") + + @cmp.CProcess.register_for_signal('_changed') + def analog_in_bits(self) -> int: + """ + Retrieves the number bits used by the AnalogIn ADC. The oscilloscope channel settings are identical + across all channels. + Calls WaveForms API Function 'FDwfAnalogInBitsInfo(HDWF hdwf, int *pnBits)' + :return: The number bits used by the AnalogIn ADC. + """ + int0 = c_int() + if self.connected(): + self.dwf.FDwfAnalogInBitsInfo(self.hdwf, byref(int0)) + _adc_bits = int(int0.value) + return _adc_bits + else: + self.logger.error(f"Can not read the AnalogIn Bits. Device not connected.") + raise Exception(f"Can not read the AnalogIn Bits. Device not connected.") + + @cmp.CProcess.register_for_signal('_changed') + def analog_in_buffer_size(self) -> tuple: + """ + Returns the minimum and maximum allowable buffer sizes for the instrument. The oscilloscope + channel settings are identical across all channels. + Calls WaveForms API Function 'FDwfAnalogInBufferSizeInfo(HDWF hdwf, int *pnSizeMin, int *pnSizeMax)' + :return: The minimum and maximum allowable buffer sizes for the instrument as a tuple (min, max). + """ + if self.connected(): + int0 = c_int() + int1 = c_int() + self.dwf.FDwfAnalogInBufferSizeInfo(self.hdwf, byref(int0), byref(int1)) + _buffer_size = (int(int0.value), int(int0.value)) + return _buffer_size + else: + self.logger.error(f"Can not read the AnalogIn Buffer Size. Device not connected.") + raise Exception(f"Can not read the AnalogIn Buffer Size. Device not connected.") + + @cmp.CProcess.register_for_signal('_changed') + def analog_in_channel_range_info(self) -> tuple: + """ + Returns the minimum and maximum range, peak to peak values, and the number of adjustable steps. + The oscilloscope channel settings are identical across all channels. + Calls WaveForms API Function + 'FDwfAnalogInChannelRangeInfo(HDWF hdwf, double *pvoltsMin, double *pvoltsMax, double *pnSteps)' + :return: The minimum and maximum range, peak to peak values, and the number of adjustable steps as a tuple + (min, max, steps). + """ + if self.connected: + dbl0 = c_double() + dbl1 = c_double() + dbl2 = c_double() + self.dwf.FDwfAnalogInChannelRangeInfo(self.hdwf, byref(dbl0), byref(dbl1), byref(dbl2)) + _range = (int(dbl0.value), int(dbl1.value), int(dbl2.value)) + return _range + else: + self.logger.error(f"Can not read the AnalogIn Channel Range. Device not connected.") + raise Exception(f"Can not read the AnalogIn Channel Range. Device not connected.") + + @cmp.CProcess.register_for_signal('_changed') + def analog_in_offset(self) -> tuple: + """ Returns the minimum and maximum offset levels supported, and the number of adjustable steps""" + if self.connected(): + dbl0 = c_double() + dbl1 = c_double() + dbl2 = c_double() + self.dwf.FDwfAnalogInChannelOffsetInfo(self.hdwf, byref(dbl0), byref(dbl1), byref(dbl2)) + _offset = (int(dbl0.value), int(dbl1.value), int(dbl2.value)) + return _offset + else: + self.logger.error(f"Can not read the AnalogIn Offset. Device not connected.") + raise Exception(f"Can not read the AnalogIn Offset. Device not connected.") + + # ================================================================================================================== + # Functions for opening and closing the device + # ================================================================================================================== @cmp.CProcess.register_for_signal() def open_device(self, device_index): """ Opens the device and returns the handle. :return: Device handle. """ + self.logger.debug(f"Opening device {device_index}...") self._dwf_version = self.dwf_version() - self._device_name = self.device_name(device_index) - self._device_serial_number = self.device_serial_number(device_index) # Opens the device specified by idxDevice. The device handle is returned in hdwf. If idxDevice is -1, the # first available device is opened. - self.logger.info(f"[Task] Opening device #{device_index}...") self.dwf.FDwfDeviceOpen(c_int(device_index), byref(self.hdwf)) + self._device_name = self.device_name(device_index) + self._device_serial_number = self.device_serial_number(device_index) + if self.hdwf.value == 0: szerr = create_string_buffer(512) self.dwf.FDwfGetLastErrorMsg(szerr) - self.logger.error(f"Failed to open device: {szerr.value}") + err = szerr.value.decode("utf-8") + self.logger.error(f"Failed to open device: {err}") # ad2_state.connected = False - raise Exception(f"Failed to open device: {szerr.value}") + raise Exception(f"Failed to open device: {err}") else: self.logger.info(f"Device opened: {self._device_name} " f"({self._device_serial_number})") self._connected = self.connected() def close_device(self): - #self.dwf.FDwfAnalogOutReset(self.hdwf, c_int(channel)) + # self.dwf.FDwfAnalogOutReset(self.hdwf, c_int(channel)) self.logger.info(f"[Task] Closing device...") self.dwf.FDwfDeviceClose(self.hdwf) self.hdwf.value = 0 self._connected = self.connected() self.logger.info(f"[Task] Device closed.") - def setup_aquisition(self, sample_rate: float, ain_channel: int): + # ================================================================================================================== + # Function for setting up the acquisition + # ================================================================================================================== + def setup_acquisition(self, sample_rate: float, ain_channel: int): self.dwf.FDwfAnalogInStatus(self.hdwf, c_int(1), - byref(self._ain_device_state)) # Variable to receive the acquisition state + byref(self._ain_device_state)) # Variable to receive the acquisition state self.logger.info(f"[Task] Setup for acquisition on channel {ain_channel} with rate {sample_rate} Hz.") self.dwf.FDwfAnalogInChannelEnableSet(self.hdwf, c_int(ain_channel), c_int(1)) self.dwf.FDwfAnalogInChannelRangeSet(self.hdwf, c_int(ain_channel), c_double(5)) @@ -168,177 +322,205 @@ class MPCaptDevice(cmp.CProcess): self.dwf.FDwfAnalogInRecordLengthSet(self.hdwf, 0) # -1 infinite record length # Variable to receive the acquisition state - self.dwf.FDwfAnalogInStatus(self.hdwf, c_int(1), byref(self._ain_device_state)) - self.logger.info(f"[Task] Wait 2 seconds for the offset to stabilize.") + #self.dwf.FDwfAnalogInStatus(self.hdwf, c_int(1), byref(self._ain_device_state)) + #self.logger.info(f"[Task] Wait 2 seconds for the offset to stabilize.") # wait at least 2 seconds for the offset to stabilize - time.sleep(2) - self.logger.info(f"[Task] Setup for acquisition done.") + #time.sleep(2) + #self.logger.info(f"[Task] Setup for acquisition done.") + + # ================================================================================================================== + # Python wrapper for WaveForms API Functions + # ================================================================================================================== + @timeit + def _dwf_analog_in_status(self, hdwf, read_data, ptr_device_state): + try: + _read_data_cint = c_int(int(read_data)) + self.dwf.FDwfAnalogInStatus(hdwf, _read_data_cint, ptr_device_state) + except Exception as e: + self.logger.error(f"Error while getting data from device: {e}") + raise Exception(f"Error while getting data from device: {e}") + return ptr_device_state + + @timeit + def _dwf_analog_in_status_record(self, hdwf, ptr_c_available, ptr_c_lost, ptr_c_corrupted): + """ + Retrieves information about the recording process. The data loss occurs when the device acquisition + is faster than the read process to PC. In this case, the device recording buffer is filled and data + samples are overwritten. Corrupt samples indicate that the samples have been overwritten by the + acquisition process during the previous read. + :param hdwf: Interface handle + :param c_available: Pointer to variable to receive the available number of samples. + :param c_lost: Pointer to variable to receive the lost samples after the last check. + :param c_corrupted:Pointer to variable to receive the number of samples that could be corrupt. + :return: + """ + try: + self.dwf.FDwfAnalogInStatusRecord(hdwf, ptr_c_available, ptr_c_lost, ptr_c_corrupted) + except Exception as e: + self.logger.error(f"Error while getting data from device: {e}") + raise Exception(f"Error while getting data from device: {e}") + return ptr_c_available, ptr_c_lost, ptr_c_corrupted + + @timeit + def _dwf_analog_in_status_data(self, hdwf, channel, ptr_rgd_samples, c_available): + """ + Retrieves the acquired data samples from the specified idxChannel on the AnalogIn instrument. It + copies the data samples to the provided buffer. + :param hdwf: Interface handle + :param channel: Channel index + :param rgd_samples: Pointer to allocated buffer to copy the acquisition data. + :return: + """ + try: + self.dwf.FDwfAnalogInStatusData(hdwf, c_int(channel), ptr_rgd_samples, c_available) # get channel data + except Exception as e: + self.logger.error(f"Error while getting data from device: {e}") + raise Exception(f"Error while getting data from device: {e}") + return ptr_rgd_samples, c_available def start_capture(self, sample_rate: float, ain_channel: int): """ Captures data from the device and puts it into a queue. - :param capture_data_queue: - :param state_queue: - :param start_capture: - :param end_process: - :param device_id: + :param ain_channel: :param sample_rate: - :param stream_data_queue: Queue to put the data into. - :param channel: Channel to capture data from. :return: None """ - # Streaming the data should only be set to 1000Hz, otherwise the UI will freeze. The capturing however should - # stay at the given sample rate. - # Using the modulo operation allow us to determine the variable stream_n that is required - # to scale down the streaming rate. - stream_rate = 1000 # Hz - # stream_n = sample_rate / stream_rate - # stream_sample_cnt = 0 - - time_capture_started = 0 - capturing_notified = False - # Print pid and ppid - self.logger.info(f"Starting capture thread, pid={os.getpid()}") - #ad2_state = AD2StateMPSetter(state_queue) - - #ad2_state.pid = os.getpid() - - #ad2_state.selected_ain_channel = channel - #ad2_state.sample_rate = sample_rate - #self.logger.debug(f"Setting up device {device_id} with " - # f"channel {ad2_state.selected_ain_channel} and " - # f"acquisition rate {ad2_state.sample_rate} Hz") - - #dwf, hdwf = _mp_open_device(device_id, ad2_state) - - # acquisition_state = c_byte() - - cAvailable = c_int() - cLost = c_int() - cCorrupted = c_int() - # FDwfAnalogInStatus(HDWF hdwf, BOOL fReadData, DwfState* psts) - self.setup_aquisition(ain_channel=ain_channel, sample_rate=sample_rate) - #_t_setup_sine_wave(dwf, hdwf, ad2_state) + self.setup_acquisition(ain_channel=ain_channel, sample_rate=sample_rate) + # Creates a Sin Wave on the Analog Out Channel 0 + self.setup_sine_wave(channel=0) self.logger.info("Configuring acquisition. Starting oscilloscope.") - # FDwfAnalogInConfigure(HDWF hdwf, int fReconfigure, int fStart) + # Configures the instrument and start or stop the acquisition. To reset the Auto trigger timeout, set - # fReconfigure to TRUE. - # hdwf – Interface handle. - # fReconfigure – Configure the device. - # fStart – Start the acquisition. self.dwf.FDwfAnalogInConfigure(self.hdwf, c_int(0), c_int(1)) - self.logger.info("Device configured. Starting acquisition.") - cSamples = 0 + time_capture_started = 0 capture_samples = 0 - #print(end_process.value) - # Checks the state of the acquisition. To read the data from the device, set fReadData to TRUE. For - # single acquisition mode, the data will be read only when the acquisition is finished - time_FDwfAnalogInStatus_start = time.time() - self.dwf.FDwfAnalogInStatus(self.hdwf, c_int(1), byref(self._ain_device_state)) - time_FDwfAnalogInStatus_stop = time.time() - time_FDwfAnalogInStatus = time_FDwfAnalogInStatus_stop - time_FDwfAnalogInStatus_start - print(f"FDwfAnalogInStatus took {time_FDwfAnalogInStatus} seconds") - - while True: - time_start = time.time() - print("New iteration") - - - - time_cSamples_start = time.time() - if cSamples == 0 and ( - self._ain_device_state == DwfStateConfig or - self._ain_device_state == DwfStatePrefill or - self._ain_device_state == DwfStateArmed): - self.logger.info("Device in idle state. Waiting for acquisition to start.") - continue # Acquisition not yet started. - time_cSamples_stop = time.time() - time_cSamples = time_cSamples_stop - time_cSamples_start - print(f"cSamples took {time_cSamples} seconds") - - - time_FDwfAnalogInStatusRecord_start = time.time() - self.dwf.FDwfAnalogInStatusRecord(self.hdwf, byref(cAvailable), byref(cLost), byref(cCorrupted)) - cSamples += cLost.value - time_FDwfAnalogInStatusRecord_stop = time.time() - time_FDwfAnalogInStatusRecord = time_FDwfAnalogInStatusRecord_stop - time_FDwfAnalogInStatusRecord_start - print(f"FDwfAnalogInStatusRecord took {time_FDwfAnalogInStatusRecord} seconds") - - time_samples_l_c_start = time.time() - if cLost.value: - self._samples_lost += cLost.value - if cCorrupted.value: - self._samples_corrupted += cCorrupted.value - time_samples_l_c_stop = time.time() - time_samples_l_c = time_samples_l_c_stop - time_samples_l_c_start - print(f"time_samples_l_c took {time_samples_l_c} seconds") - - # self.dwf.FDwfAnalogInStatusSamplesValid(self.hdwf, byref(self.cValid)) - if cAvailable.value == 0: - print(cAvailable.value) - continue - else: - # print(f"Available: {cAvailable.value}") - # if cSamples + cAvailable.value > self.ad2capt_model.n_samples: - # cAvailable = c_int(self.ad2capt_model.n_samples - cSamples) - time_rgdsamples_start = time.time() - rgdSamples = (c_double * cAvailable.value)() - time_rgdsamples_stop = time.time() - time_rgdsamples = time_rgdsamples_stop - time_rgdsamples_start - print(f"rgdSamples took {time_rgdsamples} seconds") - - time_FDwfAnalogInStatusData_start = time.time() - self.dwf.FDwfAnalogInStatusData(self.hdwf, c_int(ain_channel), byref(rgdSamples), cAvailable) # get channel data - time_FDwfAnalogInStatusData_stop = time.time() - time_FDwfAnalogInStatusData = time_FDwfAnalogInStatusData_stop - time_FDwfAnalogInStatusData_start - print(f"FDwfAnalogInStatusData took {time_FDwfAnalogInStatusData} seconds") - - # Print how many samples are available - status = {"available": cAvailable.value, 'captured': len(rgdSamples), 'lost': cLost.value, - 'corrupted': cCorrupted.value, "time": time.time()} - self.logger.debug(status) - print(f"took: {time.time() - time_start} seconds") - #self.stream_data_queue.put( - # ([(float(s)) for s in rgdSamples], status) - #) - - # if self.start_capture.value == int(True): - # if not capturing_notified: - # time_capture_started = time.time() - # capture_samples = 0 - # _mp_log_info("Starting command recieved. Acquisition started.") - # ad2_state.acquisition_state = AD2Constants.CapturingState.RUNNING() - # capturing_notified = True - # capture_samples = capture_samples + len(rgdSamples) - # status = { - # "available": cAvailable.value, - # "captured": capture_samples, - # "lost": cLost.value, - # "corrupted": cCorrupted.value, - # "recording_time": time.time() - time_capture_started} - # capture_data_queue.put(([float(s) for s in rgdSamples], status)) - # # capture_data_queue.put([float(s) for s in rgdSamples]) - # elif start_capture.value == 0: - # if capturing_notified: - # ad2_state.acquisition_state = AD2Constants.CapturingState.STOPPED() - # time_capture_stopped = time.time() - # time_captured = time_capture_stopped - time_capture_started - # ad2_state.recording_time = time_captured - # _mp_log_info(f"Acquisition stopped after {time_captured} seconds. Captured {capture_samples} " - # f"samples. Resulting in a time of {capture_samples / ad2_state.sample_rate} s.") - # status = { - # "available": cAvailable.value, - # "captured": capture_samples, - # "lost": cLost.value, - # "corrupted": cCorrupted.value, - # "recording_time": time.time() - time_capture_started} - # capture_data_queue.put(([float(s) for s in rgdSamples], status)) - # - # capturing_notified = False - cSamples += cAvailable.value - + capture_started = False + capture_ended = False + + n_samples = int((sample_rate*2)) + rgd_samples = (c_double * n_samples)() + #num_sent_samples = 0 + try: + self.dwf.FDwfAnalogOutReset(self.hdwf, c_int(0)) + + + while self.kill_capture_flag.value == int(False): + self._c_samples = 0 + + + time_start = time.time() + + # Checks the state of the acquisition. To read the data from the device, set fReadData to TRUE. For + # single acquisition mode, the data will be read only when the acquisition is finished + self.dwf.FDwfAnalogInStatus(self.hdwf, c_int(1), byref(self._ain_device_state)) + + if self._c_samples == 0 and ( + self._ain_device_state == DwfStateConfig or + self._ain_device_state == DwfStatePrefill or + self._ain_device_state == DwfStateArmed): + # self.logger.info("Device in idle state. Waiting for acquisition to start.") + continue # Acquisition not yet started. + + self.dwf.FDwfAnalogInStatusRecord(self.hdwf, + byref(self._c_available), + byref(self._c_lost), byref(self._c_corrupted) + ) + self._c_samples += self._c_lost.value + #if self._c_lost.value: + # self._samples_lost += self._c_lost.value + #if self._c_corrupted.value: + # self._samples_corrupted += self._c_corrupted.value + + # self.dwf.FDwfAnalogInStatusSamplesValid(self.hdwf, byref(self.cValid)) + if self._c_available.value == 0: + pass + #print("Nothing available") + #continue + + else: + if self._c_samples + self._c_available.value > n_samples: + self._c_available = c_int(n_samples - self._c_samples) + + # print(f"Available: {self._c_available.value}") + # if cSamples + cAvailable.value > self.ad2capt_model.n_samples: + # cAvailable = c_int(self.ad2capt_model.n_samples - cSamples) + # time_rgdsamples_start = time.time() + + # arr = [None] * cAvailable.value + # time_rgdsamples_stop = time.time() + # time_rgdsamples = time_rgdsamples_stop - time_rgdsamples_start + # print(f"rgd_samples took {time_rgdsamples} seconds") + rgd_samples = (c_double * self._c_available.value)() + # Get the data from the device and store it in rgd_samples + self.dwf.FDwfAnalogInStatusData(self.hdwf, + c_int(ain_channel), + rgd_samples, + self._c_available) + #print(f"Got data from device: {self._c_available.value}") + self._c_samples += self._c_available.value + iteration_time = time.time() - time_start + + # Convert the data to a numpy array and put it into the queue + # self.logger.info("Convert data to numpy array and put it into the queue.") + + # num_sent_samples = 0 + arr = np.array(rgd_samples) + #print(f"I send {len(arr)} samples to the queue.") + self.stream_data_queue.put(arr) + + if self.start_capture_flag.value == int(True): + if not capture_started: + self.device_capturing(True) + time_capture_started = time.time() + self.logger.info( + "**************************** Starting command received. Acquisition started.") + capture_started = True + capture_ended = False + capture_samples = 0 + #capture_samples = capture_samples + len(arr) + #self.capture_data_queue.put(arr) + elif self.start_capture_flag.value == int(False): + + if not capture_ended and capture_started: + self.device_capturing(False) + time_capture_stopped = time.time() + time_captured = time_capture_stopped - time_capture_started + self.logger.info( + "**************************** Stopping command received. Acquisition stopped.") + self.logger.info( + f"Acquisition stopped after {time_captured} seconds. Captured {capture_samples} " + f"samples. Resulting in a time of {capture_samples / sample_rate} s.") + capture_ended = True + capture_started = False + #self.capture_data_queue.put(arr) + # self._c_samples += self._c_available.value + + + + except Exception as e: + self.logger.error(f"Error while capturing data from device: {e}") + raise Exception(f"Error while capturing data from device: {e}") + self.logger.info("Capture thread ended.") self.close_device() + + # ================================================================================================================== + # Others + # ================================================================================================================== + def setup_sine_wave(self, channel: int = 0): + self.logger.debug("Generating AM sine wave...") + self.dwf.FDwfAnalogOutNodeEnableSet(self.hdwf, c_int(0), c_int(0), c_int(1)) # carrier + self.dwf.FDwfAnalogOutNodeFunctionSet(self.hdwf, c_int(0), c_int(0), c_int(1)) # sine + self.dwf.FDwfAnalogOutNodeFrequencySet(self.hdwf, c_int(0), c_int(0), c_double(0.1)) + self.dwf.FDwfAnalogOutNodeAmplitudeSet(self.hdwf, c_int(0), c_int(0), c_double(1)) + # dwf.FDwfAnalogOutNodeOffsetSet(hdwf, c_int(0), c_int(0), c_double(0.5)) + # dwf.FDwfAnalogOutNodeEnableSet(hdwf, c_int(0), c_int(2), c_int(1)) # AM + # dwf.FDwfAnalogOutNodeFunctionSet(hdwf, c_int(0), c_int(2), c_int(3)) # triangle + # dwf.FDwfAnalogOutNodeFrequencySet(hdwf, c_int(0), c_int(2), c_double(0.1)) + # dwf.FDwfAnalogOutNodeAmplitudeSet(hdwf, c_int(0), c_int(2), c_double(50)) + self.dwf.FDwfAnalogOutConfigure(self.hdwf, c_int(channel), c_int(1)) + time.sleep(1) + self.logger.debug(f"Sine wave on output channel {channel} configured.") diff --git a/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDeviceControl.py b/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDeviceControl.py index e5fc099b8365e8812f8d52ace0f60538322aea07..5b73a813bf1cdb3f0fb0847cac6d007ec6e8ab63 100644 --- a/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDeviceControl.py +++ b/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDeviceControl.py @@ -1,8 +1,11 @@ +import os + import cmp from PySide6.QtCore import Signal from CaptDeviceControl.controller.mp_AD2Capture.MPCaptDevice import MPCaptDevice from model.AD2CaptDeviceModel import AD2CaptDeviceSignals, AD2CaptDeviceModel +from model.AD2Constants import AD2Constants class MPCaptDeviceControl(cmp.CProcessControl): @@ -17,45 +20,75 @@ class MPCaptDeviceControl(cmp.CProcessControl): open_device_finished = Signal() close_device_finished = Signal() + analog_in_bits_changed = Signal(int) + analog_in_buffer_size_changed = Signal(int) + analog_in_channel_range_changed = Signal(tuple) + analog_in_offset_changed = Signal(tuple) + + device_capturing_changed = Signal(bool) + def __init__(self, model: AD2CaptDeviceModel, streaming_data_queue, capturing_data_queue, start_capture_flag, + kill_capture_flag, parent=None, - enable_logging=False): - super().__init__(parent, enable_logging=enable_logging) + enable_internal_logging=False): + super().__init__(parent, enable_internal_logging=enable_internal_logging) + self.model = model self.register_child_process( MPCaptDevice(self.state_queue, self.cmd_queue, streaming_data_queue, capturing_data_queue, start_capture_flag, - enable_logging=enable_logging)) + kill_capture_flag, + enable_internal_logging=enable_internal_logging)) + + self.logger, self.logger_handler = self.create_new_logger(f"{self.__class__.__name__}({os.getpid()})") + + self.connected_devices_changed.connect( + lambda x: type(model.device_information).connected_devices.fset(model.device_information, x)) + self.dwf_version_changed.connect( + lambda x: type(model).dwf_version.fset(model, x)) + self.device_name_changed.connect( + lambda x: type(model.device_information).device_name.fset(model.device_information, x)) + self.device_serial_number_changed.connect( + lambda x: type(model.device_information).device_serial_number.fset(model.device_information, x)) + self.connected_changed.connect( + lambda x: type(model.device_information).device_connected.fset(model.device_information, x)) + + # Analog In Information + self.ain_channels_changed.connect(lambda x: type(model.analog_in).ain_channels.fset(model.analog_in, x)) + self.ain_buffer_size_changed.connect(lambda x: type(model.analog_in).ain_buffer_size.fset(model.analog_in, x)) + self.analog_in_bits_changed.connect(lambda x: type(model.analog_in).ain_bits.fset(model.analog_in, x)) + self.analog_in_buffer_size_changed.connect(lambda x: type(model.analog_in).ain_buffer_size.fset(model.analog_in, x)) + self.analog_in_channel_range_changed.connect(lambda x: type(model.analog_in).ai.fset(model.analog_in, x)) + self.analog_in_offset_changed.connect(lambda x: type(model.analog_in).ain_offset.fset(model.analog_in, x)) - self.connected_devices_changed.connect(lambda x: type(model).connected_devices.fset(model, x)) - self.ain_channels_changed.connect(lambda x: type(model).ain_channels.fset(model, x)) - self.ain_buffer_size_changed.connect(lambda x: type(model).ain_buffer_size.fset(model, x)) - self.dwf_version_changed.connect(lambda x: type(model).dwf_version.fset(model, x)) - self.device_name_changed.connect(lambda x: type(model).device_name.fset(model, x)) - self.device_serial_number_changed.connect(lambda x: type(model).device_serial_number.fset(model, x)) - self.connected_changed.connect(lambda x: type(model).connected.fset(model, x)) + self.device_capturing_changed.connect(self.on_capturing_state_changed) + def on_capturing_state_changed(self, capturing: bool): + if capturing: + self.model.device_capturing_state = AD2Constants.CapturingState.RUNNING() + else: + self.model.device_capturing_state = AD2Constants.CapturingState.STOPPED() @cmp.CProcessControl.register_function() def connected_devices(self): - self._internal_logger.info("Discovering connected devices.") + self.logger.info("Discovering connected devices.") @cmp.CProcessControl.register_function() def ain_channels(self, device_id): - self._internal_logger.info(f"Reading available analog input channels for device {device_id}.") + self.logger.info(f"Reading available analog input channels for device {device_id}.") @cmp.CProcessControl.register_function() def open_device(self, device_index): - self._internal_logger.info(f"Opening device {device_index}.") + self.logger.info(f"Opening device {device_index}.") @cmp.CProcessControl.register_function() def close_device(self): - self._internal_logger.info(f"Closing device device.") + self.logger.info(f"Closing device device.") @cmp.CProcessControl.register_function() def start_capture(self, sample_rate, ain_channel): diff --git a/src/CaptDeviceControl/controller/mp_AD2Capture/MPDeviceControl.py b/src/CaptDeviceControl/controller/mp_AD2Capture/MPDeviceControl.py index 9aadab180388d634ac475dbb4d7fe239ad2ee1fd..e6692352a63dd679fd140a890b982315c9211b2d 100644 --- a/src/CaptDeviceControl/controller/mp_AD2Capture/MPDeviceControl.py +++ b/src/CaptDeviceControl/controller/mp_AD2Capture/MPDeviceControl.py @@ -9,7 +9,7 @@ import os import random import sys import time -from ctypes import c_int, byref, c_double, cdll, create_string_buffer, c_int32 +from ctypes import c_int, byref, c_double, cdll, create_string_buffer, c_int32, CDLL from CaptDeviceControl.controller.mp_AD2Capture.AD2StateMPSetter import AD2StateMPSetter from CaptDeviceControl.model.AD2Constants import AD2Constants @@ -54,25 +54,20 @@ def mp_capture(stream_data_queue, capture_data_queue, state_queue, :param channel: Channel to capture data from. :return: None """ - # Streaming the data should only be set to 1000Hz, otherwise the UI will freeze. The capturing however should - # stay at the given sample rate. - # Using the modulo operation allow us to determine the variable stream_n that is required - # to scale down the streaming rate. - stream_rate = 1000 # Hz - #stream_n = sample_rate / stream_rate - #stream_sample_cnt = 0 time_capture_started = 0 capturing_notified = False - # Print pid and ppid - _mp_log_info(f"Starting capture thread, pid={os.getpid()}, ppid={os.getppid()}") - ad2_state = AD2StateMPSetter(state_queue) + ad2_state = AD2StateMPSetter(state_queue) ad2_state.pid = os.getpid() + ad2_state.ppid = os.getppid() + # Print pid and ppid + _mp_log_info(f"Starting capture thread, pid={ad2_state.pid}, ppid={ad2_state.ppid}") ad2_state.selected_ain_channel = channel ad2_state.sample_rate = sample_rate - _mp_log_debug(f"Setting up device {device_id} with " + ad2_state.device_index = device_id + _mp_log_debug(f"Setting up device {ad2_state.device_index} with " f"channel {ad2_state.selected_ain_channel} and " f"acquisition rate {ad2_state.sample_rate} Hz") @@ -250,25 +245,12 @@ def _t_setup_aquisition(dwf, hdwf, ad2_state: AD2StateMPSetter): def _mp_discover_connected_devices(dwf): _mp_log_info(f"Discovering connected devices...") - # enumerate connected devices - connected_devices = [] - # for filter_type in [(c_int32(enumfilterType.value | enumfilterUSB.value), 'USB'), - # (c_int32(enumfilterType.value | enumfilterNetwork.value), 'Network'), - # (c_int32(enumfilterType.value | enumfilterAXI.value), 'AXI'), - # (c_int32(enumfilterType.value | enumfilterRemote.value), 'Remote'), - # (c_int32(enumfilterType.value | enumfilterAudio.value), 'Audio'), - # (c_int32(enumfilterType.value | enumfilterDemo.value), 'Demo')]: - cDevice = c_int() - filter, type = (c_int32(enumfilterType.value | enumfilterDemo.value| enumfilterUSB.value), 'USB') - # filter, type = (c_int32(enumfilterType.value | enumfilterDemo.value), 'DEMO') - _mp_log_debug(f"Filtering {type} devices...") - dwf.FDwfEnum(filter, byref(cDevice)) - num_of_connected_devices = cDevice + num_of_connected_devices = 0 devicename = create_string_buffer(64) serialnum = create_string_buffer(16) - for iDevice in range(0, cDevice.value): + for iDevice in enumerate_devices(dwf, show_demo_devices=True): dwf.FDwfEnumDeviceName(c_int(iDevice), devicename) dwf.FDwfEnumSN(c_int(iDevice), serialnum) connected_devices.append({ @@ -282,8 +264,25 @@ def _mp_discover_connected_devices(dwf): # print(f"Discoverd {len(self.model.connected_devices)} devices.") return connected_devices +def enumerate_devices(dwf: CDLL, show_demo_devices=False) -> list: + """ + Enumerates all connected devices. Function is used to discover all connected, compatible devices. + Builds an internal list of detected devices filtered by the enumfilter parameter. It must be called + before using other FDwfEnum functions because they obtain information about enumerated devices + from this list identified by the device index. + :param show_demo_devices: Specify if demo devices should be shown. + :return: A list from 0 to n devices. + """ + if show_demo_devices: + enum_filter = c_int32(enumfilterType.value | enumfilterUSB.value | enumfilterDemo.value) + else: + enum_filter = c_int32(enumfilterType.value | enumfilterUSB.value) + + cDevice = c_int() + dwf.FDwfEnum(enum_filter, byref(cDevice)) + return list(range(0, cDevice.value)) -def _mp_open_device(device_index, ad2_state: AD2StateMPSetter): +def _mp_open_device(ad2_state: AD2StateMPSetter): """ Opens the device and returns the handle. :return: Device handle. @@ -303,14 +302,14 @@ def _mp_open_device(device_index, ad2_state: AD2StateMPSetter): # Opens the device specified by idxDevice. The device handle is returned in hdwf. If idxDevice is -1, the # first available device is opened. - _mp_log_info(f"[Task] Opening device #{device_index}...") - dwf.FDwfDeviceOpen(c_int(device_index), byref(hdwf)) + _mp_log_info(f"[Task] Opening device #{ad2_state.device_index}...") + dwf.FDwfDeviceOpen(c_int(ad2_state.device_index), byref(hdwf)) devicename = create_string_buffer(64) serialnum = create_string_buffer(16) - dwf.FDwfEnumDeviceName(c_int(device_index), devicename) - dwf.FDwfEnumSN(c_int(device_index), serialnum) + dwf.FDwfEnumDeviceName(c_int(ad2_state.device_index), devicename) + dwf.FDwfEnumSN(c_int(ad2_state.device_index), serialnum) ad2_state.device_name = str(devicename.value.decode("utf-8")) ad2_state.device_serial_number = str(serialnum.value.decode("utf-8")).replace("SN:", "") diff --git a/src/CaptDeviceControl/model/AD2CaptDeviceAnalogInModel.py b/src/CaptDeviceControl/model/AD2CaptDeviceAnalogInModel.py new file mode 100644 index 0000000000000000000000000000000000000000..c22edf93a6eae905a01fa5d2b1222f8c499685b5 --- /dev/null +++ b/src/CaptDeviceControl/model/AD2CaptDeviceAnalogInModel.py @@ -0,0 +1,86 @@ +from ctypes import c_int, c_byte + +from PySide6.QtCore import QObject, Signal +from CaptDeviceControl.CaptDeviceConfig import CaptDeviceConfig +class AD2CaptDeviceAnalogInSignals(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + # Analog In Information + selected_ain_channel_changed = Signal(int) + ain_channels_changed = Signal(list) + ain_bits_changed = Signal(int) + ain_buffer_size_changed = Signal(int) + ain_channel_range_changed = Signal(tuple) + ain_offset_changed = Signal(tuple) + ain_device_state_changed = Signal(int) + + +class AD2CaptDeviceAnalogInModel: + + def __init__(self, config: CaptDeviceConfig): + self.signals = AD2CaptDeviceAnalogInSignals() + self.config = config + # Analog In Information + self._ain_channels: list = [] + self._ain_bits: int = -1 + self._ain_buffer_size: tuple = (-1, -1) + self._ain_channel_range: tuple = (-1, -1, -1) + self._ain_offset: tuple = (-1, -1, -1) + self._ain_device_state: int = -1 + + # ================================================================================================================== + # Analog In Information + # ================================================================================================================== + @property + def selected_ain_channel(self) -> int: + return self.config.ain_channel.get() + + @selected_ain_channel.setter + def selected_ain_channel(self, value: int | c_int): + if isinstance(value, c_int): + value = int(value.value) + else: + value = int(value) + self.config.ain_channel.set(value) + self.signals.selected_ain_channel_changed.emit(self.selected_ain_channel) + + + @property + def ain_channels(self) -> list: + return self._ain_channels + + @ain_channels.setter + def ain_channels(self, value: list): + self._ain_channels = value + self.signals.ain_channels_changed.emit(self.ain_channels) + + @property + def ain_buffer_size(self) -> tuple: + return self._ain_buffer_size + + @ain_buffer_size.setter + def ain_buffer_size(self, value: float): + self._ain_buffer_size = value + self.signals.ain_buffer_size_changed.emit(self.ain_buffer_size) + + @property + def ain_bits(self) -> int: + return self._ain_bits + + @ain_bits.setter + def ain_bits(self, value: int): + self._ain_bits = value + self.signals.ain_bits_changed.emit(self.ain_bits) + + @property + def ain_device_state(self) -> int: + return self._ain_device_state + + @ain_device_state.setter + def ain_device_state(self, value: c_int | int | c_byte): + if isinstance(value, c_int) or isinstance(value, c_byte): + self._ain_device_state = int(value.value) + else: + self._ain_device_state = int(value) + self.signals.ain_device_state_changed.emit(self.ain_device_state) \ No newline at end of file diff --git a/src/CaptDeviceControl/model/AD2CaptDeviceInformationModel.py b/src/CaptDeviceControl/model/AD2CaptDeviceInformationModel.py new file mode 100644 index 0000000000000000000000000000000000000000..0e0734369f20745e1b067661b5cc2b75cc969280 --- /dev/null +++ b/src/CaptDeviceControl/model/AD2CaptDeviceInformationModel.py @@ -0,0 +1,117 @@ +from ctypes import c_int, Array + +from PySide6.QtCore import QObject, Signal + + +class AD2CaptDeviceInformationSignals(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + # Connected Device Information + num_of_connected_devices_changed = Signal(int) + connected_devices_changed = Signal(list) + selected_device_index_changed = Signal(int) + + # Device information + device_connected_changed = Signal(bool) + device_name_changed = Signal(str) + device_serial_number_changed = Signal(str) + device_index_changed = Signal(int) + + +class AD2CaptDeviceInformationModel: + def __init__(self): + self.signals = AD2CaptDeviceInformationSignals() + + # Connected Device Information + self._num_of_connected_devices: int = 0 + self._connected_devices: list = [] + + # Device Information + self._selected_device_index: int = 0 + self._device_connected: bool = False + self._device_name: str = "Unknown" + self._device_serial_number: str = "Unknown" + + # ================================================================================================================== + # Connected Device Information + # ================================================================================================================== + @property + def num_of_connected_devices(self) -> int: + return self._num_of_connected_devices + + @num_of_connected_devices.setter + def num_of_connected_devices(self, value: c_int | int): + if isinstance(value, c_int): + self._num_of_connected_devices = int(value.value) + else: + self._num_of_connected_devices = int(value) + self.signals.num_of_connected_devices_changed.emit(self._num_of_connected_devices) + + @property + def connected_devices(self) -> list: + return self._connected_devices + + @connected_devices.setter + def connected_devices(self, value: list): + self._connected_devices = value + self.signals.connected_devices_changed.emit(self.connected_devices) + + @property + def selected_device_index(self) -> int: + return self._selected_device_index + + @selected_device_index.setter + def selected_device_index(self, value: int): + self._selected_device_index = value + self.signals.selected_device_index_changed.emit(self.selected_device_index) + + # ================================================================================================================== + # Device Information + # ================================================================================================================== + @property + def device_connected(self): + return self._device_connected + + @device_connected.setter + def device_connected(self, value): + self._device_connected = value + self.signals.device_connected_changed.emit(self._device_connected) + + @property + def device_name(self) -> str: + return self._device_name + + @device_name.setter + def device_name(self, value: Array | str): + if not isinstance(value, str): + self._device_name = str(value.value.decode('UTF-8')) + else: + self._device_name = str(value) + self.signals.device_name_changed.emit(self.device_name) + + @property + def device_serial_number(self) -> str: + return self._device_serial_number + + @device_serial_number.setter + def device_serial_number(self, value: Array | str): + if not isinstance(value, str): + self._device_serial_number = str(value.value.decode('UTF-8')) + else: + self._device_serial_number = str(value) + (self.signals. + + device_serial_number_changed.emit(self.device_serial_number)) + + @property + def device_index(self) -> int: + return self._device_index + + @device_index.setter + def device_index(self, value: c_int | int): + if isinstance(value, c_int): + self._device_index = int(value.value) + else: + self._device_index = int(value) + self.signals.device_serial_number_changed.emit(self.device_index) diff --git a/src/CaptDeviceControl/model/AD2CaptDeviceModel.py b/src/CaptDeviceControl/model/AD2CaptDeviceModel.py index 3b9f0dc82c3100e03ba4d0e9f295e1729032441a..5a9c6bc1d3c47d8072ac0f2d993f9cc3b8f57433 100644 --- a/src/CaptDeviceControl/model/AD2CaptDeviceModel.py +++ b/src/CaptDeviceControl/model/AD2CaptDeviceModel.py @@ -4,11 +4,16 @@ from PySide6.QtCore import QObject, Signal from CaptDeviceControl.model.AD2Constants import AD2Constants from CaptDeviceControl.CaptDeviceConfig import CaptDeviceConfig as Config -#from MeasurementData.Properties.AD2CaptDeviceProperties import AD2CaptDeviceProperties +from CaptDeviceControl.model.AD2CaptDeviceAnalogInModel import AD2CaptDeviceAnalogInModel +from CaptDeviceControl.model.AD2CaptDeviceInformationModel import AD2CaptDeviceInformationSignals, \ + AD2CaptDeviceInformationModel + + +# from MeasurementData.Properties.AD2CaptDeviceProperties import AD2CaptDeviceProperties class AD2CaptDeviceSignals(QObject): - def __init__(self, parent = None): + def __init__(self, parent=None): super().__init__(parent) ad2captdev_config_changed = Signal(Config) @@ -16,29 +21,11 @@ class AD2CaptDeviceSignals(QObject): # WaveForms Runtime (DWF) Information dwf_version_changed = Signal(str) - # Connected Device Information - num_of_connected_devices_changed = Signal(int) - connected_devices_changed = Signal(list) - selected_device_changed = Signal(int) - - # Device information - connected_changed = Signal(bool) - device_name_changed = Signal(str) - device_serial_number_changed = Signal(str) - device_index_changed = Signal(int) - # Acquisition Settings sample_rate_changed = Signal(int) streaming_rate_changed = Signal(int) - selected_ain_channel_changed = Signal(int) - duration_streaming_history_changed = Signal(int) - - # Analog In Information - ain_channels_changed = Signal(list) - ain_buffer_size_changed = Signal(int) - ain_bits_changed = Signal(int) - ain_device_state_changed = Signal(int) + duration_streaming_history_changed = Signal(int) # Analog Out Information aout_channels_changed = Signal(list) @@ -63,17 +50,13 @@ class AD2CaptDeviceSignals(QObject): # Multiprocessing Information pid_changed = Signal(int) - # ================================================================================================================== # Delete later # Signals for device information hwdf_changed = Signal(int) - device_ready_changed = Signal(bool) - - # Signals for reporting if samples were lost or corrupted fLost_changed = Signal(int) fCorrupted_changed = Signal(int) @@ -83,13 +66,11 @@ class AD2CaptDeviceSignals(QObject): # Recording settings for starting, stopping and pausing - # Signal if new samples have been aquired all_recorded_samples_changed = Signal(list) num_of_current_recorded_samples_changed = Signal(int) measurement_time_changed = Signal(float) - ad2_settings = Signal(dict) error = Signal(str) @@ -99,8 +80,6 @@ class AD2CaptDeviceSignals(QObject): ad2_captured_value = Signal(list) - - class AD2CaptDeviceModel: def __init__(self, ad2captdev_config: Config): @@ -110,31 +89,15 @@ class AD2CaptDeviceModel: # WaveForms Runtime (DWF) Information self._dwf_version: str = "Unknown" - # Connected Device Information - self._num_of_connected_devices: int = 0 - self._connected_devices: list = [] - self._selected_device: int = 0 - - # Device Information - self._connected: bool = False - self._device_name: str = "Unknown" - self._device_serial_number: str = "Unknown" - self._device_index: int = -1 + self.device_information = AD2CaptDeviceInformationModel() + self.analog_in = AD2CaptDeviceAnalogInModel(self.ad2captdev_config) # Acquisition Settings self._sample_rate: int = self.ad2captdev_config.sample_rate.value self._streaming_rate: int = self.ad2captdev_config.streaming_rate.value - self._selected_ain_channel: int = 0 self._duration_streaming_history: float = 0 - - # Analog In Information - self._ain_channels: list = [] - self._ain_buffer_size: float = -1 - self._ain_device_state: int = -1 - self._ain_bits: int = -1 - # Analog Out Information self.aout_channels: list = [] @@ -150,7 +113,6 @@ class AD2CaptDeviceModel: self._unconsumed_stream_samples: int = 0 self._unconsumed_capture_samples: int = 0 - # Recording Flags (starting, stopping and pausing) self._device_capturing_state: AD2Constants.CapturingState = AD2Constants.CapturingState.STOPPED() self._start_recording = False @@ -181,8 +143,6 @@ class AD2CaptDeviceModel: self._num_of_current_recorded_samples: int = 0 self._n_samples: int = 0 - - @property def ad2captdev_config(self) -> Config: return self._ad2captdev_config @@ -207,89 +167,6 @@ class AD2CaptDeviceModel: self._dwf_version = value self.signals.dwf_version_changed.emit(self.dwf_version) - # ================================================================================================================== - # Connected Device Information - # ================================================================================================================== - @property - def num_of_connected_devices(self) -> int: - return self._num_of_connected_devices - - @num_of_connected_devices.setter - def num_of_connected_devices(self, value: c_int | int): - if isinstance(value, c_int): - self._num_of_connected_devices = int(value.value) - else: - self._num_of_connected_devices = int(value) - self.signals.num_of_connected_devices_changed.emit(self._num_of_connected_devices) - - @property - def connected_devices(self) -> list: - return self._connected_devices - - @connected_devices.setter - def connected_devices(self, value: list): - self._connected_devices = value - self.signals.connected_devices_changed.emit(self.connected_devices) - - @property - def selected_device(self) -> int: - return self._selected_device - - @selected_device.setter - def selected_device(self, value: int): - self._selected_device = value - self.signals.selected_device_changed.emit(self.selected_device) - - - # ================================================================================================================== - # Device Information - # ================================================================================================================== - @property - def connected(self): - return self._connected - - @connected.setter - def connected(self, value): - self._connected = value - self.signals.connected_changed.emit(self._connected) - - @property - def device_name(self) -> str: - return self._device_name - - @device_name.setter - def device_name(self, value: Array | str): - if not isinstance(value, str): - self._device_name = str(value.value.decode('UTF-8')) - else: - self._device_name = str(value) - self.signals.device_name_changed.emit(self.device_name) - - @property - def device_serial_number(self) -> str: - return self._device_serial_number - - @device_serial_number.setter - def device_serial_number(self, value: Array | str): - if not isinstance(value, str): - self._device_serial_number = str(value.value.decode('UTF-8')) - else: - self._device_serial_number = str(value) - self.signals.device_serial_number_changed.emit(self.device_serial_number) - - @property - def device_index(self) -> int: - return self._device_index - - @device_index.setter - def device_index(self, value: c_int | int): - if isinstance(value, c_int): - self._device_index = int(value.value) - else: - self._device_index = int(value) - self.signals.device_serial_number_changed.emit(self.device_index) - - # ================================================================================================================== # Acquisition Settings # ================================================================================================================== @@ -313,19 +190,6 @@ class AD2CaptDeviceModel: self.ad2captdev_config.streaming_rate.set(self._streaming_rate) self.signals.streaming_rate_changed.emit(self._streaming_rate) - @property - def selected_ain_channel(self) -> int: - return self._selected_ain_channel - - @selected_ain_channel.setter - def selected_ain_channel(self, value: int | c_int): - if isinstance(value, c_int): - self._selected_ain_channel = int(value.value) - else: - self._selected_ain_channel = int(value) - self.ad2captdev_config.ain_channel.set(self._selected_ain_channel) - self.signals.selected_ain_channel_changed.emit(self.selected_ain_channel) - @property def duration_streaming_history(self) -> float: return self._duration_streaming_history @@ -335,48 +199,6 @@ class AD2CaptDeviceModel: self._duration_streaming_history = value self.signals.duration_streaming_history_changed.emit(self.duration_streaming_history) - # ================================================================================================================== - # Analog In Information - # ================================================================================================================== - @property - def ain_channels(self) -> list: - return self._ain_channels - - @ain_channels.setter - def ain_channels(self, value: list): - self._ain_channels = value - self.signals.ain_channels_changed.emit(self.ain_channels) - - @property - def ain_buffer_size(self) -> float: - return self._ain_buffer_size - - @ain_buffer_size.setter - def ain_buffer_size(self, value: float): - self._ain_buffer_size = value - self.signals.ain_buffer_size_changed.emit(self.ain_buffer_size) - - @property - def ain_bits(self) -> int: - return self._ain_bits - - @ain_bits.setter - def ain_bits(self, value: int): - self._ain_bits = value - self.signals.ain_bits_changed.emit(self.ain_bits) - - @property - def ain_device_state(self) -> int: - return self._ain_device_state - - @ain_device_state.setter - def ain_device_state(self, value: c_int | int | c_byte): - if isinstance(value, c_int) or isinstance(value, c_byte): - self._ain_device_state = int(value.value) - else: - self._ain_device_state = int(value) - self.signals.ain_device_state_changed.emit(self.ain_device_state) - # ================================================================================================================== # Analog Out Information # ================================================================================================================== @@ -389,7 +211,6 @@ class AD2CaptDeviceModel: self._aout_channels = value self.signals.aout_channels_changed.emit(self.aout_channels) - # ================================================================================================================== # Acquired Signal Information # ================================================================================================================== @@ -401,7 +222,7 @@ class AD2CaptDeviceModel: def recorded_samples(self, value: list): self._recorded_samples = value self.samples_captured = len(self._recorded_samples) - #self.signals.num_of_current_recorded_samples_changed.emit(self.num_of_current_recorded_samples) + # self.signals.num_of_current_recorded_samples_changed.emit(self.num_of_current_recorded_samples) self.signals.recorded_samples_changed.emit(self.recorded_samples) @property @@ -440,17 +261,15 @@ class AD2CaptDeviceModel: self._samples_corrupted = value self.signals.samples_corrupted_changed.emit(self.samples_corrupted) - @property def capturing_finished(self) -> bool: return self._capturing_finished - + @capturing_finished.setter def capturing_finished(self, value: bool): self._capturing_finished = value - print(f"Set _capturing_finished to { self._capturing_finished}") + print(f"Set _capturing_finished to {self._capturing_finished}") self.signals.capturing_finished_changed.emit(self._capturing_finished) - # ================================================================================================================== # Recording Flags (starting, stopping and pausing) @@ -491,7 +310,6 @@ class AD2CaptDeviceModel: self._reset_recording = value self.signals.reset_recording_changed.emit(self._reset_recording) - # ================================================================================================================== # Multiprocessing Flags # ================================================================================================================== @@ -522,8 +340,6 @@ class AD2CaptDeviceModel: self._unconsumed_capture_samples = value self.signals.unconsumed_capture_samples_changed.emit(self.unconsumed_capture_samples) - - # ================================================================================================================== # ================================================================================================================== # DELETE LATER @@ -538,7 +354,7 @@ class AD2CaptDeviceModel: self._auto_connect = value @property - #def ad2_properties(self) -> AD2CaptDeviceProperties: + # def ad2_properties(self) -> AD2CaptDeviceProperties: # return AD2CaptDeviceProperties(self._fLost, self._fCorrupted, # self._sample_rate, self._n_samples, # self._measurement_time) @@ -593,7 +409,6 @@ class AD2CaptDeviceModel: self._all_recorded_samples = value self.signals.all_recorded_samples_changed.emit(self.all_recorded_samples) - @property def n_samples(self): return self._n_samples @@ -620,5 +435,3 @@ class AD2CaptDeviceModel: def fLost(self, value): self._fLost = value self.signals.fLost_changed.emit(self._fLost) - - diff --git a/src/CaptDeviceControl/model/AD2CaptDeviceSignals.py b/src/CaptDeviceControl/model/AD2CaptDeviceSignals.py new file mode 100644 index 0000000000000000000000000000000000000000..81f119f75525d46bf37bfaddff45d65e97a7399b --- /dev/null +++ b/src/CaptDeviceControl/model/AD2CaptDeviceSignals.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +""" +from PySide6.QtCore import QObject, Signal + +from CaptDeviceConfig import CaptDeviceConfig as Config +from controller.DeviceInformation.HWDeviceInformation import HWDeviceInformation +from model.AD2Constants import AD2Constants + + +class AD2CaptDeviceSignals(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + ad2captdev_config_changed = Signal(Config) + + # WaveForms Runtime (DWF) Information + dwf_version_changed = Signal(str) + + # Connected Device Information + num_of_discovered_devices_changed = Signal(int) + discovered_devices_changed = Signal(list) + selected_devices_changed = Signal(HWDeviceInformation) + + # Device information + connected_changed = Signal(bool) + device_name_changed = Signal(str) + serial_number_changed = Signal(str) + device_index_changed = Signal(int) + + # Acquisition Settings + sample_rate_changed = Signal(int) + streaming_rate_changed = Signal(int) + selected_ain_channel_changed = Signal(int) + duration_streaming_history_changed = Signal(int) + + # Analog In Information + ain_channels_changed = Signal(list) + ain_buffer_size_changed = Signal(int) + ain_bits_changed = Signal(int) + ain_device_state_changed = Signal(int) + + # Analog Out Information + aout_channels_changed = Signal(list) + + # Acquired Signal Information + recorded_samples_changed = Signal(list) + recording_time_changed = Signal(float) + samples_captured_changed = Signal(int) + samples_lost_changed = Signal(int) + samples_corrupted_changed = Signal(int) + # Actually for the worker, these are the samples that have not been consumed yet by the UI thread. + unconsumed_stream_samples_changed = Signal(int) + unconsumed_capture_samples_changed = Signal(int) + + # Recording Flags (starting, stopping and pausing) + device_capturing_state_changed = Signal(AD2Constants.CapturingState) + start_recording_changed = Signal(bool) + stop_recording_changed = Signal(bool) + reset_recording_changed = Signal(bool) + capturing_finished_changed = Signal(bool) + + # Multiprocessing Information + pid_changed = Signal(int) + + # ================================================================================================================== + # Delete later + # Signals for device information + hwdf_changed = Signal(int) + + device_ready_changed = Signal(bool) + + # Signals for reporting if samples were lost or corrupted + fLost_changed = Signal(int) + fCorrupted_changed = Signal(int) + # Acquisition information + + n_samples_changed = Signal(int) + + # Recording settings for starting, stopping and pausing + + # Signal if new samples have been aquired + + all_recorded_samples_changed = Signal(list) + num_of_current_recorded_samples_changed = Signal(int) + measurement_time_changed = Signal(float) + + ad2_settings = Signal(dict) + error = Signal(str) + ad2_is_capturing = Signal(bool) + + ad2_set_acquisition = Signal(bool) + ad2_captured_value = Signal(list) diff --git a/src/CaptDeviceControl/view/AD2CaptDeviceView.py b/src/CaptDeviceControl/view/AD2CaptDeviceView.py index cdb348a5a6cca2066d56354169b63e54c1380a5a..93aa47b51004a0c12b335db918af26768b71222c 100644 --- a/src/CaptDeviceControl/view/AD2CaptDeviceView.py +++ b/src/CaptDeviceControl/view/AD2CaptDeviceView.py @@ -1,4 +1,5 @@ import logging +import os from collections import deque import numpy as np @@ -9,8 +10,9 @@ from PySide6.QtWidgets import QMainWindow, QStatusBar from pyqtgraph.dockarea import DockArea, Dock import pyqtgraph as pg +from rich.logging import RichHandler -from CaptDeviceControl.controller.AD2CaptDeviceController import AD2CaptDeviceController +from CaptDeviceControl.controller.BaseAD2CaptDevice import BaseAD2CaptDevice from CaptDeviceControl.model.AD2CaptDeviceModel import AD2CaptDeviceModel from CaptDeviceControl.model.AD2Constants import AD2Constants from CaptDeviceControl.view.Ui_AD2ControlWindow import Ui_AD2ControlWindow @@ -23,9 +25,14 @@ from fswidgets import PlayPushButton class ControlWindow(QMainWindow): - def __init__(self, model: AD2CaptDeviceModel, controller: AD2CaptDeviceController): + def __init__(self, model: AD2CaptDeviceModel, controller: BaseAD2CaptDevice): super().__init__() - self.logger = logging.getLogger("AD2ControlWindow") + self.handler = RichHandler(rich_tracebacks=True) + self.logger = logging.getLogger(f"AD2Window({os.getpid()})") + self.logger.handlers = [self.handler] + self.logger.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(name)s %(message)s') + self.handler.setFormatter(formatter) self.controller = controller self.model = model @@ -51,10 +58,10 @@ class ControlWindow(QMainWindow): # Timer for periodically updating the plot self.capture_update_timer = QTimer() self.capture_update_timer.setInterval(10) - self.capture_update_timer.timeout.connect(self._on_capture_update_plot) + #self.capture_update_timer.timeout.connect(self._on_capture_update_plot) self.stream_update_timer = QTimer() - self.stream_update_timer.setInterval(10) + self.stream_update_timer.setInterval(40) self.stream_update_timer.timeout.connect(self._on_stream_update_timer_timeout) self.stream_samples_frequency = 1000 @@ -95,33 +102,33 @@ class ControlWindow(QMainWindow): self.model.signals.dwf_version_changed.connect(self._on_dwf_version_changed) # Connected Device Information - self.model.signals.num_of_connected_devices_changed.connect(self._on_num_of_connected_devices_changed) - self.model.signals.connected_devices_changed.connect(self._on_connected_devices_changed) + self.model.device_information.signals.num_of_connected_devices_changed.connect(self._on_num_of_connected_devices_changed) + self.model.device_information.signals.connected_devices_changed.connect(self._on_connected_devices_changed) # Device information - self.model.signals.connected_changed.connect(self._on_connected_changed) - self.model.signals.device_name_changed.connect(self._on_device_name_changed) - self.model.signals.device_serial_number_changed.connect(self._on_device_serial_number_changed) - self.model.signals.device_index_changed.connect(self._on_device_index_changed) + self.model.device_information.signals.device_connected_changed.connect(self._on_connected_changed) + self.model.device_information.signals.device_name_changed.connect(self._on_device_name_changed) + self.model.device_information.signals.device_serial_number_changed.connect(self._on_device_serial_number_changed) + self.model.device_information.signals.device_index_changed.connect(self._on_device_index_changed) # Acquisition Settings self.model.signals.sample_rate_changed.connect(self._model_on_sample_rate_changed) - self.model.signals.selected_ain_channel_changed.connect(self._model_on_selected_ain_changed) # Analog In Information - self.model.signals.ain_channels_changed.connect(self._on_ain_channels_changed) - self.model.signals.ain_buffer_size_changed.connect(self._on_ain_buffer_size_changed) - self.model.signals.ain_bits_changed.connect(self._on_ain_bits_changed) - self.model.signals.ain_device_state_changed.connect(self._on_ain_device_state_changed) + self.model.analog_in.signals.selected_ain_channel_changed.connect(self._model_on_selected_ain_changed) + self.model.analog_in.signals.ain_channels_changed.connect(self._on_ain_channels_changed) + self.model.analog_in.signals.ain_buffer_size_changed.connect(self._on_ain_buffer_size_changed) + self.model.analog_in.signals.ain_bits_changed.connect(self._on_ain_bits_changed) + self.model.analog_in.signals.ain_device_state_changed.connect(self._on_ain_device_state_changed) # Analog Out Information self.model.signals.aout_channels_changed.connect(self._on_aout_channels_changed) # Acquired Signal Information - self.model.signals.recorded_samples_changed.connect(self._on_recorded_samples_changed) - self.model.signals.recording_time_changed.connect(self._on_recording_time_changed) - self.model.signals.samples_captured_changed.connect(self._on_samples_captured_changed) + #self.model.signals.recorded_samples_changed.connect(self._on_recorded_samples_changed) + #self.model.signals.recording_time_changed.connect(self._on_recording_time_changed) + #self.model.signals.samples_captured_changed.connect(self._on_samples_captured_changed) self.model.signals.samples_lost_changed.connect(self._on_samples_lost_changed) self.model.signals.samples_corrupted_changed.connect(self._on_samples_corrupted_changed) @@ -179,8 +186,8 @@ class ControlWindow(QMainWindow): # Slots for Model # ================================================================================================================== def _on_device_selected_changed(self, index): - self.model.selected_device = index - self.controller.get_analog_in_informatio() + self.model.device_information.device_index = index + self.controller.device_selected_index_changed() # First populate the AIn box+ #m: dict = self.model.connected_devices[index] #self.model.ain_channels = list(range(0, int(m['analog_in_channels']))) @@ -201,6 +208,7 @@ class ControlWindow(QMainWindow): dev: dict # 'type': type, 'device_id', 'device_name', 'serial_number' self._ui.cb_device_select.addItem(f"{it}: {dev['type']}{dev['device_id']} - {dev['device_name']}") + self._ui.cb_device_select.setCurrentIndex(0) # ============== Device information def _on_connected_changed(self, connected): @@ -396,7 +404,7 @@ class ControlWindow(QMainWindow): self.scope_original.clear() # print(self.ad2device.recorded_samples) self.scope_original.plot( - list(self.controller.streaming_data_dqueue),#[::self.stream_n], + np.array(self.controller.streaming_data_dqueue)[::100], pen=pg.mkPen(width=1)) self._ui.lcd_unconsumed_stream.display(self.model.unconsumed_stream_samples) @@ -405,8 +413,8 @@ class ControlWindow(QMainWindow): # # ================================================================================================================== def on_btn_connect_to_device_clicked(self): - print(self.model.connected) - if self.model.connected: + print(self.model.device_information.device_connected) + if self.model.device_information.device_connected: self.controller.close_device() self._ui.btn_connect.setText("Connect") else: diff --git a/tests/testing_mp_pyside.py b/tests/testing_mp_pyside.py new file mode 100644 index 0000000000000000000000000000000000000000..14a8cbb4949b99c614a7942f73d612594968c1a9 --- /dev/null +++ b/tests/testing_mp_pyside.py @@ -0,0 +1,42 @@ +import sys +import multiprocessing +from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget +from PySide6.QtCore import Signal, QObject, Qt +from time import sleep + +class ChildProcess(QObject): + finished = Signal(str) + + def run(self): + app = QApplication(sys.argv) + main_window = MainWindow() + main_window.show() + sys.exit(app.exec_()) + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + + self.setWindowTitle("Multiprocessing with PySide6") + self.setGeometry(100, 100, 400, 200) + + self.central_widget = QWidget(self) + self.setCentralWidget(self.central_widget) + + self.layout = QVBoxLayout(self.central_widget) + + self.start_button = QPushButton("Start Child Process", self) + self.start_button.clicked.connect(self.start_child_process) + self.layout.addWidget(self.start_button) + + self.child_process = None + + def start_child_process(self): + if not self.child_process or not self.child_process.is_alive(): + self.child_process = multiprocessing.Process(target=self.run_child_process) + self.child_process.start() + self.start_button.setEnabled(False) + + def run_child_process(self): + child = ChildProcess() + child.finished.connect(self.handle_child_signal)