Commit 8655bdb0 authored by Jose Tiago Coutinho Macara's avatar Jose Tiago Coutinho Macara
Browse files

First implementation of biologic potentiostat

parent ad2e6a56
......@@ -186,3 +186,38 @@ class Null(object):
__slots__ = []
def API(typ, mod=None):
"""
When used as a decorator, it makes the function/class visible in the given
module. If *mod* is None, it is made visible in the module it was defined
otherwise it is made visible to the given module
In practice it means the name of *typ* will appear in `mod.__all__`
The following example exports the foo method in the bar module::
# bar.py
from bliss.common.utils import API
@API
def foo(a, b=None):
print('Hello, world!')
"""
name = typ.__name__
if mod is None:
mod = inspect.getmodule(typ)
else:
# make sure the type is in the given module
setattr(mod, name, typ)
try:
_all = mod.__all__
except AttributeError:
_all = mod.__all__ = []
# make sure __all__ is mutable
if isinstance(_all, tuple):
_all = mod.__all__ = list(mod.__all__)
if name not in _all:
_all.append(name)
return typ
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2016 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2016 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
from .biologic import *
__doc__ = biologic.__doc__
\ No newline at end of file
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2016 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
from .biologic import main
main()
\ No newline at end of file
This diff is collapsed.
# -*- coding: utf-8 -*-
'''Catalog of biologic potentiostat techniques'''
import functools
from enum import Enum
import numpy
from .biologic import (MParameter, MTechnique, TechniqueIdentifier,
IntensityRange, VMP3_SERIES, SP_300_SERIES, BiologicError)
def ocv_data_handle(pid, buff, info, values):
nb_cols = info.NbCols-1 # because time takes two columns
table = numpy.zeros((info.NbRows, nb_cols))
data_slice = slice(2, 2 + info.NbCols)
for row in range(info.NbRows):
row_offset = row * info.NbCols
row_data = buff[row_offset:row_offset + info.NbCols]
table[row, 0] = info.StartTime + values.TimeBase * (row_data[0] << 32 + row_data[1])
table[row, 1:] = numpy.cast['float32'](row_data[data_slice])
cols = ['t', 'Ewe', 'Ece'][:info.NbCols-1]
return table, cols
OCV = MTechnique(TechniqueIdentifier.OCV,
MParameter('Rest_time_T', float, 'Rest duration (s)'),
MParameter('Record_every_dE', float, 'Record every dE (V)'),
MParameter('Record_every_dT', float, 'Record every dT (s)'),
data_handler=ocv_data_handle,
summary='''Open Circuit Voltage technique (OCV)''',
description='''\
# Instr. VMP3 SP-300
# file: ocv ocv4
# timebase: 20us 20us
The Open Circuit Voltage (OCV) technique consists of a period during which no
potential or current is applied to the working electrode. The cell is disconnected from
the power amplifier. Only, the potential measurement is available. So the evolution of
the rest potential can be recorded.
''')
CA = MTechnique(TechniqueIdentifier.CA,
MParameter('Voltage_step', float, 'Voltage step (V)', len_range=(0, 100)),
MParameter('vs_initial', bool, 'Voltage step vs initial one', len_range=(0, 100)),
MParameter('Duration_step', float, 'Duration step (s)', len_range=(0, 100)),
MParameter('Step_number', int, 'Number of steps minus 1'),
MParameter('Record_every_dT', float, 'Record every dt (s)'),
MParameter('Record_every_dI', float, 'Record every dI (A)'),
MParameter('N_Cycles', int, 'Number of times the technique is repeated'),
summary='''''',
description='''
# Instr. VMP3 SP-300
# file: ca ca4
# timebase: 24us 21us
The basis of the controlled-potential techniques is the measurement of the current
response to an applied potential step.
The Chronoamperometry (CA) technique involves stepping the potential of the
working electrode from an initial potential, at which (generally) no faradic reaction
occurs, to a potential Ei at which the faradic reaction occurs. The current-time
response reflects the change in the concentration gradient in the vicinity of the
surface.
Chronoamperometry is often used for measuring the diffusion coefficient of
electroactive species or the surface area of the working electrode. This technique can
also be applied to the study of electrode processes mechanisms.
An alternative and very useful mode for recording the electrochemical response is to
integrate the current, so that one obtains the charge passed as a function of time.
This is the chronocoulometric mode that is particularly used for measuring the
quantity of adsorbed reactants.
''')
CP = MTechnique(TechniqueIdentifier.CP,
MParameter('Current_step', float, 'Current step (A)', len_range=(0, 100)),
MParameter('vs_initial', bool, 'Current step vs initial one', len_range=(0, 100)),
MParameter('Duration_step', float, 'Duration step (s)', len_range=(0, 100)),
MParameter('Step_number', int, 'Number of steps minus 1'),
MParameter('Record_every_dT', float, 'Record every dt (s) (>=0)'),
MParameter('Record_every_dE', float, 'Record every dE (V) (>=0)'),
MParameter('N_Cycles', int, 'Number of times the technique is repeated (>=0)'),
MParameter('I_Range', IntensityRange, 'I range'),
summary='''Chrono-Potentiometry technique''',
description='''
# Instr. VMP3 SP-300
# file: cp cp4
# timebase: 21us 21us
The Chronopotentiometry (CP) is a controlled current technique. The current is
controlled and the potential is the variable determined as a function of time. The
chronopotentiometry technique is similar to the Chronoamperometry technique,
potential steps being replaced by current steps. The current is applied between the
working and the counter electrode.
This technique can be used for different kind of analysis or to investigate electrode
kinetics. It is considered less sensitive than voltammetric techniques for analytical
uses. Generally, the curves Ewe = f(t) contains plateaus that correspond to the redox
potential of electroactive species.
''')
CV = MTechnique(TechniqueIdentifier.CV,
MParameter('vs_initial', bool, 'Current step vs initial one', len_range=(5, 5)),
MParameter('Voltage_step', float, 'Voltage step (V) [Ei, E1, E2, Ei, Ef]', len_range=(5, 5)),
MParameter('Scan_Rate', float, 'slew rate array (mV/s) (>=0)', len_range=(5, 5)),
MParameter('Scan_number', int, 'Scan number (=2)'),
MParameter('Record_every_dE', float, 'recording on dE (V) (>=0)'),
MParameter('Average_over_dE', bool, 'average every dE'),
MParameter('N_Cycles', int, 'Number of cycles (>=0)'),
MParameter('Begin_measuring_I', float, 'Begin step accumulation. 1 means 100% of step ([0..1])'),
MParameter('End_measuring_I', float, 'End step accumulation. 1 means 100% of step ([0..1])'),
summary='''Cyclic Voltammetry technique''',
description='''
# Instr. VMP3 SP-300
# file: cv cv4
# timebase: 40us 45us
Cyclic voltammetry (CV) is the most widely used technique for acquiring qualitative
informations about electrochemical reactions. CV provides informations on redox
processes, heterogeneous electron-transfer reactions and adsorption processes. It
offers a rapid location of redox potential of the electroactive species.
CV consists of scanning linearly the potential of a stationary working electrode using a
triangular potential waveform. During the potential sweep, the potentiostat measures
the current resulting from electrochemical reactions (consecutive to the applied
potential). The cyclic voltammogram is a current response as a function of the applied
potential.
''')
CVA = MTechnique(TechniqueIdentifier.CVA,
MParameter('vs_initial_scan', bool, 'Current scan vs initial one', len_range=(4, 4)),
MParameter('Voltage_scan', float, 'Voltage scan (V) )[Ei, E1, E2, Ef])', len_range=(4, 4)),
MParameter('Scan_Rate', float, 'slew rate array (mV/s) (>=0)', len_range=(4, 4)),
MParameter('Scan_number', int, 'Scan number (=2)'),
MParameter('Record_every_dE', float, 'recording on dE (>=0)'),
MParameter('Average_over_dE', bool, 'average every dE'),
MParameter('N_Cycles', int, 'Number of cycles (>=0)'),
MParameter('Begin_measuring_I', float, 'Begin step accumulation. 1 means 100% of step ([0..1])'),
MParameter('End_measuring_I', float, 'End step accumulation. 1 means 100% of step ([0..1])'),
MParameter('vs_initial_step', bool, 'Current step vs initial one', len_range=(2, 2)),
MParameter('Voltage_step', float, 'Voltage step (V)', len_range=(2, 2)),
MParameter('Duration_step', float, 'Duration step (s)', len_range=(2, 2)),
MParameter('Step_number', int, 'Step number'),
MParameter('Record_every_dT', float, 'Recording on dT'),
MParameter('Record_every_dI', float, 'Recording on dI'),
MParameter('Trig_on_off', bool, 'trigger'),
file_name='biovscan',
summary='''Cyclic Voltammetry Advanced technique''',
description='''
# Instr. VMP3 SP-300
# file: biovscan biovscan
# timebase: 40us 40us
Cyclic voltammetry (CV) is the most widely used technique for acquiring qualitative
information about electrochemical reactions. CV provides information on redox
processes, heterogeneous electron-transfer reactions and adsorption processes. It
offers a rapid location of redox potential of the electroactive species.
CV consists of scanning linearly the potential of a stationary working electrode using a
triangular potential waveform. During the potential sweep, the potentiostat measures
the current resulting from electrochemical reactions (consecutive to the applied
potential). The cyclic voltammogram is a current response as a function of the applied
potential.
''')
PDYN = MTechnique(TechniqueIdentifier.PDYN,
MParameter('Voltage_step', float, 'Vertex potential (V)'),
MParameter('vs_initial', bool, 'Vertex potential vs initial one'),
MParameter('Scan_Rate', float, 'Scan rate (V/s) from previous vertex potential'),
MParameter('Scan_number', int, 'Number of scans minus 1'),
MParameter('Record_every_dE', float, 'Record every dE (V)'),
MParameter('N_Cycles', int, 'Number of times the technique is repeated'),
MParameter('Begin_measuring_I', float, 'Begin step accumulation. 1 means 100% of step ([0..1])'),
MParameter('End_measuring_I', float, 'End step accumulation. 1 means 100% of step ([0..1])'),
file_name='vscan',
summary='''Voltage Scan''',
description='''
The Potentiodynamic (PDYN) technique allows the user to perform potentiodynamic
periods with different scan rates.''')
# TEMPLATE:
#
# = MTechnique(TechniqueIdentifier.,
# MParameter('', , ''),
# MParameter('', , ''),
# MParameter('', , ''),
# MParameter('', , ''),
# MParameter('', , ''),
# MParameter('', , ''),
# summary='''''',
# description='''
# ''')
#: dict<technique ID: technique>
technique_map = {}
__all__ = ['technique_map']
for k, v in globals().items():
if not isinstance(v, MTechnique):
continue
__all__.append(k)
technique_map[v.id] = v
# -*- coding: utf-8 -*-
import unittest
import collections
try:
from bliss.controllers.potentiostat import biologic
except ImportError:
import os
import sys
__this_dir = os.path.dirname(__file__)
__bliss_dir = os.path.join(__this_dir, *(2*[os.path.pardir]))
__bliss_dir = os.path.realpath(__bliss_dir)
sys.path.append(__bliss_dir)
from bliss.controllers.potentiostat import biologic
from bliss.controllers.potentiostat.biologic import (Potentiostat,
DeviceInfo, ChannelInfo, ExperimentInfo, CurrentValues,
IntensityRange, VoltageRange, Bandwidth,
ECLibError, ECLibErrorCode)
from bliss.controllers.potentiostat.biologic.techniques import OCV, PDYN
_number = int
try:
_number = int, long
except NameError:
pass
class BiologicLib(unittest.TestCase):
def test_error_message(self):
for error in biologic.ECLibErrorCode:
msg = biologic.get_eclib_error_msg(error.value)
self.assertIsInstance(msg, str)
self.assertTrue(msg) # something in the message
def test_lib_version(self):
version = biologic.get_lib_version()
self.assertIsInstance(version, str) # check it is string
self.assertTrue(version) # check non empty string
self.assertIn('.', version) # check that has at least one '.' character
def test_volume_serial_number(self):
volume_nb = biologic.get_volume_serial_number()
self.assertIsInstance(volume_nb, _number) # check it is integer
self.assertGreater(volume_nb, 0) # check non 0
def test_find_echem_dev(self):
devs = biologic.find_echem_dev()
self.assertIsInstance(devs, collections.Sequence)
def test_find_echem_eth_dev(self):
devs = biologic.find_echem_eth_dev()
self.assertIsInstance(devs, collections.Sequence)
def test_find_echem_usb_dev(self):
try:
devs = biologic.find_echem_usb_dev()
self.assertIsInstance(devs, collections.Sequence)
except biologic.BLFindError as e:
pass
class BiologicControllerError(unittest.TestCase):
def test_potentiostat_connect_error(self):
p = Potentiostat('an invalid host')
try:
p.connect()
raise Exception('Should have raised ECLibException')
except ECLibError as e:
self.assertEquals(e.error_code, ECLibErrorCode.CONNECTIONFAILED)
try:
p.connect('another invalid host')
raise Exception('Should have raised ECLibException')
except ECLibError as e:
self.assertEquals(e.error_code, ECLibErrorCode.CONNECTIONFAILED)
class BiologicController(unittest.TestCase):
def setUp(self):
self.p = Potentiostat('192.109.209.128')
def tearDown(self):
self.p.disconnect()
pass
def test_connection(self):
self.assertTrue(self.p.test_connection())
def test_info(self):
self.assertIsInstance(self.p.info, DeviceInfo)
def test_is_channel_plugged(self):
self.assertTrue(self.p.is_channel_plugged(0))
def test_get_channel_info(self):
info = self.p.get_channel_info(0)
self.assertIsInstance(info, ChannelInfo)
def test_get_current_values(self):
values = self.p.get_current_values(0)
self.assertIsInstance(values, CurrentValues)
def test_get_experiment_info(self):
info = self.p.get_experiment_info(0)
self.assertIsInstance(info, ExperimentInfo)
# def test_set_experiment_info(self):
# info = ExperimentInfo(Group=55, PCidentifier=12, TimeHMS=456,
# TimeYMD=1111, Filename='bla.dat')
# self.p.set_experiment_info(0, info)
# result = self.p.get_experiment_info(0)
# self.assertEqual(info, result)
def test_load_technique(self):
ocv = OCV(OCV.Rest_time_T(0.1),
OCV.Record_every_dE(0.1),
OCV.Record_every_dT(0.01),
OCV.E_Range(VoltageRange.AUTO))
self.p.load_technique(0, ocv)
info = self.p.get_channel_info(0)
self.assertEquals(info.NbOfTechniques, 1)
def test_load_techniques(self):
ocv = OCV(OCV.Rest_time_T(0.1),
OCV.Record_every_dE(0.1),
OCV.Record_every_dT(0.01),
OCV.E_Range(VoltageRange.AUTO))
pydn = PDYN(PDYN.Voltage_step[0.0, 1.0, -2.0, 0.0],
PDYN.vs_initial[False, False, False, False],
PDYN.Scan_Rate[0.0, 10.0, 15.0, 20.0],
PDYN.Scan_number(2),
PDYN.N_Cycles(0),
PDYN.Record_every_dE(0.01),
PDYN.Begin_measuring_I(0.4),
PDYN.End_measuring_I(0.8),
PDYN.I_Range(IntensityRange._10mA),
PDYN.E_Range(VoltageRange.AUTO),
PDYN.Bandwidth(Bandwidth._5))
self.p.load_techniques({0: [ocv, pydn]})
info = self.p.get_channel_info(0)
self.assertEquals(info.NbOfTechniques, 2)
if __name__ == '__main__':
import logging
logging.basicConfig(level=logging.ERROR)
unittest.main()
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment