Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Alisher Gaibulaev
bliss
Commits
8655bdb0
Commit
8655bdb0
authored
Sep 27, 2016
by
Jose Tiago Coutinho Macara
Browse files
First implementation of biologic potentiostat
parent
ad2e6a56
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
bliss/common/utils.py
View file @
8655bdb0
...
...
@@ -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
bliss/controllers/potentiostat/__init__.py
0 → 100644
View file @
8655bdb0
# -*- 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.
bliss/controllers/potentiostat/biologic/__init__.py
0 → 100644
View file @
8655bdb0
# -*- 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
bliss/controllers/potentiostat/biologic/__main__.py
0 → 100644
View file @
8655bdb0
# -*- 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
bliss/controllers/potentiostat/biologic/biologic.py
0 → 100644
View file @
8655bdb0
This diff is collapsed.
Click to expand it.
bliss/controllers/potentiostat/biologic/techniques.py
0 → 100644
View file @
8655bdb0
# -*- 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
tests/controllers/test_biologic.py
0 → 100644
View file @
8655bdb0
# -*- 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
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment