|
| 1 | +import numpy as np |
| 2 | +from pymodaq.utils.daq_utils import ThreadCommand |
| 3 | +from pymodaq.utils.data import DataFromPlugins, Axis, DataToExport |
| 4 | +from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main |
| 5 | +from pymodaq.utils.parameter import Parameter |
| 6 | +from daqhats import mcc128, OptionFlags, AnalogInputMode, TriggerModes, AnalogInputRange |
| 7 | +from pymodaq.utils.parameter.utils import iter_children |
| 8 | + |
| 9 | + |
| 10 | +class DAQ_1DViewer_daqhats(DAQ_Viewer_base): |
| 11 | + """ Instrument plugin class for a 1D viewer. |
| 12 | +
|
| 13 | + This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Viewer module through inheritance via |
| 14 | + DAQ_Viewer_base. It makes a bridge between the DAQ_Viewer module and the Python wrapper of a particular instrument. |
| 15 | +
|
| 16 | + Attributes: |
| 17 | + ----------- |
| 18 | + controller: object |
| 19 | + The particular object that allow the communication with the hardware, in general a python wrapper around the |
| 20 | + hardware library. |
| 21 | +
|
| 22 | + """ |
| 23 | + params = comon_parameters + [ |
| 24 | + {'title': 'Trigger Active', 'name': 'trigger_active', 'type': 'bool', 'value': False}, |
| 25 | + {'title': 'Trigger Mode', 'name': 'trigger_mode', 'type': 'list', 'value': 'RISING_EDGE', |
| 26 | + 'limits': ['RISING_EDGE', 'FALLING_EDGE', 'ACTIVE_HIGH', |
| 27 | + 'ACTIVE_LOW']}, |
| 28 | + {'title': 'External_clock', 'name': 'extclock_mode', 'type': 'bool', 'value': False}, |
| 29 | + {'title': 'External sampling rate(Hz)', 'name': 'extclock_rate', 'type': 'int', 'value': 0, 'visible': False}, |
| 30 | + {'title': 'Number of samples:', 'name': 'num_sample', 'type': 'int', 'value': 1000, 'min': 0}, |
| 31 | + {'title': 'Sampling rate(Hz) :', 'name': 'sampling_rate', 'type': 'int', 'value': 10000, 'min': 0, 'max': 100000}, |
| 32 | + {'title': 'Range(V)', 'name': 'range', 'type': 'list', 'value': 10, 'limits': [10, 5, 2, 1]}, |
| 33 | + {'title': 'Mode', 'name': 'mode', 'type': 'list', 'value': 'SINGLE_END', |
| 34 | + 'limits': ['SINGLE_ENDED', 'DIFFERENTIAL']}, |
| 35 | + {'title': "Channel", 'name': 'channel_on', 'type': 'group', 'children': [ |
| 36 | + {'title': 'CH0H', 'name': 'CH0H', 'type': 'bool', 'value': False}, |
| 37 | + {'title': 'CH1H', 'name': 'CH1H', 'type': 'bool', 'value': False}, |
| 38 | + {'title': 'CH2H', 'name': 'CH2H', 'type': 'bool', 'value': False}, |
| 39 | + {'title': 'CH3H', 'name': 'CH3H', 'type': 'bool', 'value': False}, |
| 40 | + {'title': 'CH0L', 'name': 'CH0L', 'type': 'bool', 'value': False}, |
| 41 | + {'title': 'CH1L', 'name': 'CH1L', 'type': 'bool', 'value': False}, |
| 42 | + {'title': 'CH2L', 'name': 'CH2L', 'type': 'bool', 'value': False}, |
| 43 | + {'title': 'CH3L', 'name': 'CH3L', 'type': 'bool', 'value': False} |
| 44 | + ]} |
| 45 | + |
| 46 | + ] |
| 47 | + |
| 48 | + def __init__(self, parent=None, params_state=None): |
| 49 | + super().__init__(parent, params_state) |
| 50 | + self.num_channels = None |
| 51 | + |
| 52 | + def ini_attributes(self): |
| 53 | + self.controller: mcc128 = None |
| 54 | + self.option = 0 |
| 55 | + self.mode = 0 |
| 56 | + self.trigger_mode = 0 |
| 57 | + self.range = 0 |
| 58 | + self.channel_mask = 0 |
| 59 | + self.num_channels = 0 |
| 60 | + self.data_signal = [] |
| 61 | + self.list_channel_names = [] |
| 62 | + |
| 63 | + def commit_settings(self, param: Parameter): |
| 64 | + """Apply the consequences of a change of value in the detector settings |
| 65 | + Parameters |
| 66 | + ---------- |
| 67 | + param: Parameter |
| 68 | + A given parameter (within detector_settings) whose value has been changed by the user |
| 69 | + """ |
| 70 | + |
| 71 | + if param.name() == 'mode': |
| 72 | + self.set_mode() |
| 73 | + elif param.name() == 'range': |
| 74 | + self.set_range() |
| 75 | + elif param.name() == 'trigger_mode': |
| 76 | + self.set_trigger() |
| 77 | + elif param.name() in iter_children(self.settings.child('channel_on'), []): |
| 78 | + self.active_channel() |
| 79 | + elif param.name() == 'sampling_rate': |
| 80 | + sample_rate_real = self.controller.a_in_scan_actual_rate(self.num_channels, param.value()) |
| 81 | + param.setValue(sample_rate_real) |
| 82 | + elif param.name() == 'extclock_mode': |
| 83 | + self.settings.child('extclock_rate').setOpts(visible = param.value()) |
| 84 | + |
| 85 | + def scan_data(self, totalsamples: int, scan_rate: int, name_channel: int): |
| 86 | + """This starts a hardware-paced analog input channel scan . Then this function reads and returns the data from the |
| 87 | + buffer. |
| 88 | +
|
| 89 | + The aggregate sampling rate of all activated channels can't exceed 100kS/s |
| 90 | + ================= ================ |
| 91 | + Channels activate Scan rate (kS/s) |
| 92 | + ================= ================ |
| 93 | + 1 100 |
| 94 | + 2 50 |
| 95 | + 3 33,33 |
| 96 | + 4 25 |
| 97 | + 5 20 |
| 98 | + 6 16,67 |
| 99 | + 7 14,29 |
| 100 | + 8 12,50 |
| 101 | + ============== ================ |
| 102 | +
|
| 103 | + Parameters |
| 104 | + ----------- |
| 105 | + totalsamples: int |
| 106 | + The numbers of samples which we want to aquire |
| 107 | + scan_rate: int |
| 108 | + The sampling rate of each channel |
| 109 | + name_channel: int |
| 110 | + The bit mask of the channels activated |
| 111 | +
|
| 112 | + Returns |
| 113 | + ------- |
| 114 | + voltage.data : list |
| 115 | + The list of data scanned |
| 116 | + """ |
| 117 | + |
| 118 | + self.controller.a_in_scan_start(name_channel, totalsamples, scan_rate, self.option) |
| 119 | + voltage = self.controller.a_in_scan_read_numpy(totalsamples, 0.5) |
| 120 | + self.controller.a_in_scan_stop() |
| 121 | + self.controller.a_in_scan_cleanup() |
| 122 | + return voltage.data |
| 123 | + |
| 124 | + def set_range(self): |
| 125 | + """ This sets the analog input range to one of these values: |
| 126 | + +/- 10V |
| 127 | + +/- 5V |
| 128 | + +/- 2V |
| 129 | + +/- 1V |
| 130 | + """ |
| 131 | + |
| 132 | + if self.settings['range'] == 10: |
| 133 | + self.range = AnalogInputRange['BIP_10V'].value |
| 134 | + elif self.settings['range'] == 5: |
| 135 | + self.range = AnalogInputRange['BIP_5V'].value |
| 136 | + elif self.settings['range'] == 2: |
| 137 | + self.range = AnalogInputRange['BIP_2V'].value |
| 138 | + elif self.settings['range'] == 1: |
| 139 | + self.range = AnalogInputRange['BIP_1V'].value |
| 140 | + self.controller.a_in_range_write(self.range) |
| 141 | + |
| 142 | + def set_mode(self): |
| 143 | + """ This sets the analog input mode to one of two values |
| 144 | + * Single ended |
| 145 | + * Differential |
| 146 | + """ |
| 147 | + |
| 148 | + if self.settings['mode'] == 'SINGLE_ENDED': |
| 149 | + self.mode = AnalogInputMode.SE.value |
| 150 | + elif self.settings['mode'] == 'DIFFERENTIAL': |
| 151 | + self.mode = AnalogInputMode.DIFF.value |
| 152 | + self.controller.a_in_mode_write(self.mode) |
| 153 | + |
| 154 | + def set_trigger(self): |
| 155 | + """Read and Set the external trigger mode input of the card |
| 156 | +
|
| 157 | + There are 4 type available TRIGGER mode: |
| 158 | + *RISING_EDGE: Start the scan when the trigger signal transition from LOW to HIGH |
| 159 | + *FALLING_EDGE: Start the scan when the trigger signal transition from HIGH to LOW |
| 160 | + *ACTIVE_HIGH: Start the scan when the trigger signal is HIGH |
| 161 | + *ACTIVE_LOW: Start the scan when the trigger signal is LOW |
| 162 | +
|
| 163 | + """ |
| 164 | + |
| 165 | + mode_trigger = self.settings['trigger_mode'] |
| 166 | + if mode_trigger == 'RISING_EDGE': |
| 167 | + self.trigger_mode = TriggerModes.RISING_EDGE |
| 168 | + elif mode_trigger == 'FALLING_EDGE': |
| 169 | + self.trigger_mode = TriggerModes.FALLING_EDGE |
| 170 | + elif mode_trigger == 'ACTIVE_HIGH': |
| 171 | + self.trigger_mode = TriggerModes.ACTIVE_HIGH |
| 172 | + elif mode_trigger == 'ACTIVE_LOW': |
| 173 | + self.trigger_mode = TriggerModes.ACTIVE_LOW |
| 174 | + self.controller.trigger_mode(self.trigger_mode) |
| 175 | + |
| 176 | + |
| 177 | + def active_channel(self): |
| 178 | + """This function reads the states of channels on the interface and sets the bit mask of the active channels, |
| 179 | + the number of activated channels, and a list of the names of these channels. |
| 180 | +
|
| 181 | + """ |
| 182 | + |
| 183 | + bit_mask = 0 # the bit mask of the active channels |
| 184 | + number_channels = 0 # the number of activated channels |
| 185 | + name_channels = [] # the list of the names of these channels |
| 186 | + if self.settings['channel_on', 'CH0H']: |
| 187 | + bit_mask = bit_mask + 1 |
| 188 | + number_channels += 1 |
| 189 | + name_channels.append('CH0H') |
| 190 | + if self.settings['channel_on', 'CH1H']: |
| 191 | + bit_mask = bit_mask + 2 |
| 192 | + number_channels += 1 |
| 193 | + name_channels.append('CH1H') |
| 194 | + if self.settings['channel_on', 'CH2H']: |
| 195 | + bit_mask = bit_mask + 4 |
| 196 | + number_channels += 1 |
| 197 | + name_channels.append('CH2H') |
| 198 | + if self.settings['channel_on', 'CH3H']: |
| 199 | + bit_mask = bit_mask + 8 |
| 200 | + number_channels += 1 |
| 201 | + name_channels.append('CH3H') |
| 202 | + if self.settings['channel_on', 'CH0L']: |
| 203 | + bit_mask = bit_mask + 16 |
| 204 | + number_channels += 1 |
| 205 | + name_channels.append('CH0L') |
| 206 | + if self.settings['channel_on', 'CH1L']: |
| 207 | + bit_mask = bit_mask + 32 |
| 208 | + number_channels += 1 |
| 209 | + name_channels.append('CH1L') |
| 210 | + if self.settings['channel_on', 'CH2L']: |
| 211 | + bit_mask = bit_mask + 64 |
| 212 | + number_channels += 1 |
| 213 | + name_channels.append('CH2L') |
| 214 | + if self.settings['channel_on', 'CH3L']: |
| 215 | + bit_mask = bit_mask + 128 |
| 216 | + number_channels += 1 |
| 217 | + name_channels.append('CH3L') |
| 218 | + self.channel_mask, self.num_channels, self.label = bit_mask, number_channels, name_channels |
| 219 | + |
| 220 | + def get_data(self, list_signal_non_arranged, nb_channel_activated): |
| 221 | + """ Arrange the list of data received from activated channels |
| 222 | +
|
| 223 | + This methode arranges the list of data received from activated channels. It converts this list of data into a list |
| 224 | + of numpy arrays. The order of each array corresponds to the order of each activated channel on the interface (from CH0H to CH3L) |
| 225 | + Example: When you activate 2 channels CHOH and CH2L, this function will return a list of 2 arrays: the first one is |
| 226 | + data of channel CH0H and the second one is data of channel CH2L |
| 227 | +
|
| 228 | + Parameters |
| 229 | + ---------- |
| 230 | + list_signal_non_arranged : list |
| 231 | + List of data obtained by function scan_data() |
| 232 | + nb_channel_activated : int |
| 233 | + The quantity of activated channels |
| 234 | +
|
| 235 | + Returns |
| 236 | + ------- |
| 237 | + list_signal_arranged : list of numpy array |
| 238 | + list of data of each channel arranged |
| 239 | + """ |
| 240 | + list_signal_arranged = [] |
| 241 | + if len(list_signal_non_arranged) != 0: |
| 242 | + for k in range(nb_channel_activated): |
| 243 | + list_signal_arranged.append(np.array(list_signal_non_arranged[k])) |
| 244 | + i = nb_channel_activated |
| 245 | + while i <= len(list_signal_non_arranged) - nb_channel_activated: |
| 246 | + for j in range(nb_channel_activated): |
| 247 | + list_signal_arranged[j] = np.append(list_signal_arranged[j], list_signal_non_arranged[i + j]) |
| 248 | + i = i + nb_channel_activated |
| 249 | + return list_signal_arranged |
| 250 | + |
| 251 | + def ini_detector(self, controller=None): |
| 252 | + """Detector communication initialization |
| 253 | +
|
| 254 | + Parameters |
| 255 | + ---------- |
| 256 | + controller: (object) |
| 257 | + custom object of a PyMoDAQ plugin (Slave case). None if only one actuator/detector by controller |
| 258 | + (Master case) |
| 259 | +
|
| 260 | + Returns |
| 261 | + ------- |
| 262 | + info: str |
| 263 | + initialized: bool |
| 264 | + False if initialization failed otherwise True |
| 265 | + """ |
| 266 | + |
| 267 | + self.ini_detector_init(old_controller=controller, |
| 268 | + new_controller=mcc128(0)) |
| 269 | + |
| 270 | + info = "" |
| 271 | + initialized = True |
| 272 | + return info, initialized |
| 273 | + |
| 274 | + def close(self): |
| 275 | + pass |
| 276 | + """Terminate the communication protocol""" |
| 277 | + |
| 278 | + def grab_data(self, Naverage=1, **kwargs): |
| 279 | + """Start a grab from the detector |
| 280 | +
|
| 281 | + Parameters |
| 282 | + ---------- |
| 283 | + Naverage: int |
| 284 | + Number of hardware averaging (if hardware averaging is possible, self.hardware_averaging should be set to |
| 285 | + True in class preamble and you should code this implementation) |
| 286 | + kwargs: dict |
| 287 | + others optionals arguments |
| 288 | + """ |
| 289 | + |
| 290 | + num_sample = self.settings['num_sample'] |
| 291 | + freq = self.settings['sampling_rate'] |
| 292 | + |
| 293 | + self.option = 0 |
| 294 | + |
| 295 | + # Set up mode Trigger |
| 296 | + if self.settings['trigger_active'] == True: |
| 297 | + self.option += OptionFlags['EXTTRIGGER'].value |
| 298 | + |
| 299 | + # Set up mode External clock |
| 300 | + if self.settings['extclock_mode'] == True: |
| 301 | + self.option += OptionFlags['EXTCLOCK'].value |
| 302 | + external_spl_rate = self.settings['extclock_rate'] |
| 303 | + if external_spl_rate == 0: |
| 304 | + raise ValueError("Sampling rate cannot be zero") |
| 305 | + else: |
| 306 | + xaxis = Axis('time', 'seconds', np.arange(0, num_sample * 1 / external_spl_rate, 1 / external_spl_rate), |
| 307 | + 0) # Redefine the x-axis when the external clock is used |
| 308 | + else: |
| 309 | + xaxis = Axis('time', 'seconds', np.arange(0, num_sample * 1 / freq, 1 / freq), 0) |
| 310 | + |
| 311 | + self.channel_mask, self.num_channels, self.list_channel_names = self.active_channel() |
| 312 | + |
| 313 | + signal = self.scan_data(num_sample, freq, self.channel_mask) |
| 314 | + |
| 315 | + if len(signal) != 0: # Condition to avoid empty data error |
| 316 | + self.data_signal = self.get_data(signal, self.num_channels) |
| 317 | + |
| 318 | + self.dte_signal.emit(DataToExport('mcc128_plugin', data=[DataFromPlugins(name='mcc128', data=self.data_signal, |
| 319 | + dim='Data1D', |
| 320 | + labels=self.list_channel_names, |
| 321 | + axes=[xaxis])])) |
| 322 | + |
| 323 | + def stop(self): |
| 324 | + """Stop the current grab hardware wise if necessary""" |
| 325 | + return '' |
| 326 | + |
| 327 | + |
| 328 | +if __name__ == '__main__': |
| 329 | + main(__file__, init=False) |
0 commit comments