diff --git a/.idea/captdevicecontrol.iml b/.idea/captdevicecontrol.iml index 26456f591c9b6fa76a53f99e762aa6e1d8be1ab5..92e4649168f953bb240ff732a1e4466591fa0481 100644 --- a/.idea/captdevicecontrol.iml +++ b/.idea/captdevicecontrol.iml @@ -7,6 +7,7 @@ <excludeFolder url="file://$MODULE_DIR$/src/CaptDeviceControl/adcaptdevicecontrol" /> <excludeFolder url="file://$MODULE_DIR$/src/CaptDeviceControl/old" /> <excludeFolder url="file://$MODULE_DIR$/.venv" /> + <excludeFolder url="file://$MODULE_DIR$/build" /> </content> <orderEntry type="jdk" jdkName="Python 3.10 (captdevicecontrol)" jdkType="Python SDK" /> <orderEntry type="sourceFolder" forTests="false" /> @@ -14,4 +15,14 @@ <component name="PyDocumentationSettings"> <option name="renderExternalDocumentation" value="true" /> </component> + <component name="PyNamespacePackagesService"> + <option name="namespacePackageFolders"> + <list> + <option value="$MODULE_DIR$/src/CaptDeviceControl" /> + <option value="$MODULE_DIR$/src/CaptDeviceControl/controller" /> + <option value="$MODULE_DIR$/src/CaptDeviceControl/view" /> + <option value="$MODULE_DIR$/src/CaptDeviceControl/model" /> + </list> + </option> + </component> </module> \ No newline at end of file diff --git a/examples/config.yaml b/examples/config.yaml deleted file mode 100644 index c81521f1a53eec72efcb975c0791a99fac1a0943..0000000000000000000000000000000000000000 --- a/examples/config.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# - Configuration file stored 2023-11-22 12:48:19.708315 - -CaptDeviceConfig: #!!python/object:controller.CaptDeviceConfig - sample_rate: 50000 # Sample rate: Sample rate of the device - streaming_rate: 500 # Streaming rate: Streaming rate in Hz (should be below 1kHz) - ain_channel: 0 # Analog In Channel: Analog in channel. Defines which channel is used for capturing. - show_simulator: True # Show Simulators: Show available simulators in the device list provided by the DreamWaves API. - streaming_history: 2000 # Streaming history (ms): Defines the range of the stream in ms - total_samples: 200000 # total_samples: None - sample_time: 45 # sample_time: None - ad2_raw_out_file: "{output_directory}/measurement/ad2_raw/ad2_out_{wafer_nr}_{date}.csv" # ad2_raw_out_file: None diff --git a/examples/main.py b/examples/main.py index 3f60687e8a2f239b18ab4c7c46349c2632946551..902aa55c54300c74b7bd67c61a41b4e87afd0a17 100644 --- a/examples/main.py +++ b/examples/main.py @@ -26,12 +26,12 @@ if __name__ == "__main__": ) - setup_logging() + #setup_logging() app = QApplication() conf = CaptDevice.Config() - conf.load("config.yaml") + #conf.load("config.yaml") model = CaptDevice.Model(conf) diff --git a/pyproject.toml b/pyproject.toml index deabe13a9f56259a66010a04e8aed8499781c6bd..04665ae48d6f210a7d6f22067dab0e7dffebf30e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ description = "A UI for controlling the Analog Discovery Series" readme = "README.md" requires-python = ">=3.7" dependencies = [ - 'pyside6', 'rich', 'pyyaml' + 'pyside6', 'rich', 'pyyaml','matplotlib', 'numpy', 'pandas', 'scipy', 'pyqtgraph' ] classifiers = [ "Programming Language :: Python :: 3", diff --git a/requirements.txt b/requirements.txt index e061be5e3eb6e2593320c309d1cc46cf0cbce277..6952b7243a1843153f4f1f0b9b5141de9a34bca5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,13 @@ -matplotlib~=3.7.1 -numpy~=1.24.2 -pandas~=1.5.3 -PySide6~=6.5.2 -scipy~=1.10.1 +matplotlib +numpy +pandas +PySide6 +scipy rich +pyqtgraph # add the git repo git+https://gitlab.tugraz.at/flexsensor-public/confighandler.git@develop git+https://gitlab.tugraz.at/flexsensor-public/fswidgets.git@develop #../fswidgets + +../../pyside-multiprocessing \ No newline at end of file diff --git a/src/CaptDeviceControl/controller/AD2CaptDeviceController.py b/src/CaptDeviceControl/controller/AD2CaptDeviceController.py index ef7ed1acd5d9d448641c4617b14328b63f5de32b..24bff829e27a76bb3f409dfb2c98f4404d8358e1 100644 --- a/src/CaptDeviceControl/controller/AD2CaptDeviceController.py +++ b/src/CaptDeviceControl/controller/AD2CaptDeviceController.py @@ -8,6 +8,7 @@ from CaptDeviceControl.model.AD2CaptDeviceModel import AD2CaptDeviceModel #from .model.AD2CaptDeviceModel import AD2CaptDeviceModel from CaptDeviceControl.constants.dwfconstants import enumfilterUSB, enumfilterType, enumfilterDemo +from CaptDeviceControl.controller.mp_AD2Capture.MPCaptDeviceControl import MPCaptDeviceControl class AD2CaptDeviceController(BaseAD2CaptDevice): @@ -16,11 +17,11 @@ class AD2CaptDeviceController(BaseAD2CaptDevice): self.dwf = cdll.dwf super().__init__(ad2capt_model) + + # This is required for acquiring the data - def connect_device(self, device_id): - self.start_device_process(device_id) - return True + def read_hardware_config(self, iDevice): hw_info_dict = {} @@ -57,48 +58,12 @@ class AD2CaptDeviceController(BaseAD2CaptDevice): return hw_info_dict - def discover_connected_devices(self): - # 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 | enumfilterUSB.value), 'USB') - filter, type = (c_int32(enumfilterType.value | enumfilterUSB.value | enumfilterDemo.value), 'USB') - self.dwf.FDwfEnum(filter, byref(cDevice)) - self.model.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) - hw_info = self.read_hardware_config(iDevice) - srn = str(serialnum.value.decode('UTF-8')) - if "demo" in srn.lower(): - type = "Simulator " - con_dev_dict = { - 'type': type, - 'device_id': int(iDevice), - 'device_name': str(devicename.value.decode('UTF-8')), - 'serial_number': srn - } - con_dev_dict = dict(con_dev_dict, **hw_info) - connected_devices.append(con_dev_dict) - - self.logger.info(connected_devices) - self.model.connected_devices = connected_devices - self.logger.info(f"Discovered {len(self.model.connected_devices)} devices.") + def discover_connected_devices(self): + pass + #self.mpcaptdevicecontrol.discover_connected_devices() - return self.model.connected_devices - def close_device(self): - self.end_process_flag.value = 1 # def _open_device(self, device_index): # devicename = create_string_buffer(64) diff --git a/src/CaptDeviceControl/controller/BaseAD2CaptDevice.py b/src/CaptDeviceControl/controller/BaseAD2CaptDevice.py index 2dce3bd7de75df3f28dab33b6f43d324c6d85257..20cb325d7566c34ece9b1b15081ed978842952db 100644 --- a/src/CaptDeviceControl/controller/BaseAD2CaptDevice.py +++ b/src/CaptDeviceControl/controller/BaseAD2CaptDevice.py @@ -11,6 +11,8 @@ from CaptDeviceControl.model.AD2CaptDeviceModel import AD2CaptDeviceModel, AD2Ca from CaptDeviceControl.model.AD2Constants import AD2Constants from multiprocessing import Process, Queue, Value, Lock +from CaptDeviceControl.controller.mp_AD2Capture.MPCaptDeviceControl import MPCaptDeviceControl + class BaseAD2CaptDevice(QObject): @@ -44,12 +46,31 @@ class BaseAD2CaptDevice(QObject): self.status_dqueue = deque(maxlen=int(1)) self.unconsumed_capture_data = 0 + self.mpcaptdevicecontrol = MPCaptDeviceControl(self.model, + self.stream_data_queue, + self.capture_data_queue, + self.start_capture_flag, + enable_logging=True) - self.discover_connected_devices() + #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) - @abstractmethod def connect_device(self, device_id): - raise NotImplementedError + self.mpcaptdevicecontrol.open_device(device_id) + self.mpcaptdevicecontrol.start_capture( + self.model.sample_rate, + self.model.selected_ain_channel) + self.start_device_process() + return True + + def close_device(self): + self.mpcaptdevicecontrol.close_device() @abstractmethod def discover_connected_devices(self): @@ -63,10 +84,6 @@ class BaseAD2CaptDevice(QObject): def _capture(self): raise NotImplementedError - @abstractmethod - def close_device(self): - raise NotImplementedError - def set_ad2_acq_status(self, record): if record: self.model.start_recording = True @@ -137,24 +154,24 @@ class BaseAD2CaptDevice(QObject): self.model.capturing_finished = False # ================================================================================================================== - def start_device_process(self, device_id): + def start_device_process(self): self.logger.info(f"[{self.pref} Task] Starting capturing process...") #self.logger.debug(f"Dataqueue maxlen={int(self.model.duration_streaming_history * self.model.sample_rate)}") self.streaming_data_dqueue = deque(maxlen=int(self.model.duration_streaming_history * self.model.sample_rate)) #print(self.model.duration_streaming_history * self.model.sample_rate) self.stream_data_queue.maxsize = int(self.model.duration_streaming_history * self.model.sample_rate) - self.proc = Process(target=mp_capture, - args=( - self.stream_data_queue, self.capture_data_queue, self.state_queue, - self.start_capture_flag, self.end_process_flag, - device_id, self.model.selected_ain_channel, self.model.sample_rate) - ) - self.proc.start() + #self.proc = Process(target=mp_capture, + # args=( + # self.stream_data_queue, self.capture_data_queue, self.state_queue, + # self.start_capture_flag, self.end_process_flag, + # device_id, self.model.selected_ain_channel, self.model.sample_rate) + # ) + #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) + #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.""" @@ -172,8 +189,9 @@ class BaseAD2CaptDevice(QObject): nth_cnt = 1 nth = 2 while not self.kill_thread and not bool(self.end_process_flag.value): - while self.stream_data_queue.qsize() > 0: - self.model.unconsumed_stream_samples = self.stream_data_queue.qsize() + 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) diff --git a/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDevice.py b/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDevice.py new file mode 100644 index 0000000000000000000000000000000000000000..d88356356d06c97f9e8e5863a37cecd6673ae886 --- /dev/null +++ b/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDevice.py @@ -0,0 +1,344 @@ +import os +import sys +import time +from ctypes import c_int, c_int32, byref, create_string_buffer, cdll, c_double, c_byte +from multiprocessing import Queue, Value + +import cmp + +from constants.dwfconstants import enumfilterType, enumfilterDemo, enumfilterUSB, acqmodeRecord, DwfStateConfig, \ + DwfStatePrefill, DwfStateArmed + + +class MPCaptDevice(cmp.CProcess): + def __init__(self, state_queue, cmd_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) + + self.start_capture_flag: Value = start_capture_flag + self.stream_data_queue = streaming_data_queue + self.capture_data_queue = capture_data_queue + + self.logger, self.ha = None, None + + self.dwf = None + self.hdwf = None + + self._dwf_version = None + self._device_serial_number: str = "" + self._device_name: str = "None" + self._connected = self.connected() + + 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.hdwf = c_int() + self._ain_device_state: c_byte = c_byte() + + @cmp.CProcess.register_for_signal('_changed') + def connected_devices(self): + 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 + + @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)) + + @cmp.CProcess.register_for_signal('_changed') + def ain_buffer_size(self, device_id) -> int: + cInfo = c_int() + 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")) + + @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:", "") + + @cmp.CProcess.register_for_signal('_changed') + def connected(self) -> bool: + if self.hdwf is None or self.hdwf.value == 0: + return False + else: + return True + + @cmp.CProcess.register_for_signal() + def open_device(self, device_index): + """ + Opens the device and returns the handle. + :return: Device handle. + """ + 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)) + + if self.hdwf.value == 0: + szerr = create_string_buffer(512) + self.dwf.FDwfGetLastErrorMsg(szerr) + self.logger.error(f"Failed to open device: {szerr.value}") + # ad2_state.connected = False + raise Exception(f"Failed to open device: {szerr.value}") + 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.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): + self.dwf.FDwfAnalogInStatus(self.hdwf, c_int(1), + 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)) + self.dwf.FDwfAnalogInAcquisitionModeSet(self.hdwf, acqmodeRecord) + self.dwf.FDwfAnalogInFrequencySet(self.hdwf, c_double(sample_rate)) + 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.") + # wait at least 2 seconds for the offset to stabilize + time.sleep(2) + self.logger.info(f"[Task] Setup for acquisition done.") + + 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 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.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 + 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 + + self.close_device() diff --git a/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDeviceControl.py b/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDeviceControl.py new file mode 100644 index 0000000000000000000000000000000000000000..e5fc099b8365e8812f8d52ace0f60538322aea07 --- /dev/null +++ b/src/CaptDeviceControl/controller/mp_AD2Capture/MPCaptDeviceControl.py @@ -0,0 +1,62 @@ +import cmp +from PySide6.QtCore import Signal + +from CaptDeviceControl.controller.mp_AD2Capture.MPCaptDevice import MPCaptDevice +from model.AD2CaptDeviceModel import AD2CaptDeviceSignals, AD2CaptDeviceModel + + +class MPCaptDeviceControl(cmp.CProcessControl): + connected_devices_changed = Signal(list) + ain_channels_changed = Signal(list) + ain_buffer_size_changed = Signal(int) + dwf_version_changed = Signal(str) + device_name_changed = Signal(str) + device_serial_number_changed = Signal(str) + connected_changed = Signal(bool) + + open_device_finished = Signal() + close_device_finished = Signal() + + def __init__(self, + model: AD2CaptDeviceModel, + streaming_data_queue, + capturing_data_queue, + start_capture_flag, + parent=None, + enable_logging=False): + super().__init__(parent, enable_logging=enable_logging) + self.register_child_process( + MPCaptDevice(self.state_queue, self.cmd_queue, + streaming_data_queue, + capturing_data_queue, + start_capture_flag, + enable_logging=enable_logging)) + + 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)) + + + @cmp.CProcessControl.register_function() + def connected_devices(self): + self._internal_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}.") + + @cmp.CProcessControl.register_function() + def open_device(self, device_index): + self._internal_logger.info(f"Opening device {device_index}.") + + @cmp.CProcessControl.register_function() + def close_device(self): + self._internal_logger.info(f"Closing device device.") + + @cmp.CProcessControl.register_function() + def start_capture(self, sample_rate, ain_channel): + print(f"Starting capture with sample rate {sample_rate} and ain channel {ain_channel}.") diff --git a/src/CaptDeviceControl/controller/mp_AD2Capture/MPDeviceControl.py b/src/CaptDeviceControl/controller/mp_AD2Capture/MPDeviceControl.py index 05c9b165d2c6b00703c8c60b6b9d85ff0d006c7d..9aadab180388d634ac475dbb4d7fe239ad2ee1fd 100644 --- a/src/CaptDeviceControl/controller/mp_AD2Capture/MPDeviceControl.py +++ b/src/CaptDeviceControl/controller/mp_AD2Capture/MPDeviceControl.py @@ -102,8 +102,8 @@ def mp_capture(stream_data_queue, capture_data_queue, state_queue, cSamples = 0 ad2_state.device_ready = True capture_samples = 0 - - while end_process.value == False: + print(end_process.value) + while end_process.value == int(False): # 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 dwf.FDwfAnalogInStatus(hdwf, c_int(1), @@ -141,7 +141,7 @@ def mp_capture(stream_data_queue, capture_data_queue, state_queue, # Print how many samples are available status = {"available": cAvailable.value, 'captured': 0, 'lost': cLost.value, 'corrupted': cCorrupted.value, "time": time.time()} - print(status) + #print(status) time.sleep(random.random()) #print(len(rgdSamples)) stream_data_queue.put( diff --git a/src/CaptDeviceControl/model/AD2CaptDeviceModel.py b/src/CaptDeviceControl/model/AD2CaptDeviceModel.py index fdf7485c82d59361431cb4b0d290e57f57bf8b76..3b9f0dc82c3100e03ba4d0e9f295e1729032441a 100644 --- a/src/CaptDeviceControl/model/AD2CaptDeviceModel.py +++ b/src/CaptDeviceControl/model/AD2CaptDeviceModel.py @@ -19,11 +19,12 @@ class AD2CaptDeviceSignals(QObject): # 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) - serial_number_changed = Signal(str) + device_serial_number_changed = Signal(str) device_index_changed = Signal(int) # Acquisition Settings @@ -111,7 +112,8 @@ class AD2CaptDeviceModel: # Connected Device Information self._num_of_connected_devices: int = 0 - self._connected_devices: dict = {} + self._connected_devices: list = [] + self._selected_device: int = 0 # Device Information self._connected: bool = False @@ -221,14 +223,23 @@ class AD2CaptDeviceModel: self.signals.num_of_connected_devices_changed.emit(self._num_of_connected_devices) @property - def connected_devices(self) -> dict: + def connected_devices(self) -> list: return self._connected_devices @connected_devices.setter - def connected_devices(self, value: dict): + 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 @@ -264,7 +275,7 @@ class AD2CaptDeviceModel: self._device_serial_number = str(value.value.decode('UTF-8')) else: self._device_serial_number = str(value) - self.signals.serial_number_changed.emit(self.device_serial_number) + self.signals.device_serial_number_changed.emit(self.device_serial_number) @property def device_index(self) -> int: @@ -276,7 +287,7 @@ class AD2CaptDeviceModel: self._device_index = int(value.value) else: self._device_index = int(value) - self.signals.serial_number_changed.emit(self.device_index) + self.signals.device_serial_number_changed.emit(self.device_index) # ================================================================================================================== diff --git a/src/CaptDeviceControl/resources/AD2ControlWindow.ui b/src/CaptDeviceControl/resources/AD2ControlWindow.ui index e9098f21608320e4182dbade557542d110ba0700..1562af315f0ed2593fdd954ae25258a6ad4caac1 100644 --- a/src/CaptDeviceControl/resources/AD2ControlWindow.ui +++ b/src/CaptDeviceControl/resources/AD2ControlWindow.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>641</width> + <width>646</width> <height>765</height> </rect> </property> @@ -55,7 +55,6 @@ QTableWidget { gridline-color: rgb(44, 49, 58); border-bottom: 1px solid rgb(44, 49, 60); } - QTableWidget::item{ border-color: rgb(44, 49, 60); padding-left: 5px; @@ -260,7 +259,7 @@ QComboBox::drop-down { border-left-style: solid; border-top-right-radius: 3px; border-bottom-right-radius: 3px; - background-image: url(:/icons/flexsensorpy/images/icons/cil-arrow-bottom.png); + background-image: url(:/icons/icons/cil-arrow-bottom.png); background-position: center; background-repeat: no-reperat; } @@ -270,9 +269,9 @@ QComboBox QAbstractItemView { padding: 10px; selection-background-color: rgb(39, 44, 54); } -QComboBox QAbstractItemView::item { +/*QComboBox QAbstractItemView::item { min-height: 150px; -} +}*/ /* ///////////////////////////////////////////////////////////////////////////////////////////////// Sliders */ @@ -350,7 +349,7 @@ QCommandLinkButton:pressed { Button */ QPushButton { border: 1px solid rgb(42, 175, 211); - border-radius: 2px; + border-radius: 2px; background-color: rgb(52, 59, 72); } QPushButton:hover { @@ -945,7 +944,7 @@ QPushButton:disabled { <rect> <x>0</x> <y>0</y> - <width>641</width> + <width>646</width> <height>22</height> </rect> </property> diff --git a/src/CaptDeviceControl/view/AD2CaptDeviceView.py b/src/CaptDeviceControl/view/AD2CaptDeviceView.py index 1e3974aae6f315f44064956a461e1af1856073d5..cdb348a5a6cca2066d56354169b63e54c1380a5a 100644 --- a/src/CaptDeviceControl/view/AD2CaptDeviceView.py +++ b/src/CaptDeviceControl/view/AD2CaptDeviceView.py @@ -32,7 +32,7 @@ class ControlWindow(QMainWindow): self._ui = Ui_AD2ControlWindow() self._ui.setupUi(self) - self._ui.btn_start_capture = PlayPushButton(self._ui.btn_start_capture) + # self._ui.btn_start_capture = PlayPushButton(self._ui.btn_start_capture) # @@ -101,7 +101,7 @@ class ControlWindow(QMainWindow): # 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.serial_number_changed.connect(self._on_device_serial_number_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) # Acquisition Settings @@ -179,9 +179,12 @@ class ControlWindow(QMainWindow): # Slots for Model # ================================================================================================================== def _on_device_selected_changed(self, index): + self.model.selected_device = index + self.controller.get_analog_in_informatio() # First populate the AIn box+ - m: dict = self.model.connected_devices[index] - self.model.ain_channels = list(range(0, int(m['analog_in_channels']))) + #m: dict = self.model.connected_devices[index] + #self.model.ain_channels = list(range(0, int(m['analog_in_channels']))) + def _on_dwf_version_changed(self, dwf_version): self.dev_info.dwf_version = dwf_version @@ -402,13 +405,15 @@ class ControlWindow(QMainWindow): # # ================================================================================================================== def on_btn_connect_to_device_clicked(self): + print(self.model.connected) if self.model.connected: self.controller.close_device() + self._ui.btn_connect.setText("Connect") else: try: self.controller.connect_device(self._ui.cb_device_select.currentIndex()) # self.plot_update_timer.setInterval(0.1) - self.stream_n = int(self.model.sample_rate / self.stream_samples_frequency) + #self.stream_n = int(self.model.sample_rate / self.stream_samples_frequency) except Exception as e: # self.status_bar.setStyleSheet('border: 0; color: red;') # self.status_bar.showMessage(f"Error: {e}") diff --git a/src/CaptDeviceControl/view/Ui_AD2ControlWindow.py b/src/CaptDeviceControl/view/Ui_AD2ControlWindow.py index 3b8a1a8962dbf50759b3913f796edc92e6104892..288514f9ea6b59555137c84a7eb29ee8c121f513 100644 --- a/src/CaptDeviceControl/view/Ui_AD2ControlWindow.py +++ b/src/CaptDeviceControl/view/Ui_AD2ControlWindow.py @@ -27,7 +27,7 @@ class Ui_AD2ControlWindow(object): def setupUi(self, AD2ControlWindow): if not AD2ControlWindow.objectName(): AD2ControlWindow.setObjectName(u"AD2ControlWindow") - AD2ControlWindow.resize(641, 765) + AD2ControlWindow.resize(646, 765) AD2ControlWindow.setStyleSheet(u"") self.centralwidget = QWidget(AD2ControlWindow) self.centralwidget.setObjectName(u"centralwidget") @@ -69,7 +69,6 @@ class Ui_AD2ControlWindow(object): " gridline-color: rgb(44, 49, 58);\n" " border-bottom: 1px solid rgb(44, 49, 60);\n" "}\n" -"\n" "QTableWidget::item{\n" " border-color: rgb(44, 49, 60);\n" " padding-left: 5px;\n" @@ -98,8 +97,8 @@ class Ui_AD2ControlWindow(object): " border-top-left-radius: 7px;\n" " border-top-right-radius: 7px;\n" "}\n" -"QHeaderVie" - "w::section:vertical\n" +"QHeaderView::se" + "ction:vertical\n" "{\n" " border: 1px solid rgb(44, 49, 60);\n" "}\n" @@ -133,8 +132,8 @@ class Ui_AD2ControlWindow(object): "QPlainTextEdit QScrollBar:vertical {\n" " width: 8px;\n" " }\n" -"QPlainTextEdit QScrollBar:h" - "orizontal {\n" +"QPlainTextEdit QScrollBar:horizo" + "ntal {\n" " height: 8px;\n" " }\n" "QPlainTextEdit:hover {\n" @@ -171,8 +170,8 @@ class Ui_AD2ControlWindow(object): " border: none;\n" " background: rgb(55, 63, 77);\n" " width: 20px;\n" -" border-top-l" - "eft-radius: 4px;\n" +" border-top-left-r" + "adius: 4px;\n" " border-bottom-left-radius: 4px;\n" " subcontrol-position: left;\n" " subcontrol-origin: margin;\n" @@ -209,8 +208,8 @@ class Ui_AD2ControlWindow(object): " QScrollBar::sub-line:vertical {\n" " border: none;\n" " background: rgb(55, 63, 77);\n" -" " - " height: 20px;\n" +" he" + "ight: 20px;\n" " border-top-left-radius: 4px;\n" " border-top-right-radius: 4px;\n" " subcontrol-position: top;\n" @@ -242,8 +241,8 @@ class Ui_AD2ControlWindow(object): " background-image: url(:/icons/images/icons/cil-check-alt.png);\n" "}\n" "\n" -"/* //////////////////////////////////////////////////////" - "///////////////////////////////////////////\n" +"/* ///////////////////////////////////////////////////////////" + "//////////////////////////////////////\n" "RadioButton */\n" "QRadioButton::indicator {\n" " border: 3px solid rgb(52, 59, 72);\n" @@ -277,10 +276,10 @@ class Ui_AD2ControlWindow(object): " border-left-width: 2px;\n" " border-left-color: rgba(39, 44, 54, 150);\n" " border-left-style: solid;\n" -" border-top-right-r" - "adius: 3px;\n" +" border-top-right-radius" + ": 3px;\n" " border-bottom-right-radius: 3px; \n" -" background-image: url(:/icons/flexsensorpy/images/icons/cil-arrow-bottom.png);\n" +" background-image: url(:/icons/icons/cil-arrow-bottom.png);\n" " background-position: center;\n" " background-repeat: no-reperat;\n" " }\n" @@ -290,9 +289,9 @@ class Ui_AD2ControlWindow(object): " padding: 10px;\n" " selection-background-color: rgb(39, 44, 54);\n" "}\n" -"QComboBox QAbstractItemView::item {\n" +"/*QComboBox QAbstractItemView::item {\n" " min-height: 150px;\n" -"}\n" +"}*/\n" "\n" "/* /////////////////////////////////////////////////////////////////////////////////////////////////\n" "Sliders */\n" @@ -311,10 +310,10 @@ class Ui_AD2ControlWindow(object): " height: 10px;\n" " width: 8px;\n" " margin: 0px;\n" -" border-radius: 5" - "px;\n" +" border-radius: 5px;\n" "}\n" -"QSlider::handle:horizontal:hover {\n" +"QSlider" + "::handle:horizontal:hover {\n" " background-color: rgb(42, 141, 211);\n" " border: none;\n" " height: 10px;\n" @@ -351,9 +350,9 @@ class Ui_AD2ControlWindow(object): "}\n" "\n" "/* /////////////////////////////////////////////////////////////////////////////////////////////////\n" -"Co" - "mmandLinkButton */\n" -"QCommandLinkButton { \n" +"CommandLinkButton */\n" +"" + "QCommandLinkButton { \n" " color: rgb(255, 121, 198);\n" " border-radius: 5px;\n" " padding: 5px;\n" @@ -372,7 +371,7 @@ class Ui_AD2ControlWindow(object): "Button */\n" "QPushButton {\n" " border: 1px solid rgb(42, 175, 211);\n" -" border-radius: 2px;\n" +" border-radius: 2px; \n" " background-color: rgb(52, 59, 72);\n" "}\n" "QPushButton:hover {\n" @@ -394,8 +393,8 @@ class Ui_AD2ControlWindow(object): "\n" "\n" "\n" -"/* QMenu --------------------------------------------" - "----------------------\n" +"/* QMenu ------------------------------------------------------------------" + "\n" "\n" "examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenu\n" "\n" @@ -435,10 +434,10 @@ class Ui_AD2ControlWindow(object): "QMenu::indicator {\n" " width: 13px;\n" " height: 13px;\n" -"" - "}\n" +"}\n" "\n" -"QTabWidget::pane {\n" +"QTabWidge" + "t::pane {\n" " border: 1px solid lightgray;\n" " top:-1px; \n" " background: rgb(40, 44, 52); \n" @@ -700,7 +699,7 @@ class Ui_AD2ControlWindow(object): " border-left: 22px solid transparent;\n" " text-align: left;\n" " padding-left: 44px;\n" -" background-image: url(:/icons/images/icons/cil-media-play.png);\n" +" background-image: url(:/icons/icons/cil-media-play.png);\n" "}\n" "\n" "QPushButton:hover {\n" @@ -734,7 +733,7 @@ class Ui_AD2ControlWindow(object): " border-left: 22px solid transparent;\n" " text-align: left;\n" " padding-left: 44px;\n" -" background-image: url(:/icons/images/icons/cil-media-stop.png)\n" +" background-image: url(:/icons/icons/cil-media-stop.png)\n" "}\n" "\n" "QPushButton:hover {\n" @@ -774,7 +773,7 @@ class Ui_AD2ControlWindow(object): AD2ControlWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(AD2ControlWindow) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 641, 22)) + self.menubar.setGeometry(QRect(0, 0, 646, 22)) AD2ControlWindow.setMenuBar(self.menubar) self.statusbar = QStatusBar(AD2ControlWindow) self.statusbar.setObjectName(u"statusbar")