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
# -*- 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.
'''
Biologic potentiostat module
Example::
>>> from bliss.controllers.potentiostat.biologic import Potentiostat
>>> p = Potentiostat('192.109.209.128')
>>> # device information
>>> print(p.info)
DeviceInfo:
DeviceType = DeviceType.SP50
RAMsize = 32
CPU = 9200
NumberOfChannels = ...
>>> # list of plugged channels
>>> p.get_channels_plugged()
set([0])
>>> # make sure channel 0 is installed
>>> p.is_channel_plugged(0)
True
>>> # show current values of channel 0
>>> print(p.get_current_values(0))
CurrentValues:
State = STOP
MemFilled = 0
Ewe = -0.007
I = 0.0
Freq = ...
>>> # load OCV technique into channel 0
>>> from bliss.controllers.potentiostat.biologic import VoltageRange
>>> from bliss.controllers.potentiostat.biologic.techniques import OCV
>>> ocv = OCV(OCV.Rest_time_T(0.1, 0),
... OCV.Record_every_dE(0.1, 0),
... OCV.Record_every_dT(0.01, 0),
... OCV.E_Range(VoltageRange.AUTO))
>>> self.p.load_technique(0, ocv)
'''
__all__ = ['PID', 'DeviceInfo', 'ChannelInfo', 'CurrentValues',
'DataInfo', 'HardwareConf', 'TechniqueInfo',
'ExperimentInfo', 'DeviceType', 'VMP3_SERIES', 'VMP4_SERIES',
'SP_300_SERIES', 'Instrument', 'Technique', 'Parameter']
import os
import sys
import ctypes
import struct
import inspect
import logging
import functools
import collections
from enum import Enum
from ctypes import (c_int16, c_uint16, c_int32, c_uint32, c_int64, c_uint64,
c_int8, c_uint8, c_float, c_double, c_bool, c_ubyte, c_char, c_char_p,
Structure, byref, pointer, POINTER, create_string_buffer)
try:
from collections import OrderedDict
except AttributeError:
from ordereddict import OrderedDict
import numpy
from bliss.common.utils import API
c_uint8_p = POINTER(c_uint8)
c_uint16_p = POINTER(c_uint16)
c_uint32_p = POINTER(c_uint32)
c_uint64_p = POINTER(c_uint64)
c_int8_p = POINTER(c_int8)
c_int16_p = POINTER(c_int16)
c_int32_p = POINTER(c_int32)
c_int64_p = POINTER(c_int64)
_this_dir, _name = os.path.split(os.path.realpath(__file__))
_name, _ = os.path.splitext(_name)
_log = logging.getLogger(_name)
_is_32 = (8 * struct.calcsize('P')) == 32
_PY3 = sys.version_info[0] > 2
def NamedTuple(*args, **kwargs):
r = collections.namedtuple(*args, **kwargs)
def s(self):
n = max(map(len, self._fields))
T = '{{0: >{0}}} = {{1}}'.format(n)
fields = '\n'.join([T.format(k, getattr(self, k)) for k in self._fields])
return '{0}:\n{1}'.format(self.__class__.__name__, fields)
r.__str__ = s
return r
#: potentiostat identification
PID = NamedTuple('PID', 'ID dev_info url')
# Note on the need for following functions: We could have just done
# something like:
# eclib = LoadLibrary('eclib.dll')
# The problem that if the library is not present or we are not under
# windows, the generation of documentation would fail since the module
# could not be loaded. The functions allow for a *lazy* load and
# initialization of the biologic DLLs
def __get_lib(name):
if not _is_32:
name += '64'
lib_path = os.path.join(_this_dir, name)
return ctypes.WinDLL(lib_path)
def __init_eclib(c_eclib):
# General functions
c_eclib.BL_GetLibVersion.argtypes = [c_char_p, c_uint32_p]
c_eclib.BL_GetLibVersion.restype = c_int32
c_eclib.BL_GetVolumeSerialNumber.restype = c_uint32
c_eclib.BL_GetErrorMsg.argtypes = [c_int32, c_char_p, c_uint32_p]
c_eclib.BL_GetErrorMsg.restype = c_int32
# Communication functions
c_eclib.BL_Connect.argtypes = [c_char_p, c_uint8, c_int32_p, _DeviceInfo_p]
c_eclib.BL_Connect.restype = c_int32
c_eclib.BL_Disconnect.argtypes = [c_int32]
c_eclib.BL_Disconnect.restype = c_int32
c_eclib.BL_TestConnection.argtypes = [c_int32]
c_eclib.BL_TestConnection.restype = c_int32
c_eclib.BL_TestCommSpeed.argtypes = [c_int32, c_uint8, c_int32_p, c_int32_p]
c_eclib.BL_TestCommSpeed.restype = c_int32
c_eclib.BL_GetUSBdeviceinfos.argtypes = [c_uint32, c_char_p, c_uint32_p, c_char_p, c_uint32_p, c_char_p, c_uint32_p]
c_eclib.BL_GetUSBdeviceinfos.restype = c_bool
# Firmware functions
c_eclib.BL_LoadFirmware.argtypes = [c_int32, c_uint8_p, c_int32_p, c_uint8,
c_bool, c_bool, c_char_p, c_char_p]
c_eclib.BL_LoadFirmware.restype = c_int32
# Channel information functions
c_eclib.BL_IsChannelPlugged.argtypes = [c_int32, c_uint8]
c_eclib.BL_IsChannelPlugged.restype = c_bool
c_eclib.BL_GetChannelsPlugged.argtypes = [c_int32, c_uint8_p, c_uint8]
c_eclib.BL_GetChannelsPlugged.restype = c_int32
c_eclib.BL_GetChannelInfos.argtypes = [c_int32, c_uint8, _ChannelInfo_p]
c_eclib.BL_GetChannelInfos.restype = c_int32
# Technique functions
c_eclib.BL_DefineBoolParameter.argtypes = [c_char_p, c_bool, c_int32, _EccParam_p]
c_eclib.BL_DefineBoolParameter.restype = c_int32
c_eclib.BL_DefineSglParameter.argtypes = [c_char_p, c_float, c_int32, _EccParam_p]
c_eclib.BL_DefineSglParameter.restype = c_int32
c_eclib.BL_DefineIntParameter.argtypes = [c_char_p, c_int32, c_int32, _EccParam_p]
c_eclib.BL_DefineIntParameter.restype = c_int32
c_eclib.BL_LoadTechnique.argtypes = [c_int32, c_uint8, c_char_p, _EccParams, c_bool, c_bool, c_bool]
c_eclib.BL_LoadTechnique.restype = c_int32
# Start/stop functions
# Data functions
c_eclib.BL_GetCurrentValues.argtypes = [c_int32, c_uint8, _CurrentValues_p]
c_eclib.BL_GetCurrentValues.restype = c_int32
# Miscellaneous functions
c_eclib.BL_SetExperimentInfos.argtypes = [c_int32, c_uint8, _ExperimentInfo]
c_eclib.BL_SetExperimentInfos.restype = c_int32
__ECLIB = None
def eclib():
global __ECLIB
if not __ECLIB:
__ECLIB = __get_lib('eclib')
__init_eclib(__ECLIB)
return __ECLIB
def __init_blfind(c_blfind):
c_blfind.BL_FindEChemDev.argtypes = [c_char_p, c_uint32_p, c_uint32_p]
c_blfind.BL_FindEChemDev.restype = c_int32
c_blfind.BL_FindEChemEthDev.argtypes = [c_char_p, c_uint32_p, c_uint32_p]
c_blfind.BL_FindEChemEthDev.restype = c_int32
c_blfind.BL_FindEChemUsbDev.argtypes = [c_char_p, c_uint32_p, c_uint32_p]
c_blfind.BL_FindEChemUsbDev.restype = c_int32
__BLFIND = None
def blfind():
global __BLFIND
if not __BLFIND:
__BLFIND = __get_lib('blfind')
__init_blfind(__BLFIND)
return __BLFIND
def __struct_to_namedtuple_type(struct_class):
# convert a ctypes.Structure to a namedtuple
ret = NamedTuple(struct_class.__name__[1:], zip(*struct_class._fields_)[0])
if _PY3:
ret.__doc__ = s.__doc__
return ret
def __struct_to_namedtuple(struct, ntuple_type=None):
# convert a ctypes.Structure instance to a namedtuple instance. If
# ntuple_type is None, it tries to find in globals a namedtuple with the
# name of s[1:], if s starts with '_' otherwise throws ValueError
if ntuple_type is None:
struct_name = struct.__class__.__name__
if not struct_name.startswith('_'):
raise ValueError('Cannot determine namedtuple name from structure ' \
'name {0}'.format(struct_name))
ntuple_type = globals()[struct_name[1:]]
kwargs = dict([(k, getattr(struct, k)) for k, _ in struct._fields_])
return ntuple_type(**kwargs)
def __struct_to_dict(struct):
fields = struct.__class__._fields_
return dict([(name, getattr(struct, name)) for name, _ in fields])
def __namedtuple_to_struct(ntuple, struct_class=None):
if struct_class is None:
ntuple_name = ntuple.__class__.__name__
if not ntuple_name.startswith('_'):
raise ValueError('Cannot determine structure name from ' \
'namedtuple name {0}'.format(struct_name))
struct_class = globals()['_' + ntuple_name]
return struct_class(*ntuple)
def stringify_struct(k):
M = max(map(len, [f[0] for f in k._fields_]))
msg = '{{0: >{0}}} = {{1}}'.format(M)
cname = k.__name__
def s(self):
f = [msg.format(name, getattr(self, name)) for name, _ in self._fields_]
return '{0}:\n{1}'.format(cname, '\n'.join(f))
k.__str__ = s
return k
@stringify_struct
class _DeviceInfo(Structure):
'''
Information about the device that :func:`connect` connected to.
'''
_fields_ = [
('DeviceCode', c_int32), # Device code
('RAMsize', c_int32), # RAM size, in MBytes
('CPU', c_int32), # Computer board cpu
('NumberOfChannels', c_int32), # Number of channels connected
('NumberOfSlots', c_int32), # Number of slots available
('FirmwareVersion', c_int32), # Communication firmware version
('FirmwareDate_yyyy', c_int32), # Communication firmware date YYYY
('FirmwareDate_mm', c_int32), # Communication firmware date MM
('FirmwareDate_dd', c_int32), # Communication firmware date DD
('HTdisplayOn', c_int32), # Allow hyper-terminal prints (true/false)
('NbOfConnectedPC', c_int32), # Number of connected PC
]
_DeviceInfo_p = POINTER(_DeviceInfo)
DeviceInfo = __struct_to_namedtuple_type(_DeviceInfo)
@stringify_struct
class _ChannelInfo(Structure):
'''
Information about the channel. You can obtain them using
:func:`get_channel_info`
'''
_fields_ = [
('Channel', c_int32), # Channel (0..15)
('BoardVersion', c_int32), # Board version
('BoardSerialNumber', c_int32), # Board serial number
('FirmwareCode', c_int32), # Firmware loaded (see FirmwareCode)
('FirmwareVersion', c_int32), # Firmware version
('XilinxVersion', c_int32), # Xilinx version
('AmpCode', c_int32), # Amplifier code (see AmplifierType)
('NbAmps', c_int32), # Number of amplifiers
('Lcboard', c_int32), # Low current board present (true/false)
('Zboard', c_int32), # true if the channel has impedance measurement capability
('RESERVED', c_int32), # not used
('RESERVED2', c_int32), # not used
('MemSize', c_int32), # Memory size (in bytes)
('MemFilled', c_int32), # Memory filled (in bytes)
('State', c_int32), # Channel State (see ChannelState)
('MaxIRange', c_int32), # Maximum I range allowed (IntensityRange)
('MinIRange', c_int32), # Minimum I range allowed (IntensityRange)
('MaxBandwidth', c_int32), # Maximum bandwidth allowed (Bandwidth)
('NbOfTechniques', c_int32), # Number of techniques loaded
]
_ChannelInfo_p = POINTER(_ChannelInfo)
ChannelInfo = __struct_to_namedtuple_type(_ChannelInfo)
@stringify_struct
class _CurrentValues(Structure):
'''
Information about the channel current values measurement.
'''
_fields_ = [
('State', c_int32), # Channel state: see ChannelState
('MemFilled', c_int32), # Memory filled (in Bytes)
('TimeBase', c_float), # Time base (s)
('Ewe', c_float), # Working electrode potential (V)
('EweRangeMin', c_float), # Ewe min range (V)
('EweRangeMax', c_float), # Ewe max range (V)
('Ece', c_float), # Counter electrode potential (V)
('EceRangeMin', c_float), # Ece min range (V)
('EceRangeMax', c_float), # Ece max range (V)
('Eoverflow', c_int32), # Potential overflow
('I', c_float), # Current value (A)
('IRange', c_int32), # Current range (see IntensityRange)
('Ioverflow', c_int32), # Current overflow
('ElapsedTime', c_float), # Elapsed time (s)
('Freq', c_float), # Frequency (Hz)
('Rcomp', c_float), # R compensation (Ohm)
('Saturation', c_int32), # E or/and I saturation
('OptErr', c_int32), # Hardware option error code (see ErrorCodes, SP-300 series only)
('OptPos', c_int32), # Index of the option generating the OptErr (SP-300 series only)
]
_CurrentValues_p = POINTER(_CurrentValues)
CurrentValues = __struct_to_namedtuple_type(_CurrentValues)
@stringify_struct
class _DataInfo(Structure):
'''
Holds metadata about the data you just received with :func:`get_data`
'''
_fields_ = [
('IRQskipped', c_int32), # Number of IRQ skipped
('NbRows', c_int32), # Number of rows into the data buffer, i.e.number of points saved in the data buffer
('NbCols', c_int32), # Number of columns into the data buffer, i.e. number of variables defining a po('in the data buffer
('TechniqueIndex', c_int32), # Index (0-based) of the technique who has generated the data. This field is only useful for linked techniques
('TechniqueID', c_int32), # Identifier of the technique who has generated the data. Must be used to identify the data format into the data buffer (see TechniqueIdentifier )
('ProcessIndex', c_int32), # Index (0-based) of the process of the technique who has generated the data. Must be used to identify the data format into the data buffer
('loop', c_int32), # Loop number
('StartTime', c_double), # Start time (s)
('MuxPad', c_int32), # Active MP-MEA option pad number (SP-300 series only)
]
_DataInfo_p = POINTER(_DataInfo)
DataInfo = __struct_to_namedtuple_type(_DataInfo)
_DataBuffer = 1000 * c_uint32
_DataBuffer_p = POINTER(_DataBuffer)
@stringify_struct
class _EccParam(Structure):
'''
Defines an elementary technique parameter and is used by
:func:`load_technique`
'''
_fields_ = [
('ParamStr', 64*c_char), # (len=64) string who defines the parameter
# label (see section 7. Techniques in PDF for
# a complete description of parameters available
# for each technique)
('ParamType', c_int32), # Parameter type (see ParamType)
('ParamVal', c_int32), # Parameter value. \warning Numerical value
('ParamIndex', c_int32), # Parameter index (0-based), useful for multi-step parameters. Otherwise should be 0.
]
_EccParam_p = POINTER(_EccParam)
@stringify_struct
class _EccParams(Structure):
'''
Defines an array of elementary technique parameters and is used by
:func:`load_technique`
'''
_fields_ = [
('len', c_int32), # Length of the array pointed by pParams
('pParams', _EccParam_p), # Pointer on the array of technique parameters (array of structure EccParam)
]
_EccParams_p = POINTER(_EccParams)
@stringify_struct
class _HardwareConf(Structure):
'''
Describes the channel electrode configuration.
See :func:`get_hard_conf` and :func:`set_hard_conf`
'''
_fields_ = [
('Conn', c_int32), # Electrode connection (see ElectrodeConn)
('Ground', c_int32), # Instrument ground (see ElectrodeMode)
]
_HardwareConf_p = POINTER(_HardwareConf)
HardwareConf = __struct_to_namedtuple_type(_HardwareConf)
@stringify_struct
class _TechniqueInfo(Structure):
'''Technique information'''
_fields_ = [
('id', c_int32), # technique id
('indx', c_int32), # index of the technique
('nbParams', c_int32), # number of parameters
('nbSettings', c_int32), # number of hardware settings
('Params', _EccParam_p), # pointer to the parameters
('HardSettings', _EccParam_p), # pointer to the hardware settings
]
_TechniqueInfo_p = POINTER(_TechniqueInfo)
TechniqueInfo = __struct_to_namedtuple_type(_TechniqueInfo)
@stringify_struct
class _ExperimentInfo(Structure):
'''Experiment informations'''
_fields_ = [
('Group', c_int32),
('PCidentifier', c_int32),
('TimeHMS', c_int32),
('TimeYMD', c_int32),
('Filename', 256*c_char),
]
_ExperimentInfo_p = POINTER(_ExperimentInfo)
ExperimentInfo = __struct_to_namedtuple_type(_ExperimentInfo)
@API
class DeviceType(Enum):
'''Device type'''
VMP = 0 #: VMP device
VMP2 = 1 #: VMP2 device
MPG = 2 #: MPG device
BISTAT = 3 #: BISTAT device
MCS_200 = 4 #: MCS-200 device
VMP3 = 5 #: VMP3 device
VSP = 6 #: VSP device
HCP803 = 7 #: HCP-803 device
EPP400 = 8 #: EPP-400 device
EPP4000 = 9 #: EPP-4000 device
BISTAT2 = 10 #: BISTAT 2 device
FCT150S = 11 #: FCT-150S device
VMP300 = 12 #: VMP-300 device
SP50 = 13 #: SP-50 device
SP150 = 14 #: SP-150 device
FCT50S = 15 #: FCT-50S device
SP300 = 16 #: SP300 device
CLB500 = 17 #: CLB-500 device
HCP1005 = 18 #: HCP-1005 device
CLB2000 = 19 #: CLB-2000 device
VSP300 = 20 #: VSP-300 device
SP200 = 21 #: SP-200 device
MPG2 = 22 #: MPG2 device
ND1 = 23 #: RESERVED
ND2 = 24 #: RESERVED
ND3 = 25 #: RESERVED
ND4 = 26 #: RESERVED
SP240 = 27 #: SP-240 device
MPG205 = 28 #: MPG-205 (VMP3)
MPG210 = 29 #: MPG-210 (VMP3)
MPG220 = 30 #: MPG-220 (VMP3)
MPG240 = 31 #: MPG-240 (VMP3)
UNKNOWN = 255 #: Unknown device
for i in range(32,255):
setattr(DeviceType, 'UNSUPPORTED_{0}'.format(i), i)
VMP3_SERIES = set((DeviceType.VMP2, DeviceType.VMP3, DeviceType.BISTAT,
DeviceType.VSP, DeviceType.SP50, DeviceType.SP150,
DeviceType.MPG2, DeviceType.HCP803))
VMP4_SERIES = set((DeviceType.SP200, DeviceType.SP240, DeviceType.SP300,
DeviceType.VSP300, DeviceType.VMP300))
SP_300_SERIES = set((DeviceType.SP200, DeviceType.SP240, DeviceType.SP300))
@API
class FirmwareCode(Enum):
'''Firmware code'''
NONE = 0 #: No firmware loaded
INTERPR = 1 #: Firmware for EC-Lab software
UNKNOWN = 4 #: Unknown firmware loaded
KERNEL = 5 #: Firmware for the library
INVALID = 8 #: Invalid firmware loaded
ECAL = 10 #: Firmware for calibration software
@API
class AmplifierType(Enum):
'''Amplifier type'''
VMP3_NONE = 0 #: No amplifier VMP3 series
VMP3_2A = 1 #: Amplifier 2 A VMP3 series
VMP3_1A = 2 #: Amplifier 1 A VMP3 series
VMP3_5A = 3 #: Amplifier 5 A VMP3 series
VMP3_10A = 4 #: Amplifier 10 A VMP3 series
VMP3_20A = 5 #: Amplifier 20 A VMP3 series
VMP3_HEUS = 6 #: reserved VMP3 series
VMP3_LC = 7 #: Low current amplifier VMP3 series
VMP3_80A = 8 #: Amplifier 80 A VMP3 series
VMP3_4AI = 9 #: Amplifier 4 A VMP3 series
VMP3_PAC = 10 #: Fuel Cell Tester VMP3 series
VMP3_4AI_VSP = 11 #: Amplifier 4 A (VSP instrument) VMP3 series
VMP3_LC_VSP = 12 #: Low current amplifier (VSP instrument) VMP3 series
VMP3_UNDEF = 13 #: Undefined amplifier VMP3 series
VMP3_MUIC = 14 #: reserved VMP3 series
VMP3_NONE_GIL = 15 #: No amplifier VMP3 series
VMP3_8AI = 16 #: Amplifier 8 A VMP3 series
VMP3_LB500 = 17 #: Amplifier LB500 VMP3 series
VMP3_100A5V = 18 #: Amplifier 100 A VMP3 series
VMP3_LB2000 = 19 #: Amplifier LB2000 VMP3 series
_1A48V = 20 #: Amplifier 1A 48V SP-300 series
_4A10V = 21 #: Amplifier 4A 10V SP-300 series
_5A_MPG2B = 22 #: MPG-205 5A amplifier
_10A_MPG2B = 23 #: MPG-210 10A amplifier
_20A_MPG2B = 24 #: MPG-220 20A amplifier
_40A_MPG2B = 25 #: MPG-240 40A amplifier
COIN_CELL_HOLDER = 26 #: coin cell holder