Examples
The following examples are also available in the examples directory.
Load and save data
This example shows how to load/save methods and measurements and how to inspect the data.
from pathlib import Path
import pandas as pd
import pypalmsens as ps
examples_dir = Path(__file__).parent
# load a method file
method = ps.load_method_file(examples_dir / 'PSDummyCell_LSV.psmethod', as_method=True)
# save the method file
ps.save_method_file(examples_dir / 'PSDummyCell_LSV_copy.psmethod', method)
# load a session file
measurements = ps.load_session_file(examples_dir / 'Demo CV DPV EIS IS-C electrode.pssession')
for measurement in measurements:
print(f'loaded measurement: {measurement.title}, {measurement.timestamp}')
print(f'number of curves: {len(measurement.curves)}')
for curve in measurement.curves:
print(f'curve title: {curve.title}')
print(f'number of points: {len(curve.x_array)}')
print(f'number of peaks: {len(curve.peaks)}')
print(f'Has EIS fit results: {"yes" if len(measurement.eis_fit) > 0 else "no"}')
# save the session file
ps.save_session_file(
examples_dir / 'Demo CV DPV EIS IS-C electrode_copy.pssession', [measurements[0]]
)
# convert measurments to pandas dataframes
frames = []
frame_names = []
for measurement in measurements:
dataset = measurement.dataset
frames.append(dataset.to_dataframe())
frame_names.append(measurement.title)
df = pd.concat(frames, keys=frame_names)
print(df)
Manual control
This example shows how to discover devices, establish a connection and control an instrument manually.
import pypalmsens as ps
instruments = ps.discover()
print(instruments)
with ps.connect(instruments[0]) as manager:
manager.set_cell(True)
print('cell enabled')
manager.set_potential(1)
print('set potential to 1V')
manager.set_current_range('1mA')
print('set cell to to 1mA currrent range')
current = manager.read_current()
print(f'current = {current} µA')
manager.set_cell(False)
print('cell disabled')
Manual control async
This example shows how to discover devices, establish a connection and control an instrument manually using the asynchronous instrument manager.
import asyncio
import pypalmsens as ps
async def main():
instruments = await ps.discover_async()
print(instruments)
async with await ps.connect_async(instruments[0]) as manager:
await manager.set_cell(True)
print('cell enabled')
await manager.set_potential(1)
print('set potential to 1V')
await manager.set_current_range('1mA')
print('set cell to to 1mA currrent range')
current = await manager.read_current()
print(f'current = {current} µA')
await manager.set_cell(False)
print('cell disabled')
asyncio.run(main())
Measure CA
This example shows how to set up and run a chronoamperometry measurement.
import pypalmsens as ps
def new_data_callback(data):
print(data.last_datapoint())
instruments = ps.discover()
print(instruments)
with ps.connect(instruments[0]) as manager:
serial = manager.get_instrument_serial()
print(serial)
# Chronoamperometry measurement using helper class
method = ps.ChronoAmperometry(
interval_time=0.01,
potential=1.0,
run_time=10.0,
)
measurement = manager.measure(method, callback=new_data_callback)
print(measurement)
Measure CA async
This example shows how to set up and run a chronoamperometry measurement using the asynchronous instrument manager.
import asyncio
import pypalmsens as ps
def new_data_callback(data):
print(data.last_datapoint())
async def main():
instruments = await ps.discover_async()
print(instruments)
async with await ps.connect_async(instruments[0]) as manager:
serial = await manager.get_instrument_serial()
print(serial)
method = ps.ChronoAmperometry(
interval_time=0.02,
potential=1.0,
run_time=2.0,
)
measurement = await manager.measure(method, callback=new_data_callback)
print(measurement)
asyncio.run(main())
Measure CV
This example shows how to set up and run a cyclic voltammetry measurement.
import pypalmsens as ps
def new_data_callback(data):
print(data.last_datapoint())
instruments = ps.discover()
print(instruments)
with ps.connect(instruments[0]) as manager:
serial = manager.get_instrument_serial()
print(serial)
method = ps.CyclicVoltammetry(
current_range={
'max': '1A', # 1 A range
'min': '1uA', # 1 µA range
'start': '1mA', # 1 mA range
},
equilibration_time=2, # seconds
begin_potential=-2, # V
vertex1_potential=-2, # V
vertex2_potential=3, # V
step_potential=0.05, # V
scanrate=5, # V/s
n_scans=3, # number of scans
)
measurement = manager.measure(method, callback=new_data_callback)
print(measurement)
Measure EIS
This example shows how to set up and run a EIS measurement.
import pypalmsens as ps
def new_data_callback(data):
print(data.last_datapoint())
instruments = ps.discover()
print(instruments)
with ps.connect(instruments[0]) as manager:
serial = manager.get_instrument_serial()
print(serial)
method = ps.ElectrochemicalImpedanceSpectroscopy()
measurement = manager.measure(method, callback=new_data_callback)
print(measurement)
Mixed Mode
This example shows how to set up a Mixed Mode measurement for a typicial charge / discharge cycle of a common Lithium battery. Note that the example has a reduced number of cycles and maximum run time.
import pypalmsens as ps
# example with a single cycle with reduced charge/discharge timeouts
N_CYCLES = 1
OCP_TIME = 5
TIMEOUT = 10
# # For a complete 1000-cycle run, you could use he following parameters
# # Note that this could take a while :-)
# N_CYCLES = 1000
# OCP_TIME = 300
# TIMEOUT = 10000
def new_data_callback(data):
print(data.last_datapoint())
instruments = ps.discover()
print(instruments)
method = ps.mixed_mode.MixedMode(
current_range={
'min': '1mA', # 1 mA range
'max': '100mA', # 100 mA range
'start': '100mA', # 100 mA range
},
interval_time=1.0,
cycles=N_CYCLES,
stages=[
{
'stage_type': 'OpenCircuit',
'run_time': OCP_TIME, # s
},
{
'stage_type': 'ConstantI',
'run_time': TIMEOUT, # s
'current': 3.0,
'applied_current_range': '100mA',
'potential_limits': {'max': 4.2},
},
{
'stage_type': 'ConstantE',
'run_time': TIMEOUT, # s
'potential': 4.2,
'current_limits': {'min': 50000}, # mA
},
{
'stage_type': 'OpenCircuit',
'run_time': OCP_TIME, # s
},
{
'stage_type': 'ConstantI',
'run_time': TIMEOUT, # s
'current': -3.0,
'applied_current_range': '100mA',
'potential_limits': {'min': 2.5},
},
],
)
with ps.connect(instruments[0]) as manager:
serial = manager.get_instrument_serial()
print(serial)
measurement = manager.measure(method, callback=new_data_callback)
print(measurement)
MethodSCRIPT sandbox
This example shows how to set up and run a MethodSCRIPT Sandbox measurement.
import pypalmsens as ps
def new_data_callback(data):
print(data.last_datapoint())
script = """e
var c
var p
var e
var l
var r
var j
var o
var d
var n
set_gpio_cfg 0x0f 1
set_pgstat_chan 0
set_pgstat_mode 3
set_acquisition_frac_autoadjust 50
set_max_bandwidth 0
cell_off
set_range ba 210m
set_autoranging ba 210m 210m
set_range ab 4200m
set_autoranging ab 210m 4200m
meas_loop_ocp o 200m 1
pck_start
pck_add o
pck_end
endloop
set_range ba 2100u
set_autoranging ba 2100n 2100u
set_range ab 4200m
set_autoranging ab 4200m 4200m
store_var d -200m ab
add_var d o
store_var n 200m ab
add_var n o
set_e d
cell_on
set_gpio 10i
meas_loop_acv p c e l r j d n 10m 200m 10m 100
pck_start
pck_add p
pck_add c
pck_add e
pck_add l
pck_add r
pck_add j
pck_end
endloop
on_finished:
cell_off
"""
instruments = ps.discover()
print(instruments)
with ps.connect(instruments[0]) as manager:
serial = manager.get_instrument_serial()
print(serial)
method = ps.MethodScript(script=script)
measurement = manager.measure(method, callback=new_data_callback)
print(measurement)
Status callback
This example shows how to set up a callback to read out the idle status updates (current/potential) and store the pretreatment data.
import asyncio
import pypalmsens as ps
from pypalmsens.data import Status
pretreatment_data = []
def idle_status_callback(point: Status):
if point.device_state == 'Pretreatment':
pretreatment_data.append(
{
'phase': point.pretreatment_phase,
'current': point.current,
'potential': point.potential,
}
)
print(f'{point.device_state}: {point}')
def new_data_callback(data):
print(data.last_datapoint())
async def main():
instruments = await ps.discover_async()
print(instruments)
async with await ps.connect_async(instruments[0]) as manager:
manager.register_status_callback(idle_status_callback)
# While sleeping, the callback reports the
# idle current/potential every second
await asyncio.sleep(5)
# The status callback repurts the
# current/potential during pretreatment phases
method = ps.ChronoAmperometry(
pretreatment=ps.settings.Pretreatment(
conditioning_time=2,
deposition_time=2,
),
interval_time=0.02,
potential=1.0,
run_time=2.0,
)
measurement = await manager.measure(method, callback=new_data_callback)
await asyncio.sleep(5)
manager.unregister_status_callback()
print(measurement)
asyncio.run(main())
print(pretreatment_data)
Stream data to CSV
This example shows how to set up and run a chronoamperometry measurement and write the results to a CSV file in real-time.
import csv
import pypalmsens as ps
def stream_to_csv_callback(data):
for point in data.new_datapoints():
csv_writer.writerow([point['index'], point['x'], point['y']])
## for EIS
# csv_writer.writerow([point['Frequency'], point['ZRe'], point['ZIm']])
csv_file = open('test.csv', 'w', newline='')
csv_writer = csv.writer(csv_file)
instruments = ps.discover()
print(instruments)
with ps.connect(instruments[0]) as manager:
serial = manager.get_instrument_serial()
print(serial)
# Chronoamperometry measurement using helper class
method = ps.ChronoAmperometry(
interval_time=0.004,
potential=1.0,
run_time=10.0,
)
measurement = manager.measure(method, callback=stream_to_csv_callback)
print(measurement)
csv_file.close()
SWV versus OCP
This example shows how to set up and run a square wave voltammetry measurement versus OCP.
import pypalmsens as ps
def new_data_callback(data):
print(data.last_datapoint())
instruments = ps.discover()
print(instruments)
with ps.connect(instruments[0]) as manager:
method = ps.SquareWaveVoltammetry(
pretreatment={
'conditioning_potential': 2.0, # V
'conditioning_time': 2, # seconds
},
versus_ocp={
'mode': 3, # versus begin and end potential
'max_ocp_time': 1, # seconds
},
begin_potential=-0.5, # V
end_potential=0.5, # V
step_potential=0.01, # V
amplitude=0.08, # V
frequency=50, # Hz
)
measurement = manager.measure(method, callback=new_data_callback)
print(measurement)
print(f'ocp: {measurement.ocp_value}')
Multiplexer
This example shows how to set up and control a multiplexer and run consecutive and alternating multiplexer measurments.
import pypalmsens as ps
def new_data_callback(data):
print(data.last_datapoint())
instruments = ps.discover()
print(instruments)
with ps.connect(instruments[0]) as manager:
n_multiplexer_channels = manager.initialize_multiplexer(2)
manager.set_mux8r2_settings()
for channel in range(n_multiplexer_channels):
manager.set_multiplexer_channel(channel)
# When measuring alternatingly the selection is restricted to the first n channels
altnernating_multiplexer_method = ps.ChronoAmperometry(
interval_time=0.5, # seconds
potential=1.0, # volts
run_time=5.0, # seconds
multiplexer={
'mode': 'alternate', # 'none', 'consecutive', 'alternate'
'channels': [1, 2], # 8 channels, 1 and 2 are enabled
'connect_sense_to_working_electrode': False,
'combine_reference_and_counter_electrodes': False,
'use_channel_1_reference_and_counter_electrodes': False,
'set_unselected_channel_working_electrode': 0,
},
)
measurement = manager.measure(altnernating_multiplexer_method, callback=new_data_callback)
print(measurement)
consecutive_multiplexer_method = ps.SquareWaveVoltammetry(
begin_potential=-0.5, # volts
end_potential=0.5, # volts
step_potential=0.01, # volts
amplitude=0.1, # volts
frequency=10, # hertz
multiplexer={
'mode': 'consecutive', # 'none', 'consecutive', 'alternate'
'channels': [1, 2, 7, 8], # channels 1, 2, 7 and 8 are enabled
'connect_sense_to_working_electrode': False,
'combine_reference_and_counter_electrodes': False,
'use_channel_1_reference_and_counter_electrodes': False,
'set_unselected_channel_working_electrode': 0,
},
)
measurement = manager.measure(consecutive_multiplexer_method, callback=new_data_callback)
print(measurement)
Multichannel measurement
This example shows how to connect to a collection of instruments and run a chronoamperometry measurement on all channels simultaneously.
import pypalmsens as ps
def new_data_callback(data):
print(data.last_datapoint())
method = ps.ChronoAmperometry(
interval_time=0.004,
potential=1.0,
run_time=5.0,
)
instruments = ps.discover()
print(instruments)
# run multichannel experiment with callback
with ps.InstrumentPool(instruments) as pool:
results = pool.measure(method=method, callback=new_data_callback)
print(results)
Multichannel CSV writer
This example shows how to connect to a how to use a callback to automatically store data to a csv file while collecting data from collection of instruments.
import asyncio
import pypalmsens as ps
import csv
import functools
def stream_to_csv_callback(data, csv_writer):
for point in data.new_datapoints():
csv_writer.writerow([point['index'], point['x'], point['y']])
async def stream_to_csv(manager, *, method):
"""Measure with a custom csv writer callback."""
serial = await manager.get_instrument_serial()
with open(f'{serial}.csv', 'w', newline='') as csv_file:
csv_writer = csv.writer(csv_file)
callback = functools.partial(stream_to_csv_callback, csv_writer=csv_writer)
measurement = await manager.measure(method, callback=callback)
print(f'Wrote data to {csv_file.name}')
return measurement
async def main():
method = ps.ChronoAmperometry(
interval_time=0.004,
potential=1.0,
run_time=5.0,
)
instruments = await ps.discover_async()
print(instruments)
# run multichannel experiment with csv writer
async with ps.InstrumentPoolAsync(instruments) as pool:
results = await pool.submit(stream_to_csv, method=method)
print(results)
asyncio.run(main())
Multichannel custom loop
This example shows how to run and set up a sequence of measurements on a collection of channels simultaneously.
import asyncio
import pypalmsens as ps
async def custom_loop(manager, *, method, steps):
measurements = []
for step in steps:
method = method.model_copy(step)
measurements.append(await manager.measure(method))
return measurements
async def main():
method = ps.ChronoAmperometry(
interval_time=0.004,
run_time=5.0,
)
steps = [
{'potential': 0.4},
{'potential': 0.6},
{'potential': 1.0},
]
instruments = await ps.discover_async()
print(instruments)
async with ps.InstrumentPoolAsync(instruments) as pool:
results = await pool.submit(custom_loop, method=method, steps=steps)
print(results)
for i, measurements in enumerate(results):
ps.save_session_file(f'example-{i}.pssession', measurements)
asyncio.run(main())
Multichannel HW sync
On multi-channel devices that support it, hardware sync can be used to synchronize measurements between multiple channels. When synchronization is enabled the follower device will wait until the main channel enables synchronisation. After that, the follower and main will synchronize their measurement loop start and iterations.
This example shows how to connect to a collection of instruments and run a chronopotentiometry measurement on all channels simultaneously using hardware synchronization.
For hardware synchronization, you set use_hardware_sync = True on the method.
This is the equivalent of set_channel_sync 1 in MethodSCRIPT.
At the moment this only works with async.
In addition, your pool of instruments must contain:
- channels from a single multi-channel instrument
- the first (main) channel of the instrument
- at least one follower channel
All instruments are prepared and put in a waiting state. The measurements are started via a hardware sync trigger on channel 1. Only channel 1 (the main channel) has hardware required to trigger the other channels.
import asyncio
import pypalmsens as ps
async def main():
method = ps.ChronoAmperometry(
interval_time=0.004,
potential=1.0,
run_time=5.0,
)
method.general.use_hardware_sync = True
instruments = await ps.discover_async()
print(instruments)
async with ps.InstrumentPoolAsync(instruments) as pool:
results = await pool.measure(method)
print(results)
asyncio.run(main())