Skip to content

Instrument management (async)

The InstrumentManagerAsync() class and supporting functions use asyncio to provide a high-performance concurrent interface for instrument control.

These api for these functions and classes remain largely the same as the sequential (non-async) version.

The main difference is that these are async enabled. This means you have to use the await/async expressions to manage the event loop.

For example, to start a measurement:

>>> import pypalmsens as ps

>>> method = ps.CyclicVoltammetry()
>>> await ps.measure(method)

Or to manage the connection yourself:

>>> async with await ps.connect_async() as manager:
...     method = ps.ChronoAmperometry()
...     measurement = await manager.measure(method)

Or using InstrumentManagerAsync() directly as a context manager:

>>> instruments = await discover_async()

>>> async with ps.InstrumentManagerAsync(instruments[0]) as manager:
...     measurement = await manager.measure(method)

Or managing the instrument connection yourself:

>>> instruments = await discover_async()

>>> manager = ps.InstrumentManagerAsync(instruments[0])
>>> await manager.connect()
... # ...
>>> await manager.disconnect()

For more information, see the measurement documentation.

pypalmsens.connect_async async

connect_async(instrument: None | Instrument = None) -> InstrumentManagerAsync

Async connect to instrument and return InstrumentManagerAsync.

Connects to any plugged-in PalmSens USB device. Error if multiple devices are plugged-in.

Parameters:

  • instrument

    (Instrument, default: None ) –

    Connect to a specific instrument. Use pypalmsens.discover_async() to discover instruments.

Returns:

  • manager ( InstrumentManagerAsync ) –

    Return instance of InstrumentManagerAsync connected to the given instrument.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
async def connect_async(
    instrument: None | Instrument = None,
) -> InstrumentManagerAsync:
    """Async connect to instrument and return `InstrumentManagerAsync`.

    Connects to any plugged-in PalmSens USB device.
    Error if multiple devices are plugged-in.

    Parameters
    ----------
    instrument : Instrument, optional
        Connect to a specific instrument.
        Use `pypalmsens.discover_async()` to discover instruments.

    Returns
    -------
    manager : InstrumentManagerAsync
        Return instance of `InstrumentManagerAsync` connected to the given instrument.
    """
    if not instrument:
        available_instruments = await discover_async(ignore_errors=True)

        if not available_instruments:
            raise ConnectionError('No instruments were discovered.')

        if len(available_instruments) > 1:
            raise ConnectionError('More than one device discovered.')

        instrument = available_instruments[0]

    manager = InstrumentManagerAsync(instrument)
    await manager.connect()
    return manager

pypalmsens.discover_async async

discover_async(ftdi: bool = True, usbcdc: bool = True, winusb: bool = True, bluetooth: bool = False, serial: bool = True, ignore_errors: bool = False) -> list[Instrument]

Discover instruments.

For a list of device interfaces, see: https://sdk.palmsens.com/python/latest/installation.html#compatibility

Parameters:

  • ftdi

    (bool, default: True ) –

    If True, discover ftdi devices

  • usbcdc

    (bool, default: True ) –

    If True, discover usbcdc devices (Windows only)

  • winusb

    (bool, default: True ) –

    If True, discover winusb devices (Windows only)

  • bluetooth

    (bool, default: False ) –

    If True, discover bluetooth devices (Windows only)

  • serial

    (bool, default: True ) –

    If True, discover serial devices

  • ignore_errors

    (False, default: False ) –

    Ignores errors in device discovery

Returns:

  • discovered ( list[Instrument] ) –

    List of dataclasses with discovered instruments.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
async def discover_async(
    ftdi: bool = True,
    usbcdc: bool = True,
    winusb: bool = True,
    bluetooth: bool = False,
    serial: bool = True,
    ignore_errors: bool = False,
) -> list[Instrument]:
    """Discover instruments.

    For a list of device interfaces, see:
        https://sdk.palmsens.com/python/latest/installation.html#compatibility

    Parameters
    ----------
    ftdi : bool
        If True, discover ftdi devices
    usbcdc : bool
        If True, discover usbcdc devices (Windows only)
    winusb : bool
        If True, discover winusb devices (Windows only)
    bluetooth : bool
        If True, discover bluetooth devices (Windows only)
    serial : bool
        If True, discover serial devices
    ignore_errors : False
        Ignores errors in device discovery

    Returns
    -------
    discovered : list[Instrument]
        List of dataclasses with discovered instruments.
    """
    interfaces: dict[str, Any] = {}

    if ftdi:
        interfaces['ftdi'] = FTDIDevice

    if WINDOWS:
        if usbcdc:
            interfaces['usbcdc'] = USBCDCDevice

        if winusb:
            interfaces['winusb'] = WinUSBDevice

        if bluetooth:
            interfaces['bluetooth'] = BluetoothDevice
            interfaces['ble'] = BLEDevice

    if LINUX:
        if serial:
            interfaces['serial'] = SerialPortDevice

    instruments: list[Instrument] = []

    for name, interface in interfaces.items():
        try:
            devices: list[PalmSens.Devices.Device] = await create_future(
                interface.DiscoverDevicesAsync()
            )
        except System.DllNotFoundException:
            if ignore_errors:
                continue

            if name == 'ftdi':
                msg = (
                    'Cannot discover FTDI devices (missing driver).'
                    '\nfor more information see: '
                    'https://sdk.palmsens.com/python/latest/installation.html#ftdisetup'
                    '\nSet `ftdi=False` to hide this message.'
                )
                warnings.warn(msg, stacklevel=2)
                continue
            raise

        for device in devices:
            instruments.append(
                Instrument(
                    id=device.ToString(),
                    interface=name,
                    device=device,
                )
            )

    instruments.sort(key=lambda instrument: instrument.id)

    return instruments

pypalmsens.measure_async async

measure_async(method: BaseTechnique, instrument: None | Instrument = None, callback: Callback | None = None) -> Measurement

Run measurement async.

Executes the given method on any plugged-in PalmSens USB device. Error if multiple devices are plugged-in.

Parameters:

  • instrument

    (Instrument, default: None ) –

    Connect to and meassure on a specific instrument. Use pypalmsens.discover_async() to discover instruments.

  • callback

    (Callback | None, default: None ) –

    If specified, call this function on every new set of data points. New data points are batched, and contain all points since the last time it was called. Each point is an instance of ps.data.CallbackData for non-impedimetric or ps.data.CallbackDataEIS for impedimetric measurments.

Returns:

  • measurement ( Measurement ) –

    Finished measurement.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
async def measure_async(
    method: BaseTechnique,
    instrument: None | Instrument = None,
    callback: Callback | None = None,
) -> Measurement:
    """Run measurement async.

    Executes the given method on any plugged-in PalmSens USB device.
    Error if multiple devices are plugged-in.

    Parameters
    ----------
    instrument : Instrument, optional
        Connect to and meassure on a specific instrument.
        Use `pypalmsens.discover_async()` to discover instruments.
    callback: Callback, optional
        If specified, call this function on every new set of data points.
        New data points are batched, and contain all points since the last
        time it was called. Each point is an instance of `ps.data.CallbackData`
        for non-impedimetric or  `ps.data.CallbackDataEIS`
        for impedimetric measurments.

    Returns
    -------
    measurement : Measurement
        Finished measurement.
    """
    async with await connect_async(instrument=instrument) as manager:
        measurement = await manager.measure(method, callback=callback)

    assert measurement

    return measurement

pypalmsens.InstrumentManagerAsync

InstrumentManagerAsync(instrument: Instrument)

              flowchart TD
              pypalmsens.InstrumentManagerAsync[InstrumentManagerAsync]
              pypalmsens._instruments.instrument_manager_async.SupportedMixin[SupportedMixin]

                              pypalmsens._instruments.instrument_manager_async.SupportedMixin --> pypalmsens.InstrumentManagerAsync
                


              click pypalmsens.InstrumentManagerAsync href "" "pypalmsens.InstrumentManagerAsync"
              click pypalmsens._instruments.instrument_manager_async.SupportedMixin href "" "pypalmsens._instruments.instrument_manager_async.SupportedMixin"
            

Asynchronous instrument manager for PalmSens instruments.

Parameters:

  • instrument

    (Instrument) –

    Instrument to connect to, use discover() to find connected instruments.

Methods:

Attributes:

  • instrument (Instrument) –

    Instrument to connect to.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
297
298
299
300
301
302
303
def __init__(self, instrument: Instrument):
    self.instrument: Instrument = instrument
    """Instrument to connect to."""

    self._comm: CommManager
    self._status_callback: CallbackStatus
    self._loop: asyncio.AbstractEventLoop

instrument instance-attribute

instrument: Instrument = instrument

Instrument to connect to.

