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
>>> import asyncio

>>> method = ps.CyclicVoltammetry()

>>> async def main():
...     await ps.measure_async(method)

>>> asyncio.run(main())

Or to manage the connection yourself:

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

>>> asyncio.run(main())

Or using InstrumentManagerAsync() directly as a context manager:

>>> async def main():
...     instrument, *_ = await ps.discover_async()
...     async with ps.InstrumentManagerAsync(instrument) as manager:
...         measurement = await manager.measure(method)

>>> asyncio.run(main())

Or managing the instrument connection yourself:

>>> async def main():
...     instrument, *_ = await ps.discover_async()
...     manager = ps.InstrumentManagerAsync(instrument)
...     await manager.connect()
...     # ...
...     await manager.disconnect()

>>> asyncio.run(main())

For more information, see the measurement documentation.

Classes:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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

    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://dev.palmsens.com/python/latest/_attachments/installation/index.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.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
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://dev.palmsens.com/python/latest/_attachments/installation/index.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 WINDOWS:
        if ftdi:
            interfaces['ftdi'] = PSDevices.FTDIDevice

        if usbcdc:
            interfaces['usbcdc'] = PSDevices.USBCDCDevice

        if winusb:
            interfaces['winusb'] = PSDevices.WinUSBDevice

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

    if LINUX:
        if ftdi:
            interfaces['ftdi'] = PSDevices.FTDIDevice

        if serial:
            interfaces['serial'] = PSDevices.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://dev.palmsens.com/python/latest/_attachments/installation/index.html#ftdisetup'
                    '\nSet `ftdi=False` to hide this message.'
                )
                warnings.warn(msg, stacklevel=2)
                continue
            raise

        for device in devices:
            instruments.append(Instrument._from_device(device))

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

    return instruments

pypalmsens.measure_async async

