Connecting and Measuring

The following chapter details how to connect to a device, read data from the device, manually controlling the potential, run measurements on the device and finally how to properly close a connection to a device.

The pypalmsens top-level module contains all the relevant functions and classes for discovering and controlling instruments. The InstrumentManager() and InstrumentManagerAsync() class are wrappers around our .NET libraries which make it possible to connect to and control PalmSens instruments from Python.

Mains Frequency

To eliminate noise induced by other electrical appliances it is highly recommended to set your regional mains frequency (50/60 Hz) in the general settings when performing a measurement ps.settings.General.power_frequency.

Connecting to a device

The recommended way to connect to a device for most workflows is to use the ps.connect() context manager. The contextmanager manages the connection, and closes the connection to the device if it is no longer needed. It returns an instance of InstrumentManager(), which can be used to control the instrument and start a measurement:

>>> import pypalmsens as ps

>>> with ps.connect() as manager:
...     measurement = manager.measure(method)

By default, connect() connects to the first instrument it discovers. With only one device connected, this is fine. With more than one instrument connected, you can use ps.discover() to find all devices and manage them yourself. For example, this is how to get a list of all available devices, and how to connect to the first one.

>>> available_instruments = ps.discover()
>>> available_instruments
[Instrument(name='EmStat4 HR [1]', interface='usbcdc')]

>>> first_instrument = available_instruments[0]

>>> with ps.connect(first_instrument) as manager:
...    measurement = manager.measure(method)

Finally, you can set up the InstrumentManager() yourself.

>>> available_instruments = ps.discover()
>>> manager = ps.InstrumentManager()
>>> manager.connect(available_instruments[0])

The InstrumentManager.disconnect() function disconnects from the device freeing it up for other things to connect to it.

>>> manager.disconnect()

Currently PyPalmSens supports discovering instruments connected via FTDI, serial (usbcdc/com), and Bluetooth (classic/low energy). By default scanning with Bluetooth is disabled.

You can enable scanning with Bluetooth by setting:

>>> ps.discover(bluetooth=True)

Manually controlling the device

Depending on your device’s capabilities it can be used to set a potential/current and to switch current ranges. The potential can be set manually in potentiostatic mode and the current can be set in galvanostatic mode. The following example show how to manually set a potential, for more examples refer to the ManualControlExample and ManualControlExampleAsync scripts included with the SDK.

>>> manager.set_potential(1)

Measuring

Starting a measurement is done by sending method parameters to a PalmSens/Nexus/EmStat/Sensit device. The InstrumentManager.measure() method returns a Measurement objcet and also supports keeping a reference to the underlying .NET object. For more information please refer to PalmSens.Core.dll.

The following example runs a chronoamperometry measurement on an instrument.

>>> method = ps.ChronoAmperometry(
...     interval_time=0.01,
...     e=1.0,
...     run_time=10.0
... )
>>> measurement = manager.measure(method)

Callback

It is possible to process measurement results in real-time by specifying a callback on the InstrumentManager/InstrumentManagerAsync either by providing it as an override when it is created using the new_data_callback argument:

>>> def new_data_callback(new_data):
...     for point in new_data:
...         print(point)
...
>>> manager = instruments.InstrumentManager(
...     callback=new_data_callback
... )

or by setting it on the InstrumentManager’s callback field.

>>> with ps.connect() as manager:
...     manager.callback = stream_to_csv_callback(csv_writer)

The callback is passed a collection of points that have been added since the last time it was called. Points contain a dictionary with the following information:

Non-impedimetric techniques

Techniques such as linear sweep voltammetry or chronopotentiometry return a dictionary containing the following values:

  • index: the index of the point

  • x, x_unit and x_type; depending on the technique this will be:

    • Time in seconds for amperometry and potentiometry techniques that do not specify a begin and an end potential

    • Potential in volts for voltammetry techniques such as linear sweep, cyclic and square-wave voltammetry

    • Current in micro amperes for linear sweep potentiometry

  • y, y_unit and y_type; depending on the techniques this will be:

    • Current in micro amperes for all potentiometric techniques such as linear sweep and cyclic voltammetry and chronoamperometry and multistep amperometry

    • Potential in volts for all galvanostatic techniques such as chronopotentiometry and linear sweep potentiometry

Impedimetric techniques

The exception are (galvanostatic/) electrochemical impedance spectroscopy. These techniques return the following:

  • frequency: the applied frequency of the sample in hertz

  • z_re: the real impedance in ohms

  • z_im: the imaginary impedance in ohms