abort async

abort() -> None

Abort measurement.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
599
600
601
602
async def abort(self) -> None:
    """Abort measurement."""
    async with self._lock():
        await create_future(self._comm.AbortAsync())

connect async

connect() -> None

Connect to instrument.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
async def connect(self) -> None:
    """Connect to instrument."""
    if self.is_connected():
        return

    psinstrument = self.instrument.device
    try:
        await create_future(psinstrument.OpenAsync())
    except System.UnauthorizedAccessException as err:
        raise ConnectionError(
            f'Cannot open instrument connection (reason: {err.Message}). Check if the device is already in use.'
        ) from err

    self._comm = await create_future(CommManager.CommManagerAsync(psinstrument))

    firmware_warning(self._comm.Capabilities)

disconnect async

disconnect()

Disconnect from the instrument.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
696
697
698
699
700
701
702
703
async def disconnect(self):
    """Disconnect from the instrument."""
    if not self.is_connected():
        return

    await create_future(self._comm.DisconnectAsync())
    self._comm.Dispose()
    del self._comm

ensure_connection

ensure_connection()

Raises connection error if the instrument is not connected.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
348
349
350
351
def ensure_connection(self):
    """Raises connection error if the instrument is not connected."""
    if not self.is_connected():
        raise ConnectionError('Not connected to an instrument')

get_instrument_serial async

get_instrument_serial() -> str

Return instrument serial number.

Returns:

  • serial ( str ) –

    Instrument serial.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
440
441
442
443
444
445
446
447
448
449
450
451
452
453
async def get_instrument_serial(self) -> str:
    """Return instrument serial number.

    Returns
    -------
    serial : str
        Instrument serial.
    """
    async with self._lock():
        serial: PalmSens.Comm.DeviceSerialV3 = await create_future(
            self._comm.GetDeviceSerialAsync()
        )

    return serial.ToString()

initialize_multiplexer async

initialize_multiplexer(mux_model: int) -> int

Initialize the multiplexer.

Parameters:

  • mux_model

    (int) –

    The model of the multiplexer. - 0 = 8 channel - 1 = 16 channel - 2 = 32 channel

Returns:

  • channels ( int ) –

    Number of available multiplexes channels

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
async def initialize_multiplexer(self, mux_model: int) -> int:
    """Initialize the multiplexer.

    Parameters
    ----------
    mux_model: int
        The model of the multiplexer.
        - 0 = 8 channel
        - 1 = 16 channel
        - 2 = 32 channel

    Returns
    -------
    channels : int
        Number of available multiplexes channels
    """
    async with self._lock():
        model = MuxModel(mux_model)

        if model == MuxModel.MUX8R2 and (
            self._comm.ClientConnection.GetType().Equals(
                clr.GetClrType(PalmSens.Comm.ClientConnectionPS4)
            )
            or self._comm.ClientConnection.GetType().Equals(
                clr.GetClrType(PalmSens.Comm.ClientConnectionMS)
            )
        ):
            await create_future(self._comm.ClientConnection.ReadMuxInfoAsync())

        self._comm.Capabilities.MuxModel = model

        if self._comm.Capabilities.MuxModel == MuxModel.MUX8:
            self._comm.Capabilities.NumMuxChannels = 8
        elif self._comm.Capabilities.MuxModel == MuxModel.MUX16:
            self._comm.Capabilities.NumMuxChannels = 16
        elif self._comm.Capabilities.MuxModel == MuxModel.MUX8R2:
            await create_future(self._comm.ClientConnection.ReadMuxInfoAsync())

    channels = self._comm.Capabilities.NumMuxChannels
    return channels

is_connected

is_connected() -> bool

Return True if an instrument connection exists.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
339
340
341
342
343
344
345
346
def is_connected(self) -> bool:
    """Return True if an instrument connection exists."""
    try:
        self._comm
    except AttributeError:
        return False
    else:
        return True

is_measuring

is_measuring() -> bool

Return True if device is measuring.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
319
320
321
def is_measuring(self) -> bool:
    """Return True if device is measuring."""
    return int(self._comm.State) == CommManager.DeviceState.Measurement

measure async

measure(method: BaseTechnique, *, callback: Callback | None = None, sync_event: Event | None = None)

Start measurement using given method parameters.