measure_async(method: MethodTypeCompatible, instrument: None | Instrument = None, callback: Callback | CallbackEIS | 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 | CallbackEIS | 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
 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
async def measure_async(
    method: MethodTypeCompatible,
    instrument: None | Instrument = None,
    callback: Callback | CallbackEIS | 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)

Asynchronous instrument manager for PalmSens instruments.

Parameters:

  • instrument

    (Instrument) –

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

Methods:

Attributes:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
122
123
124
125
126
127
128
129
130
131
132
133
def __init__(self, instrument: Instrument):
    super().__init__()

    self.instrument: Instrument = instrument
    """Instrument being managed by this class."""

    self.gpio: GPIOAsync = GPIOAsync(self)
    """High-level GPIO interface."""

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

capabilities property

capabilities: Capabilities

Retrieve device capabilities and device info as a dataclass.

Returns:

  • capabilities ( Capabilities ) –

    Device capabilities and device info.

gpio instance-attribute

gpio: GPIOAsync = GPIOAsync(self)

High-level GPIO interface.

instrument instance-attribute

instrument: Instrument = instrument

Instrument being managed by this class.

abort async

abort() -> None

Abort measurement.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
440
441
442
443
async def abort(self) -> None:
    """Abort measurement."""
    async with self._lock():
        await create_future(self._comm.AbortAsync())

configure_mux8r2 async

configure_mux8r2(*, connect_se_we: bool = False, combine_re_ce: bool = False, common_re_ce: bool = False, unused_we: Literal['float', 'ground', 'standby'] = 'float')

Configure the Mux8R2 multiplexer.

This method sets the Mux8R2 parameters globally, including for techniques. If you specify pypalmsens.settings.Multiplexer in your technique, those settings will override the values set here.

Parameters:

  • connect_se_we

    (bool, default: False ) –

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

  • combine_re_ce

    (bool, default: False ) –

    Combine the reference and counter electrodes. Default is False.

  • common_re_ce

    (bool, default: False ) –

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

  • unused_we

    (Literal['float', 'ground', 'standby'], default: 'float' ) –

    State of the unused channel working electrodes: floating, ground, or standby potential. Default is 'float'.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
async def configure_mux8r2(
    self,
    *,
    connect_se_we: bool = False,
    combine_re_ce: bool = False,
    common_re_ce: bool = False,
    unused_we: Literal['float', 'ground', 'standby'] = 'float',
):
    """Configure the Mux8R2 multiplexer.

    This method sets the Mux8R2 parameters globally, including for techniques.
    If you specify [pypalmsens.settings.Multiplexer][] in your technique,
    those settings will override the values set here.

    Parameters
    ---------
    connect_se_we : bool, optional
        Connect the sense electrode to the working electrode. Default is False.
    combine_re_ce : bool, optional
        Combine the reference and counter electrodes. Default is False.
    common_re_ce : bool, optional
        Use channel 1 reference and counter electrodes for all working electrodes. Default is False.
    unused_we : Literal['float', 'ground', 'standby'], optional
        State of the unused channel working electrodes: floating,
        ground, or standby potential. Default is 'float'.
    """
    self.ensure_connection()

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

    mux_settings = PalmSens.Method.MuxSettings(False)
    mux_settings.ConnSEWE = connect_se_we
    mux_settings.ConnectCERE = combine_re_ce
    mux_settings.CommonCERE = common_re_ce
    unused_we_setting = {
        'float': PalmSens.Method.MuxSettings.UnselWESetting.FLOAT,
        'ground': PalmSens.Method.MuxSettings.UnselWESetting.GND,
        'standby': PalmSens.Method.MuxSettings.UnselWESetting.VSTDBY,
    }[unused_we]
    mux_settings.UnselWE = unused_we_setting

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

connect async

connect() -> None

Connect to instrument.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
172
173
174
175
176
177
178
179
180
181
182
async def connect(self) -> None:
    """Connect to instrument."""
    if self.is_connected():
        return

    self._comm = await self.instrument._connect_async()

    # Disable idle messages to improve response time and reduce noise
    await create_future(self._comm.SetStatusWhenIdleAsync(False))

    firmware_warning(self._comm.Capabilities)

disconnect async

disconnect()

Disconnect from the instrument.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
598
599
600
601
602
603
604
605
606
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
167
168
169
170
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_current_range async

get_current_range() -> AllowedCurrentRanges

Get the current range for the cell.

Returns:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
243
244
245
246
247
248
249
250
251
252
253
254
255
async def get_current_range(self) -> AllowedCurrentRanges:
    """Get the current range for the cell.

    Returns
    -------
    current_range: AllowedCurrentRanges
    """
    async with self._lock():
        value: PalmSens.CurrentRange = await create_future(
            self._comm.GetCurrentRangeAsync()
        )

    return cr_enum_to_string(value)

get_estimated_duration

get_estimated_duration(method: Method | MethodTypeCompatible) -> float

Get the estimated duration for this method.

Parameters:

  • method

    (MethodType) –

    The method to get the estimated duration for.

Returns:

  • float

    Estimated duration in seconds.

Source code in src/pypalmsens/_instruments/capabilities_mixin.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def get_estimated_duration(
    self: HasCommProtocol,
    method: PalmSens.Method | MethodTypeCompatible,
) -> float:
    """Get the estimated duration for this method.

    Parameters
    -----------
    method : MethodType
        The method to get the estimated duration for.

    Returns
    -------
    float
        Estimated duration in seconds.
    """
    self.ensure_connection()

    if not isinstance(method, PalmSens.Method):
        method = method._to_psmethod()

    capabilities = self._comm.Capabilities

    return method.GetMinimumEstimatedMeasurementDuration(capabilities)

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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
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()

get_potential_range async

get_potential_range() -> AllowedPotentialRanges

Get the potential range for the cell.

Returns:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
296
297
298
299
300
301
302
303
304
305
async def get_potential_range(self) -> AllowedPotentialRanges:
    """Get the potential range for the cell.

    Returns
    -------
    potential_range: AllowedPotentialRanges
    """
    async with self._lock():
        # no such api: self._comm.GetPotentialRangeAsync()
        return pr_enum_to_string(self._comm.PotentialRange)

initialize_multiplexer async

initialize_multiplexer(model: AllowedMuxModels) -> int

Initialize the multiplexer.

Parameters:

  • model

    (Literal['mux8', 'mux16', 'mux8r2']) –

    The model of the multiplexer.

    • 'mux8': 8 channels
    • 'mux16': 16 channels
    • 'mux8r2': 8 to 128 channels

Returns:

  • channels ( int ) –

    Number of available multiplexes channels

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
492
493
494
495
496
497
498
499
500
501
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
536
async def initialize_multiplexer(self, model: AllowedMuxModels) -> int:
    """Initialize the multiplexer.

    Parameters
    ----------
    model : Literal['mux8', 'mux16', 'mux8r2']
        The model of the multiplexer.

        - 'mux8': 8 channels
        - 'mux16': 16 channels
        - 'mux8r2': 8 to 128 channels

    Returns
    -------
    channels : int
        Number of available multiplexes channels
    """
    mux_model = {
        'mux8': PalmSens.MuxModel.MUX8,
        'mux16': PalmSens.MuxModel.MUX16,
        'mux8r2': PalmSens.MuxModel.MUX8R2,
    }[model]

    async with self._lock():
        if mux_model == PalmSens.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 = mux_model

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

    channels = self._comm.Capabilities.NumMuxChannels
    return channels

is_cell_on async

is_cell_on() -> bool

Get cell status.

Returns:

  • cell_on ( bool ) –

    Return true if the cell is on

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
217
218
219
220
221
222
223
224
225
226
227
228
async def is_cell_on(self) -> bool:
    """Get cell status.

    Returns
    -------
    cell_on : bool
        Return true if the cell is on
    """
    async with self._lock():
        cell_on: bool = await create_future(self._comm.GetCellOnAsync())

    return cell_on

is_connected

is_connected() -> bool

Return True if an instrument connection exists.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
163
164
165
def is_connected(self) -> bool:
    """Return True if an instrument connection exists."""
    return hasattr(self, '_comm')

is_measuring

is_measuring() -> bool

Return True if device is measuring.

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

measure async

measure(method: MethodTypeCompatible, *, callback: Callback | CallbackEIS | None = None, stream: Path | str | None = None, sync_event: Event | None = None)

Start measurement using given method parameters.

Parameters:

  • method

    (MethodTypeCompatible) –

    Method parameters for measurement.

  • callback

    (Callback | CallbackEIS | 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.

  • stream

    (Path | str | None, default: None ) –

    If defined, stream data directly to this file in JSON Lines text format (https://jsonlines.org). This option is useful for long-term measurements. In case of a PC crash or power outage, the most recent measurement data will still be available.

  • 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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
async def measure(
    self,
    method: MethodTypeCompatible,
    *,
    callback: Callback | CallbackEIS | None = None,
    stream: Path | str | None = None,
    sync_event: asyncio.Event | None = None,
):
    """Start measurement using given method parameters.

    Parameters
    ----------
    method: MethodType
        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.
    stream: Path | str | None
        If defined, stream data directly to this file in JSON Lines text format
        (https://jsonlines.org). This option is useful for long-term measurements.
        In case of a PC crash or power outage, the most recent measurement data will
        still be available.
    sync_event: asyncio.Event
        Event for hardware synchronization. Do not use directly.
        Instead, initiate hardware sync via `InstrumentPoolAsync.measure()`.
    """
    self.ensure_connection()
    self.validate_method(method)  # type: ignore

    measurement_manager = MeasurementManagerAsync(comm=self._comm)

    return await measurement_manager.measure(
        method,
        callback=callback,
        stream=stream,
        sync_event=sync_event,
        listeners=self._listeners,
    )

on

on(event: AllowedEvents, callback: Callable[..., None]) -> EventHandle

Register a callback to invoke for the specified event.

Parameters:

  • event

    (AllowedEvents) –

    Name of the event to subscribe to. For most events this simply appends callback to the internal listeners dictionary.

  • callback

    (Callable[..., None]) –

    Function that will be invoked when the event is triggered.

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Notes
  • 'receive_message' and 'receive_status' are handled specially: they create instances of :class:EventHandleReceiveMessage or :class:EventHandleStatus, respectively, which attach the callback directly to the underlying communication layer.
Source code in src/pypalmsens/_instruments/events_mixin.py
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
139
140
141
142
143
def on(
    self,
    event: AllowedEvents,
    callback: Callable[..., None],
) -> EventHandle:
    """Register a callback to invoke for the specified event.

    Parameters
    ----------
    event : AllowedEvents
        Name of the event to subscribe to. For most events this simply
        appends callback to the internal listeners dictionary.
    callback : Callable[..., None]
        Function that will be invoked when the event is triggered.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.

    Notes
    -----
    * ``'receive_message'`` and ``'receive_status'`` are handled specially:
      they create instances of :class:`EventHandleReceiveMessage` or
      :class:`EventHandleStatus`, respectively, which attach the callback
      directly to the underlying communication layer.
    """
    if event == 'receive_message':
        return EventHandleReceiveMessage(emitter=self, event=event, callback=callback)

    elif event == 'receive_status':
        return EventHandleStatus(emitter=self, event=event, callback=callback)

    self._listeners[event].append(callback)
    return EventHandle(emitter=self, event=event, callback=callback)

on_curve_begin

on_curve_begin(callback: Callable[[Curve], None]) -> EventHandle

Register a callback to invoke at the start of a new curve.

For EIS use on_eis_data_start.

Parameters:

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def on_curve_begin(self, callback: Callable[[Curve], None]) -> EventHandle:
    """Register a callback to invoke at the start of a new curve.

    For EIS use `on_eis_data_start`.

    Parameters
    ----------
    callback : Callable[[Curve]]
        The function to call when triggered.
        Passes [pypalmsens.data.Curve][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('curve_begin', callback=callback)

on_curve_end

on_curve_end(callback: Callable[[Curve], None]) -> EventHandle

Register a callback to invoke at the end of a curve.

For EIS use on_eis_data_end.

Parameters:

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
def on_curve_end(self, callback: Callable[[Curve], None]) -> EventHandle:
    """Register a callback to invoke at the end of a curve.

    For EIS use `on_eis_data_end`.

    Parameters
    ----------
    callback : Callable[[Curve]]
        The function to call when triggered.
        Passes [pypalmsens.data.Curve][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('curve_end', callback=callback)

on_curve_new_data

on_curve_new_data(callback: Callable[[CallbackData], None]) -> EventHandle

Register a callback to invoke when new data are received

Note that the data are batched depending on available resources.

For EIS use on_eis_new_data.

Parameters:

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def on_curve_new_data(self, callback: Callable[[CallbackData], None]) -> EventHandle:
    """Register a callback to invoke when new data are received

    Note that the data are batched depending on available resources.

    For EIS use `on_eis_new_data`.

    Parameters
    ----------
    callback : Callable[[CallbackData]]
        The function to call when triggered.
        Passes [pypalmsens.data.CallbackData][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('curve_new_data', callback=callback)

on_eis_data_begin

on_eis_data_begin(callback: Callable[[EISData], None]) -> EventHandle

Register a callback to invoke at the start of a new EIS data set.

Parameters:

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def on_eis_data_begin(self, callback: Callable[[EISData], None]) -> EventHandle:
    """Register a callback to invoke at the start of a new EIS data set.

    Parameters
    ----------
    callback : Callable[[EISData]]
        The function to call when triggered.
        Passes [pypalmsens.data.EISData][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('eis_data_begin', callback=callback)

on_eis_data_end

on_eis_data_end(callback: Callable[[], None]) -> EventHandle

Register a callback to invoke at the end of an EIS data set.

Parameters:

  • callback

    (Callable) –

    The function to call when triggered.

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def on_eis_data_end(self, callback: Callable[[], None]) -> EventHandle:
    """Register a callback to invoke at the end of an EIS data set.

    Parameters
    ----------
    callback : Callable
        The function to call when triggered.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('eis_data_end', callback=callback)

on_eis_new_data

on_eis_new_data(callback: Callable[[CallbackDataEIS], None]) -> EventHandle

Register a callback to invoke when new eis data are received.

Data points are batched depending on available resources.

Parameters:

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
def on_eis_new_data(self, callback: Callable[[CallbackDataEIS], None]) -> EventHandle:
    """Register a callback to invoke when new eis data are received.

    Data points are batched depending on available resources.

    Parameters
    ----------
    callback : Callable[[CallbackDataEIS]]
        The function to call when triggered.
        Passes [pypalmsens.data.CallbackDataEIS][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('eis_new_data', callback=callback)

on_error

on_error(callback: Callable[..., None]) -> EventHandle

Register a callback to invoke when an error occurs during a measurement.

These errors can be a connection or communication error.

Parameters:

  • callback

    (Callable) –

    The function to call when triggered.

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def on_error(self, callback: Callable[..., None]) -> EventHandle:
    """Register a callback to invoke when an error occurs during a measurement.

    These errors can be a connection or communication error.

    Parameters
    ----------
    callback : Callable
        The function to call when triggered.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('error', callback=callback)

on_measurement_begin

on_measurement_begin(callback: Callable[[Measurement], None]) -> EventHandle

Register a callback to invoke at the start of a measurement.

Parameters:

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def on_measurement_begin(self, callback: Callable[[Measurement], None]) -> EventHandle:
    """Register a callback to invoke at the start of a measurement.

    Parameters
    ----------
    callback : Callable
        The function to call when triggered.
        Passes [pypalmsens.data.Measurement][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('measurement_begin', callback=callback)

on_measurement_end

on_measurement_end(callback: Callable[[Measurement], None]) -> EventHandle

Register a callback to invoke at the end of a measurement.

Parameters:

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def on_measurement_end(self, callback: Callable[[Measurement], None]) -> EventHandle:
    """Register a callback to invoke at the end of a measurement.

    Parameters
    ----------
    callback : Callable
        The function to call when triggered.
        Passes [pypalmsens.data.Measurement][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('measurement_end', callback=callback)

on_measurement_setup

on_measurement_setup(callback: Callable[[], None]) -> EventHandle

Register a callback to invoke before the measurement starts.

Use this to set up file resources, database connections, etc.

Parameters:

  • callback

    (Callable) –

    The function to call when triggered.

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
def on_measurement_setup(self, callback: Callable[[], None]) -> EventHandle:
    """Register a callback to invoke before the measurement starts.

    Use this to set up file resources, database connections, etc.

    Parameters
    ----------
    callback : Callable
        The function to call when triggered.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('measurement_setup', callback=callback)

on_measurement_teardown

on_measurement_teardown(callback: Callable[[], None]) -> EventHandle

Register a callback to invoke after the measurement ends.

The measurement ends when it finnished successfully or after an error occurs. Use this to close files or clean up resources.

Parameters:

  • callback

    (Callable) –

    The function to call when triggered.

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
def on_measurement_teardown(self, callback: Callable[[], None]) -> EventHandle:
    """Register a callback to invoke after the measurement ends.

    The measurement ends when it finnished successfully or after an error occurs.
    Use this to close files or clean up resources.

    Parameters
    ----------
    callback : Callable
        The function to call when triggered.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('measurement_teardown', callback=callback)

on_receive_message

on_receive_message(callback: Callable[[str], None])

Register a callback for when a new message is received.

The callback will be invoked, for example, when a method is started, or when send_string is called in MethodSCRIPT.

Parameters:

  • callback

    (callable[[str]]) –

    The function to call when triggered. Passes str as argument to the callback.

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
def on_receive_message(self, callback: Callable[[str], None], /):
    """Register a callback for when a new message is received.

    The callback will be invoked, for example, when a method is started,
    or when `send_string` is called in MethodSCRIPT.

    Parameters
    ----------
    callback : callable[[str]]
        The function to call when triggered.
        Passes [str][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('receive_message', callback=callback)

on_receive_status

on_receive_status(callback: Callable[[Status], None])

Register a callback for idle status update events.

Requires active event loop (i.e. async only).

The callback will be invoked whenever the instrument sends updated current/potential values during idle state or pretreatment phases. The update frequency varies per device.

Parameters:

Returns:

  • EventHandle

    Handle that can be used to cancel the subscription.

Source code in src/pypalmsens/_instruments/events_mixin.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
def on_receive_status(self, callback: Callable[[Status], None], /):
    """Register a callback for idle status update events.

    Requires active event loop (i.e. async only).

    The callback will be invoked whenever the instrument sends
    updated current/potential values during idle state or pretreatment phases.
    The update frequency varies per device.

    Parameters
    ----------
    callback : callable[[Status]]
        The function to call when triggered.
        Passes [pypalmsens.data.Status][] as argument to the callback.

    Returns
    -------
    EventHandle
        Handle that can be used to cancel the subscription.
    """
    return self.on('receive_status', callback=callback)

query async

query(command: str, delay: float | None = None) -> str

Send a command using the communication protocol and return its response.

This is a method for direct communication with the instrument. It writes the command to the device, waits for completion, reads the full response, and returns it as a string.

For commands that run for a long time (e.g. scripts), this method will block until the script completes or times out.

See also pypalmsens.CommProtocolAsync.

Parameters:

  • command

    (str) –

    The command to send (e.g., 'i' to get the serial number). If command does not end with '\n', one is automatically added.

  • delay

    (float, default: None ) –

    Pause (in seconds) between read attempts. Defaults to self.delay.

Returns:

  • response ( str ) –

    The complete response from the device.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
async def query(self, command: str, delay: float | None = None) -> str:
    """Send a command using the communication protocol and return its response.

    This is a method for direct communication with the instrument.
    It writes the command to the device, waits for completion,
    reads the full response, and returns it as a string.

    For commands that run for a long time (e.g. scripts), this
    method will block until the script completes or times out.

    See also [pypalmsens.CommProtocolAsync][].

    Parameters
    ----------
    command : str
        The command to send (e.g., 'i' to get the serial number).
        If `command` does not end with `'\\n'`, one is automatically added.
    delay : float, optional
        Pause (in seconds) between read attempts. Defaults to `self.delay`.

    Returns
    -------
    response : str
        The complete response from the device.
    """
    if not isinstance(self._comm.ClientConnection, PalmSens.Comm.ClientConnectionMS):
        raise TypeError(
            'The Communication Protocol is only supported on MethodSCRIPT devices.'
        )

    emit_idle_messages: bool

    async with self._lock():
        # this temporarily turns off idle messages to reduce cross-talk
        if emit_idle_messages := await create_future(self._comm.GetStatusWhenIdleAsync()):
            await create_future(self._comm.SetStatusWhenIdleAsync(False))

        comm = CommProtocolAsync(self.instrument)

        try:
            response = await comm.query(command, delay=delay)
        finally:
            if emit_idle_messages:
                await create_future(self._comm.SetStatusWhenIdleAsync(emit_idle_messages))

    return response

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
230
231
232
233
234
235
236
237
238
239
240
241
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 single_to_double(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
271
272
273
274
275
276
277
278
279
280
281
282
283
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 single_to_double(potential)

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
206
207
208
209
210
211
212
213
214
215
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:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
257
258
259
260
261
262
263
264
265
266
267
268
269
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.types.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
587
588
589
590
591
592
593
594
595
596
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_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
285
286
287
288
289
290
291
292
293
294
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))

set_potential_range async

Set the potential range for the cell.

Parameters:

  • potential_range

    (AllowedPotentialRanges) –

    Set the potential range as a string. See pypalmsens.settings.AllowedPotentialRanges for options.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
307
308
309
310
311
312
313
314
315
316
317
318
319
async def set_potential_range(self, potential_range: AllowedPotentialRanges):
    """Set the potential range for the cell.

    Parameters
    ----------
    potential_range: AllowedPotentialRanges
        Set the potential range as a string.
        See `pypalmsens.settings.AllowedPotentialRanges` for options.
    """
    async with self._lock():
        await create_future(
            self._comm.SetPotentialRangeAsync(pr_string_to_enum(potential_range))
        )

status

status() -> Status

Get status.

Sets device 'StatusWhenIdle' flag on device, which tells it to periodically send an updated status message.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def status(self) -> Status:
    """Get status.

    Sets device 'StatusWhenIdle' flag on device, which tells it
    to periodically send an updated status message.
    """
    self.ensure_connection()

    if not self._comm.StatusWhenIdle:
        self._comm.StatusWhenIdle = True

        while not (status := self._comm.get_Status()):
            time.sleep(0.1)

    else:
        status = self._comm.get_Status()

    return Status(
        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/capabilities_mixin.py
63
64
65
66
67
68
69
70
71
72
def supported_applied_current_ranges(self: HasCommProtocol) -> list[AllowedCurrentRanges]:
    """List applied current ranges supported by this device.

    Returns
    -------
    current_ranges: list[AllowedCurrentRanges]
        List of supported current ranges.
    """
    self.ensure_connection()
    return CapabilitiesInterface(comm=self._comm).supported_applied_current_ranges

supported_bipot_current_ranges

supported_bipot_current_ranges() -> list[AllowedCurrentRanges]

List bipot current ranges supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/capabilities_mixin.py
74
75
76
77
78
79
80
81
82
83
def supported_bipot_current_ranges(self: HasCommProtocol) -> list[AllowedCurrentRanges]:
    """List bipot current ranges supported by this device.

    Returns
    -------
    current_ranges: list[AllowedCurrentRanges]
        List of supported current ranges.
    """
    self.ensure_connection()
    return CapabilitiesInterface(comm=self._comm).supported_bipot_current_ranges

supported_current_ranges

supported_current_ranges() -> list[AllowedCurrentRanges]

List current ranges supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/capabilities_mixin.py
52
53
54
55
56
57
58
59
60
61
def supported_current_ranges(self: HasCommProtocol) -> list[AllowedCurrentRanges]:
    """List current ranges supported by this device.

    Returns
    -------
    current_ranges: list[AllowedCurrentRanges]
        List of supported current ranges.
    """
    self.ensure_connection()
    return CapabilitiesInterface(comm=self._comm).supported_current_ranges

supported_methods

supported_methods() -> list[AllowedMethods]

List methods supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/capabilities_mixin.py
41
42
43
44
45
46
47
48
49
50
def supported_methods(self: HasCommProtocol) -> list[AllowedMethods]:
    """List methods supported by this device.

    Returns
    -------
    methods: list[AllowedMethods]
        List of supported methods.
    """
    self.ensure_connection()
    return CapabilitiesInterface(comm=self._comm).supported_methods

supported_potential_ranges

supported_potential_ranges() -> list[AllowedPotentialRanges]

List applied potential ranges supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/capabilities_mixin.py
85
86
87
88
89
90
91
92
93
94
def supported_potential_ranges(self: HasCommProtocol) -> list[AllowedPotentialRanges]:
    """List applied potential ranges supported by this device.

    Returns
    -------
    potential_ranges: list[AllowedPotentialRanges]
        List of supported potential ranges.
    """
    self.ensure_connection()
    return CapabilitiesInterface(comm=self._comm).supported_potential_ranges

validate_method

validate_method(method: MethodTypeCompatible)

Validate method.

Raise ValueError if the method cannot be validated.

Parameters:

Source code in src/pypalmsens/_instruments/capabilities_mixin.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def validate_method(
    self: HasCommProtocol,
    method: MethodTypeCompatible,
):
    """Validate method.

    Raise ValueError if the method cannot be validated.

    Parameters
    -----------
    method: MethodType
        The method to validate.
    """
    self.ensure_connection()

    capabilities = self._comm.Capabilities

    psmethod = method._to_psmethod()
    errors = psmethod.Validate(capabilities)

    if any(error.IsFatal for error in errors):
        message = '\n'.join([error.Message for error in errors])
        raise MethodIncompatibleError(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
426
427
428
429
430
431
432
433
434
435
436
437
438
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

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.

  • status

    Return status for all managers in pool.

  • submit

    Concurrently start measurement on all managers in the pool.

Attributes:

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
32
33
34
35
36
37
38
39
40
41
42
43
def __init__(
    self,
    instruments_or_managers: Sequence[Instrument | InstrumentManagerAsync],
):
    self.managers: list[InstrumentManagerAsync] = []
    """List of instruments managers in the pool."""

    for item in instruments_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
114
115
116
117
118
119
120
121
122
123
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(attempts: int = 1) -> None

Connect all instrument managers in the pool.

Parameters:

  • attempts

    (int, default: 1 ) –

    Number of attempts to establish connection. Use this if you experience connection issues via USB.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
async def connect(self, attempts: int = 1) -> None:
    """Connect all instrument managers in the pool.

    Parameters
    ----------
    attempts: int, optional
        Number of attempts to establish connection.
        Use this if you experience connection issues via USB.
    """
    tasks = [manager.connect() for manager in self.managers]

    try:
        _ = await asyncio.gather(*tasks)
    except Exception:
        if attempts <= 1:
            raise
        await asyncio.sleep(0.5)
    else:
        return

    await self.connect(attempts=attempts - 1)

disconnect async

disconnect() -> None

Disconnect all instrument managers in the pool.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
90
91
92
93
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
95
96
97
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
 99
100
101
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: MethodType, callback: Sequence[Callback | CallbackEIS] | Callback | CallbackEIS | None = None, **kwargs) -> list[Measurement]

Concurrently start measurement on all managers in the pool.

For hardware synchronization, set .general.use_hardware_sync on the method. For MethodSCRIPT, use 'set_channel_sync 1'.

In addition, the pool must contain: - channels from a single multichannel instrument only - the first channel of the multichannel 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

    (MethodType) –

    Method parameters for measurement.

  • callback

    (list[Callback] | Callback | CallbackEIS | None, default: None ) –

    If specified, call these functions/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.

    Specify a sequence of callbacks to set a different function for every channel. The number of callbacks must match the number of channels.

    Specify a single callback to set the same function to all channels.

  • **kwargs

    These keyword parameters are passed to the measure function.

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
135
136
137
138
139
140
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
async def measure(
    self,
    method: MethodType,
    callback: Sequence[Callback | CallbackEIS] | Callback | CallbackEIS | None = None,
    **kwargs,
) -> list[Measurement]:
    """Concurrently start measurement on all managers in the pool.

    For hardware synchronization, set `.general.use_hardware_sync` on the method.
    For MethodSCRIPT, use 'set_channel_sync 1'.

    In addition, the pool must contain:
    - channels from a single multichannel instrument only
    - the first channel of the multichannel 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 : MethodType
        Method parameters for measurement.
    callback : list[Callback] | Callback | CallbackEIS | None
        If specified, call these functions/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.

        Specify a sequence of callbacks to set a different function for every channel.
        The number of callbacks must match the number of channels.

        Specify a single callback to set the same function to all channels.
    **kwargs
        These keyword parameters are passed to the measure function.
    """
    tasks: list[Awaitable[Measurement]] = []

    callbacks: Sequence[Callback | CallbackEIS | None]

    if isinstance(callback, Sequence):
        if len(callback) != len(self.managers):
            raise IndexError('Number of callbacks does not match number of channels.')
        callbacks = callback
    else:
        callbacks = [callback or None for _ in self.managers]

    if method._use_hardware_sync:
        return await self._measure_hw_sync(method, callbacks=callbacks)

    for manager, _callback in zip(self.managers, callbacks):
        tasks.append(manager.measure(method, callback=_callback, **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
103
104
105
106
107
108
109
110
111
112
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()

status

status() -> list[Status]

Return status for all managers in pool.

Returns:

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
125
126
127
128
129
130
131
132
133
def status(self) -> list[Status]:
    """Return status for all managers in pool.

    Returns
    -------
    list[Status]
        List of status objects.
    """
    return [manager.status() for manager in self]

submit async

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

Concurrently start measurement on all managers in the pool.

This method does not support hardware sync.

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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
async def submit(self, func: SubmitCallable, **kwargs: Any) -> list[Any]:
    """Concurrently start measurement on all managers in the pool.

    This method does not support hardware sync.

    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