MethodSCRIPT™

The MethodSCRIPT™ scripting language is designed to integrate our OEM potentiostat (modules) effortlessly in your hardware setup or product.

MethodSCRIPT™ allows developers to program a human-readable script directly into the potentiostat module by means of a serial (TTL) connection. The simple script language allows for running all supported electrochemical techniques and makes it easy to combine different measurements and other tasks.

More script features include:

  • Use of variables

  • (Nested) loops

  • Logging results to an SD card

  • Digital I/O for example for waiting for an external trigger

  • Reading auxiliary values like pH or temperature

  • Going to sleep or hibernate mode

See the MethodSCRIPT™ documentation for more information.

Sandbox Measurements

PSTrace includes an option to make use MethodSCRIPT™ Sandbox to write and run scripts. This is a great place to test MethodSCRIPT™ measurements to see what the result would be. That script can then be used in the MethodScriptSandbox technique in the SDK as demonstrated below.

Graphical editor for MethodSCRIPT™

Multichannel measurements

PyPalmSens supports multichannel experiments via InstrumentPool and InstrumentPoolAsync.

This class manages a pool of instruments (InstrumentManagerAsync), so that one method can be executed on all instruments at the same time.

A basic multichannel measurement can be set up by passing a list of instruments, either from a multichannel device, or otherwise connected:

>>> instruments = ps.discover()
>>> instruments
[Instrument(name='EmStat4 HR [1]', interface='usbcdc'), Instrument(name='EmStat4 HR [1]', interface='usbcdc')]

>>> method = ps.CyclicVoltammetry()

>>> with ps.InstrumentPool(instruments) as pool:  (1)
...    measurements = pool.measure(method)

>>> measurements
[Measurment(...), Measurement(...)]
1 InstrumentPool is a context manager, so all instruments are disconnected after use.

The above example uses blocking calls for the instrument pool. While this works well for many straightforward use-cases, the backend for multichannel measurements is asynchronous by necessity. The rest of the documentation here focuses on the async version of the instrument pool, InstrumentPoolAsync. This is more powerful and more flexible for more demanding use cases. Note that most of the functionality and method names are shared between InstrumentPool and InstrumentPoolAsync.

>>> instruments = await ps.discover_async()

>>> method = ps.CyclicVoltammetry()

>>> async with ps.InstrumentPoolAsync(instruments) as pool:
...    results = await pool.measure(method)

>>> measurements
[Measurment(...), Measurement(...)]

The pool takes a Callback, just like a regular InstrumentManager.

>>> async with ps.InstrumentPoolAsync(
...     instruments, callback=new_data_dallback
... ) as pool:
...    results = await pool.measure(method)

You can add (pool.add()) and remove (pool.remove()) managers frem the pool:

>>> serial_numbers = ['ES4HR20B0008', ...]

>>> async with ps.InstrumentPoolAsync(instruments) as pool:
...     for manager in pool:
...        if await manager.get_instrument_serial() not in [serial_numbers]:
...             await pool.remove(manager)

You can also manage the pool yourself by passing the InstrumentManagers directly:

>>> instruments = await ps.discover_async()

>>> managers = [
...     ps.InstrumentManagerAsync(instrument) for instrument in instruments
... ]

>>> async with ps.InstrumentPoolAsync(managers) as pool:
    ...

To define your own measurement functions, you can use the submit() method. Pass a function that must take InstrumentManagerAsync as the first argument. Any other keyword arguments will be passed on.

For example to run two methods in sequence:

>>> async def my_custom_function(manager, *, method1, method2):
...     measurement1 = await manager.measure(method1)
...     measurement2 = await manager.measure(method2)
...     return measurement1, measurement2

>>> async with ps.InstrumentPoolAsync(instruments) as pool:
...     results = await pool.submit(my_task, method=method)

See examples.adoc#multichannel_csv_writer and examples.adoc#multichannel_custom_loop for a practical example of setting a custom function.

To use hardware synchronization, use the same measure method. See also examples.adoc#multichannel_hw_sync. Make sure the method has the general.use_hardware_sync flag set.

In addition, the pool must contain: - channels from a single multi-channel instrument only - the first channel of the multi-channel instrument - at least two channels

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

>>> method.general.use_hardware_sync = True

>>> async with ps.InstrumentPoolAsync(instruments) as pool:
...      results = await pool.measure_hw_sync(method)