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

Classes:

Functions:

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
200
201
202
203
204
205
206
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.

supported_methods

supported_methods() -> list[AllowedMethods]

List methods supported by this device.

Returns:

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def supported_methods(self: HasCommProtocol) -> list[AllowedMethods]:
    """List methods supported by this device.

    Returns
    -------
    methods: list[AllowedMethods]
        List of supported methods.
    """
    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_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
138
139
140
141
142
143
144
145
146
147
148
149
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()
    capabilities = self._comm.Capabilities

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

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
151
152
153
154
155
156
157
158
159
160
161
162
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()
    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
164
165
166
167
168
169
170
171
172
173
174
175
def supported_bipot_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()
    capabilities = self._comm.Capabilities

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

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
177
178
179
180
181
182
183
184
185
186
187
188
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()
    capabilities = self._comm.Capabilities

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

is_measuring

is_measuring() -> bool

Return True if device is measuring.

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

is_connected

is_connected() -> bool

Return True if an instrument connection exists.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
242
243
244
245
246
247
248
249
def is_connected(self) -> bool:
    """Return True if an instrument connection exists."""
    try:
        self._comm
    except AttributeError:
        return False
    else:
        return True

ensure_connection

ensure_connection()

Raises connection error if the instrument is not connected.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
251
252
253
254
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')

connect async

connect() -> None

Connect to instrument.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
256
257
258
259
260
261
262
263
async def connect(self) -> None:
    """Connect to instrument."""
    if self.is_connected():
        return

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

    firmware_warning(self._comm.Capabilities)

status

status() -> Status

Get status.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
265
266
267
268
269
270
def status(self) -> Status:
    """Get status."""
    return Status(
        self._comm.get_Status(),
        device_state=str(self._comm.get_State()),  # type:ignore
    )

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
272
273
274
275
276
277
278
279
280
281
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))

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
283
284
285
286
287
288
289
290
291
292
293
294
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

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
296
297
298
299
300
301
302
303
304
305
306
307
308
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)

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
310
311
312
313
314
315
316
317
318
319
320
321
322
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))
        )

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
324
325
326
327
328
329
330
331
332
333
334
335
336
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

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
338
339
340
341
342
343
344
345
346
347
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))

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
349
350
351
352
353
354
355
356
357
358
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)

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
360
361
362
363
364
365
366
367
368
369
370
371
372
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))
        )

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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
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()

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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
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

unregister_status_callback

unregister_status_callback()

Unregister callback from idle status events.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
407
408
409
410
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
421
422
423
424
425
426
427
428
429
430
431
432
433
434
def validate_method(self, method: PalmSens.Method | BaseTechnique) -> None:
    """Validate method.

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

    if not isinstance(method, PalmSens.Method):
        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 MethodIncompatibleError(f'Method not compatible:\n{message}')

measure async

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

Start measurement using given method parameters.

Parameters:

  • method
    (BaseTechnique) –

    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.

  • 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
436
437
438
439
440
441
442
443
444
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
async def measure(
    self,
    method: BaseTechnique,
    *,
    callback: Callback | CallbackEIS | 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
    )

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
519
520
521
522
523
524
525
526
527
528
529
530
531
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)

abort async

abort() -> None

Abort measurement.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
533
534
535
536
async def abort(self) -> None:
    """Abort measurement."""
    async with self._lock():
        await create_future(self._comm.AbortAsync())

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
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
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 = PalmSens.MuxModel(mux_model)

        if 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 = 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

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
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
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 != 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_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 = PalmSens.Method.MuxSettings.UnselWESetting(
        set_unselected_channel_working_electrode
    )

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

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
619
620
621
622
623
624
625
626
627
628
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))

disconnect async

disconnect()

Disconnect from the instrument.

Source code in src/pypalmsens/_instruments/instrument_manager_async.py
630
631
632
633
634
635
636
637
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

InstrumentPoolAsync

Manages a set of instrument.

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

Parameters:

Methods:

  • 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.

  • remove

    Close and remove manager from pool.

  • add

    Open and add manager to the pool.

  • measure

    Concurrently start measurement on all managers in the pool.

  • submit

    Concurrently start measurement on all managers in the pool.

Attributes:

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
31
32
33
34
35
36
37
38
39
40
41
42
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.

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
83
84
85
86
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
88
89
90
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
92
93
94
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)

remove async

Close and remove manager from pool.

Parameters:

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
 96
 97
 98
 99
100
101
102
103
104
105
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()

add async

Open and add manager to the pool.

Parameters:

Source code in src/pypalmsens/_instruments/instrument_pool_async.py
107
108
109
110
111
112
113
114
115
116
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)

measure async

measure(method: BaseTechnique, 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
    (MethodSettings) –

    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
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
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
async def measure(
    self,
    method: BaseTechnique,
    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 : MethodSettings
        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

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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
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

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
39
40
41
42
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
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

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

measure_async async

measure_async(method: BaseTechnique, 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
 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
async def measure_async(
    method: BaseTechnique,
    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