Parameters:

  • method

    (BaseTechnique) –

    Method parameters for measurement

  • callback

    (Callback | None, default: None ) –

    If specified, call this function on every new set of data points. New data points are batched, and contain all points since the last time it was called. Each point is an instance of ps.data.CallbackData for non-impedimetric or ps.data.CallbackDataEIS for impedimetric measurments.

  • sync_event

    (Event | None, default: None ) –

    Event for hardware synchronization. Do not use directly. Instead, initiate hardware sync via InstrumentPoolAsync.measure().

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
async def measure(
    self,
    method: BaseTechnique,
    *,
    callback: Callback | None = None,
    sync_event: asyncio.Event | None = None,
):
    """Start measurement using given method parameters.

    Parameters
    ----------
    method: MethodParameters
        Method parameters for measurement
    callback: Callback, optional
        If specified, call this function on every new set of data points.
        New data points are batched, and contain all points since the last
        time it was called. Each point is an instance of `ps.data.CallbackData`
        for non-impedimetric or  `ps.data.CallbackDataEIS`
        for impedimetric measurments.
    sync_event: asyncio.Event
        Event for hardware synchronization. Do not use directly.
        Instead, initiate hardware sync via `InstrumentPoolAsync.measure()`.
    """
    psmethod = method._to_psmethod()

    self.ensure_connection()

    self.validate_method(psmethod)

    measurement_manager = MeasurementManagerAsync(comm=self._comm)

    return await measurement_manager.measure(
        psmethod, callback=callback, sync_event=sync_event
    )

read_current async

read_current() -> float

Read the current in µA.

Returns:

  • current ( float ) –

    Current in µA.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
413
414
415
416
417
418
419
420
421
422
423
424
async def read_current(self) -> float:
    """Read the current in µA.

    Returns
    -------
    current : float
        Current in µA.
    """
    async with self._lock():
        current: float = await create_future(self._comm.GetCurrentAsync())

    return current

read_potential async

read_potential() -> float

Read the potential in V.

Returns:

  • potential ( float ) –

    Potential in V.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
426
427
428
429
430
431
432
433
434
435
436
437
438
async def read_potential(self) -> float:
    """Read the potential in V.

    Returns
    -------
    potential : float
        Potential in V.
    """

    async with self._lock():
        potential: float = await create_future(self._comm.GetPotentialAsync())

    return potential

register_status_callback

register_status_callback(callback: CallbackStatus)

Register callback for idle status events.

The callback is triggered when the current/potential are updated durinig idle state or pretreatment phases.

callback: CallbackStatus The function to call when triggered

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
def register_status_callback(self, callback: CallbackStatus, /):
    """Register callback for idle status events.

    The callback is triggered when the current/potential are updated
    durinig idle state or pretreatment phases.

    callback: CallbackStatus
        The function to call when triggered
    """
    self._status_callback = callback
    self._loop = asyncio.get_running_loop()

    self.status_idle_handler_async: AsyncEventHandler = AsyncEventHandler(
        self._idle_status_handler
    )

    self._comm.ReceiveStatusAsync += self._idle_status_handler

set_cell async

set_cell(cell_on: bool) -> None

Turn the cell on or off.

Parameters:

  • cell_on

    (bool) –

    If true, turn on the cell

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
377
378
379
380
381
382
383
384
385
386
async def set_cell(self, cell_on: bool) -> None:
    """Turn the cell on or off.

    Parameters
    ----------
    cell_on : bool
        If true, turn on the cell
    """
    async with self._lock():
        await create_future(self._comm.SetCellOnAsync(cell_on))

set_current_range async

set_current_range(current_range: AllowedCurrentRanges)

Set the current range for the cell.

Parameters:

  • current_range

    (AllowedCurrentRanges) –

    Set the current range as a string. See pypalmsens.settings.AllowedCurrentRanges for options.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
399
400
401
402
403
404
405
406
407
408
409
410
411
async def set_current_range(self, current_range: AllowedCurrentRanges):
    """Set the current range for the cell.

    Parameters
    ----------
    current_range: AllowedCurrentRanges
        Set the current range as a string.
        See `pypalmsens.settings.AllowedCurrentRanges` for options.
    """
    async with self._lock():
        await create_future(
            self._comm.SetCurrentRangeAsync(cr_string_to_enum(current_range))
        )

set_multiplexer_channel async

set_multiplexer_channel(channel: int)

Sets the multiplexer channel.

Parameters:

  • channel

    (int) –

    Index of the channel to set.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
685
686
687
688
689
690
691
692
693
694
async def set_multiplexer_channel(self, channel: int):
    """Sets the multiplexer channel.

    Parameters
    ----------
    channel : int
        Index of the channel to set.
    """
    async with self._lock():
        await create_future(self._comm.ClientConnection.SetMuxChannelAsync(channel))

set_mux8r2_settings async

Set the settings for the Mux8R2 multiplexer.

Parameters:

  • connect_sense_to_working_electrode

    (bool, default: False ) –

    Connect the sense electrode to the working electrode. Default is False.

  • combine_reference_and_counter_electrodes

    (bool, default: False ) –

    Combine the reference and counter electrodes. Default is False.

  • use_channel_1_reference_and_counter_electrodes

    (bool, default: False ) –

    Use channel 1 reference and counter electrodes for all working electrodes. Default is False.

  • set_unselected_channel_working_electrode

    (int, default: 0 ) –

    Set the unselected channel working electrode to disconnected/floating (0), ground (1), or standby potential (2). Default is 0.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
async def set_mux8r2_settings(
    self,
    connect_sense_to_working_electrode: bool = False,
    combine_reference_and_counter_electrodes: bool = False,
    use_channel_1_reference_and_counter_electrodes: bool = False,
    set_unselected_channel_working_electrode: int = 0,
):
    """Set the settings for the Mux8R2 multiplexer.

    Parameters
    ---------
    connect_sense_to_working_electrode: float
        Connect the sense electrode to the working electrode. Default is False.
    combine_reference_and_counter_electrodes: float
        Combine the reference and counter electrodes. Default is False.
    use_channel_1_reference_and_counter_electrodes: float
        Use channel 1 reference and counter electrodes for all working electrodes. Default is False.
    set_unselected_channel_working_electrode: float
        Set the unselected channel working electrode to disconnected/floating (0), ground (1), or standby potential (2). Default is 0.
    """
    self.ensure_connection()

    if self._comm.Capabilities.MuxModel != MuxModel.MUX8R2:
        raise ValueError(
            f"Incompatible mux model: {self._comm.Capabilities.MuxModel}, expected 'MUXR2'."
        )

    mux_settings = PSMethod.MuxSettings(False)
    mux_settings.ConnSEWE = connect_sense_to_working_electrode
    mux_settings.ConnectCERE = combine_reference_and_counter_electrodes
    mux_settings.CommonCERE = use_channel_1_reference_and_counter_electrodes
    mux_settings.UnselWE = PSMethod.MuxSettings.UnselWESetting(
        set_unselected_channel_working_electrode
    )

    async with self._lock():
        await create_future(
            self._comm.ClientConnection.SetMuxSettingsAsync(MuxType(1), mux_settings)
        )

set_potential async

set_potential(potential: float) -> None

Set the potential of the cell.

Parameters:

  • potential

    (float) –

    Potential in V

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
388
389
390
391
392
393
394
395
396
397
async def set_potential(self, potential: float) -> None:
    """Set the potential of the cell.

    Parameters
    ----------
    potential : float
        Potential in V
    """
    async with self._lock():
        await create_future(self._comm.SetPotentialAsync(potential))

status

status() -> Status

Get status.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
370
371
372
373
374
375
def status(self) -> Status:
    """Get status."""
    return Status(
        self._comm.get_Status(),
        device_state=str(self._comm.get_State()),  # type:ignore
    )

supported_applied_current_ranges

supported_applied_current_ranges() -> list[AllowedCurrentRanges]

List applied current ranges supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
251
252
253
254
255
256
257
258
259
260
261
def supported_applied_current_ranges(self: HasCommProtocol) -> list[AllowedCurrentRanges]:
    """List applied current ranges supported by this device.

    Returns
    -------
    current_ranges: list[AllowedCurrentRanges]
    """
    self.ensure_connection()
    capabilities = self._comm.Capabilities

    return [cr_enum_to_string(cr) for cr in capabilities.SupportedAppliedRanges]

supported_bipot_ranges

supported_bipot_ranges() -> list[AllowedCurrentRanges]

List applied current ranges supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
263
264
265
266
267
268
269
270
271
272
273
def supported_bipot_ranges(self: HasCommProtocol) -> list[AllowedCurrentRanges]:
    """List applied current ranges supported by this device.

    Returns
    -------
    current_ranges: list[AllowedCurrentRanges]
    """
    self.ensure_connection()
    capabilities = self._comm.Capabilities

    return [cr_enum_to_string(cr) for cr in capabilities.SupportedBipotRanges]

supported_current_ranges

supported_current_ranges() -> list[AllowedCurrentRanges]

List current ranges supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
239
240
241
242
243
244
245
246
247
248
249
def supported_current_ranges(self: HasCommProtocol) -> list[AllowedCurrentRanges]:
    """List current ranges supported by this device.

    Returns
    -------
    current_ranges: list[AllowedCurrentRanges]
    """
    self.ensure_connection()
    capabilities = self._comm.Capabilities

    return [cr_enum_to_string(cr) for cr in capabilities.SupportedRanges]

supported_methods

supported_methods() -> list[str]

List methods supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def supported_methods(self: HasCommProtocol) -> list[str]:
    """List methods supported by this device.

    Returns
    -------
    methods: list[str]
    """
    self.ensure_connection()
    capabilities = self._comm.Capabilities
    numbers = list(capabilities.SupportedMethods)
    method_ids = []

    for number in numbers:
        try:
            id = PalmSens.Method.FromTechniqueNumber(number).MethodID
        except Exception:
            pass
        else:
            method_ids.append(id)

    return method_ids

supported_potential_ranges

supported_potential_ranges() -> list[AllowedPotentialRanges]

List applied potential ranges supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
275
276
277
278
279
280
281
282
283
284
285
def supported_potential_ranges(self: HasCommProtocol) -> list[AllowedPotentialRanges]:
    """List applied potential ranges supported by this device.

    Returns
    -------
    potential_ranges: list[AllowedPotentialRanges]
    """
    self.ensure_connection()
    capabilities = self._comm.Capabilities

    return [pr_enum_to_string(pr) for pr in capabilities.SupportedPotentialRanges]

unregister_status_callback

unregister_status_callback()

Unregister callback from idle status events.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
473
474
475
476
def unregister_status_callback(self):
    """Unregister callback from idle status events."""
    self._comm.ReceiveStatusAsync -= self._idle_status_handler
    del self._status_callback

validate_method

validate_method(method: Method | BaseTechnique) -> None

Validate method.

Raise ValueError if the method cannot be validated.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
487
488
489
490
491
492
493
494
495
496
497
498
499
500
def validate_method(self, method: PSMethod | BaseTechnique) -> None:
    """Validate method.

    Raise ValueError if the method cannot be validated."""
    self.ensure_connection()

    if not isinstance(method, PSMethod):
        method = method._to_psmethod()

    errors = method.Validate(self._comm.Capabilities)

    if any(error.IsFatal for error in errors):
        message = '\n'.join([error.Message for error in errors])
        raise ValueError(f'Method not compatible:\n{message}')

wait_digital_trigger async

wait_digital_trigger(wait_for_high: bool) -> None

Wait for digital trigger.

Parameters:

  • wait_for_high

    (bool) –

    Wait for digital line high before starting

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
585
586
587
588
589
590
591
592
593
594
595
596
597
async def wait_digital_trigger(self, wait_for_high: bool) -> None:
    """Wait for digital trigger.

    Parameters
    ----------
    wait_for_high: bool
        Wait for digital line high before starting
    """
    async with self._lock():
        while True:
            if await create_future(self._comm.DigitalLineD0Async()) == wait_for_high:
                break
            await asyncio.sleep(0.05)

pypalmsens.InstrumentPoolAsync

InstrumentPoolAsync(devices_or_managers: Sequence[Instrument | InstrumentManagerAsync])

Manages a set of instrument.

Most calls are run asynchronously in the background, which means that measurements are running in parallel.

Parameters:

Methods:

  • add

    Open and add manager to the pool.

  • connect

    Connect all instrument managers in the pool.

  • disconnect

    Disconnect all instrument managers in the pool.

  • is_connected

    Return true if all managers in the pool are connected.

  • is_disconnected

    Return true if all managers in the pool are disconnected.

  • measure

    Concurrently start measurement on all managers in the pool.

  • remove

    Close and remove manager from pool.

  • submit

    Concurrently start measurement on all managers in the pool.

Attributes:

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
26
27
28
29
30
31
32
33
34
35
36
37
def __init__(
    self,
    devices_or_managers: Sequence[Instrument | InstrumentManagerAsync],
):
    self.managers: list[InstrumentManagerAsync] = []
    """List of instruments managers in the pool."""

    for item in devices_or_managers:
        if isinstance(item, Instrument):
            self.managers.append(InstrumentManagerAsync(item))
        else:
            self.managers.append(item)

managers instance-attribute

managers: list[InstrumentManagerAsync] = []

List of instruments managers in the pool.

add async

Open and add manager to the pool.

Parameters:

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
78
79
80
81
82
83
84
85
86
87
async def add(self, manager: InstrumentManagerAsync) -> None:
    """Open and add manager to the pool.

    Parameters
    ----------
    manager : InstrumentManagerAsync
        Instance of an instrument manager.
    """
    await manager.connect()
    self.managers.append(manager)

connect async

connect() -> None

Connect all instrument managers in the pool.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
49
50
51
52
async def connect(self) -> None:
    """Connect all instrument managers in the pool."""
    tasks = [manager.connect() for manager in self.managers]
    await asyncio.gather(*tasks)

disconnect async

disconnect() -> None

Disconnect all instrument managers in the pool.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
54
55
56
57
async def disconnect(self) -> None:
    """Disconnect all instrument managers in the pool."""
    tasks = [manager.disconnect() for manager in self.managers]
    await asyncio.gather(*tasks)

is_connected

is_connected() -> bool

Return true if all managers in the pool are connected.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
59
60
61
def is_connected(self) -> bool:
    """Return true if all managers in the pool are connected."""
    return all(manager.is_connected() for manager in self.managers)

is_disconnected

is_disconnected() -> bool

Return true if all managers in the pool are disconnected.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
63
64
65
def is_disconnected(self) -> bool:
    """Return true if all managers in the pool are disconnected."""
    return not any(manager.is_connected() for manager in self.managers)

measure async

measure(method: BaseTechnique, **kwargs) -> list[Measurement]

Concurrently start measurement on all managers in the pool.

For hardware synchronization, set use_hardware_sync on the method. In addition, the pool must contain: - channels from a single multi-channel instrument only - the first channel of the multi-channel instrument - at least two channels

All instruments are prepared and put in a waiting state. The measurements are started via a hardware sync trigger on channel 1.

Parameters:

  • method

    (MethodSettings) –

    Method parameters for measurement.

  • **kwargs

    These keyword parameters are passed to the measure function.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
async def measure(
    self,
    method: BaseTechnique,
    **kwargs,
) -> list[Measurement]:
    """Concurrently start measurement on all managers in the pool.

    For hardware synchronization, set `use_hardware_sync` on the method.
    In addition, the pool must contain:
    - channels from a single multi-channel instrument only
    - the first channel of the multi-channel instrument
    - at least two channels

    All instruments are prepared and put in a waiting state.
    The measurements are started via a hardware sync trigger on channel 1.

    Parameters
    ----------
    method : MethodSettings
        Method parameters for measurement.
    **kwargs
        These keyword parameters are passed to the measure function.
    """
    tasks: list[Awaitable[Measurement]] = []

    if hasattr(method, 'general') and method.general.use_hardware_sync:
        tasks = await self._measure_hw_sync(method)
    else:
        for manager in self.managers:
            tasks.append(manager.measure(method, **kwargs))

    results = await asyncio.gather(*tasks)
    return results

remove async

Close and remove manager from pool.

Parameters:

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
67
68
69
70
71
72
73
74
75
76
async def remove(self, manager: InstrumentManagerAsync) -> None:
    """Close and remove manager from pool.

    Parameters
    ----------
    manager : InstrumentManagerAsync
        Instance of an instrument manager.
    """
    self.managers.remove(manager)
    _ = await manager.disconnect()

submit async

submit(func: Callable, **kwargs: Any) -> list[Any]

Concurrently start measurement on all managers in the pool.

Parameters:

  • func

    (Callable) –

    This function gets called with an instance of InstrumentManagerAsync as the argument.

  • **kwargs

    (Any, default: {} ) –

    These keyword arguments are passed on to the submitted function.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
async def submit(self, func: Callable, **kwargs: Any) -> list[Any]:
    """Concurrently start measurement on all managers in the pool.

    Parameters
    ----------
    func : Callable
        This function gets called with an instance of
        `InstrumentManagerAsync` as the argument.
    **kwargs
        These keyword arguments are passed on to the submitted function.
    """
    tasks: list[Awaitable[Any]] = []
    for manager in self.managers:
        tasks.append(func(manager, **kwargs))

    results = await asyncio.gather(*tasks)
    return results