Skip to content

Commit

Permalink
Merge pull request #844 from bmoneke/oxford-adapter
Browse files Browse the repository at this point in the history
Turn Oxford adapter into parent class.
  • Loading branch information
BenediktBurger committed Mar 6, 2023
2 parents 40359e9 + 9bc4521 commit a24a167
Show file tree
Hide file tree
Showing 11 changed files with 265 additions and 69 deletions.
11 changes: 0 additions & 11 deletions docs/api/instruments/oxfordinstruments/adapters.rst

This file was deleted.

11 changes: 11 additions & 0 deletions docs/api/instruments/oxfordinstruments/base.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
##################################
Oxford Instruments Base Instrument
##################################

.. autoclass:: pymeasure.instruments.oxfordinstruments.base.OxfordInstrumentsBase
:members:
:show-inheritance:

.. autoclass:: pymeasure.instruments.oxfordinstruments.base.OxfordVISAError
:members:
:show-inheritance:
2 changes: 1 addition & 1 deletion docs/api/instruments/oxfordinstruments/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This section contains specific documentation on the Oxford Instruments instrumen
.. toctree::
:maxdepth: 2

adapters
base
ITC503
IPS120_10
PS120_10
1 change: 0 additions & 1 deletion pymeasure/instruments/oxfordinstruments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#


from .adapters import OxfordInstrumentsAdapter
from .itc503 import ITC503
from .ips120_10 import IPS120_10
from .ps120_10 import PS120_10
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
#


from pymeasure.adapters import VISAAdapter
from pymeasure.instruments import Instrument
from pyvisa.errors import VisaIOError

from pyvisa import constants as vconst
import re
import logging

Expand All @@ -38,23 +38,37 @@ class OxfordVISAError(Exception):
pass


class OxfordInstrumentsAdapter(VISAAdapter):
"""Adapter class for the VISA library using PyVISA to communicate
with instruments.
class OxfordInstrumentsBase(Instrument):
"""Base instrument for devices from Oxford Instruments.
Checks the replies from instruments for validity.
:param resource_name: VISA resource name that identifies the address
:param adapter: A string, integer, or :py:class:`~pymeasure.adapters.Adapter` subclass object
:param string name: The name of the instrument. Often the model designation by default.
:param max_attempts: Integer that sets how many attempts at getting a
valid response to a query can be made
:param kwargs: key-word arguments for constructing a PyVISA Adapter
:param \\**kwargs: In case ``adapter`` is a string or integer, additional arguments passed on
to :py:class:`~pymeasure.adapters.VISAAdapter` (check there for details).
Discarded otherwise.
"""

timeoutError = VisaIOError(-1073807339)

regex_pattern = r"^([a-zA-Z])[\d.+-]*$"

def __init__(self, resource_name, max_attempts=5, **kwargs):
super().__init__(resource_name, **kwargs)
def __init__(self, adapter, name="OxfordInstruments Base", max_attempts=5, **kwargs):
kwargs.setdefault('read_termination', '\r')

super().__init__(adapter,
name=name,
includeSCPI=False,
asrl={
'baud_rate': 9600,
'data_bits': 8,
'parity': vconst.Parity.none,
'stop_bits': vconst.StopBits.two,
},
**kwargs)
self.max_attempts = max_attempts

def ask(self, command):
Expand All @@ -72,9 +86,15 @@ def ask(self, command):
"""

for attempt in range(self.max_attempts):
response = super().ask(command)
# Skip the checks in "write", because we explicitly want to get an answer here
super().write(command)
self.wait_for()
response = self.read()

if self.is_valid_response(response, command):
if command.startswith("R"):
# Remove the leading R of the response
return response.strip("R")
return response

log.debug("Received invalid response to '%s': %s", command, response)
Expand All @@ -92,7 +112,7 @@ def ask(self, command):
raise OxfordVISAError(f"Retried {self.max_attempts} times without getting a valid "
"response, maybe there is something worse at hand.")

def _write(self, command):
def write(self, command):
"""Write command to instrument and check whether the reply indicates that the given command
was not understood.
The devices from Oxford Instruments reply with '?xxx' to a command 'xxx' if this command is
Expand All @@ -103,7 +123,7 @@ def _write(self, command):
:raises: :class:`~.OxfordVISAError` if the instrument does not recognise the supplied
command or if the response of the instrument is not understood
"""
super()._write(command)
super().write(command)

if not command[0] == "$":
response = self.read()
Expand Down Expand Up @@ -158,4 +178,4 @@ def is_valid_response(self, response, command):
return bool(match)

def __repr__(self):
return "<OxfordInstrumentsAdapter(resource='%s')>" % self.connection.resource_name
return "<OxfordInstrumentsAdapter(adapter='%s')>" % self.adapter.connection.resource_name
22 changes: 3 additions & 19 deletions pymeasure/instruments/oxfordinstruments/ips120_10.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from pymeasure.instruments.validators import strict_discrete_set
from pymeasure.instruments.validators import truncated_range

from .adapters import OxfordInstrumentsAdapter
from .base import OxfordInstrumentsBase

# Setup logging
log = logging.getLogger(__name__)
Expand All @@ -47,7 +47,7 @@ class SwitchHeaterError(ValueError):
pass


class IPS120_10(Instrument):
class IPS120_10(OxfordInstrumentsBase):
"""Represents the Oxford Superconducting Magnet Power Supply IPS 120-10.
.. code-block:: python
Expand Down Expand Up @@ -116,25 +116,10 @@ def __init__(self,
field_range=None,
**kwargs):

if isinstance(adapter, (int, str)):
kwargs.setdefault('read_termination', '\r')
kwargs.setdefault('send_end', True)
adapter = OxfordInstrumentsAdapter(
adapter,
asrl={
'baud_rate': 9600,
'data_bits': 8,
'parity': 0,
'stop_bits': 20,
},
preprocess_reply=lambda v: v[1:],
**kwargs,
)

super().__init__(
adapter=adapter,
name=name,
includeSCPI=False,
**kwargs
)

if switch_heater_heating_delay is not None:
Expand All @@ -155,7 +140,6 @@ def __init__(self,
version = Instrument.measurement(
"V",
""" A string property that returns the version of the IPS. """,
preprocess_reply=lambda v: v,
)

control_mode = Instrument.control(
Expand Down
21 changes: 3 additions & 18 deletions pymeasure/instruments/oxfordinstruments/itc503.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from pymeasure.instruments.validators import strict_discrete_set, \
truncated_range, strict_range

from .adapters import OxfordInstrumentsAdapter
from .base import OxfordInstrumentsBase


# Setup logging
Expand All @@ -58,7 +58,7 @@ def pointer_validator(value, values):
return tuple(strict_range(v, values) for v in value)


class ITC503(Instrument):
class ITC503(OxfordInstrumentsBase):
"""Represents the Oxford Intelligent Temperature Controller 503.
.. code-block:: python
Expand All @@ -84,25 +84,10 @@ def __init__(self,
max_temperature=1677.7,
**kwargs):

if isinstance(adapter, (int, str)):
kwargs.setdefault('read_termination', '\r')
kwargs.setdefault('send_end', True)
adapter = OxfordInstrumentsAdapter(
adapter,
asrl={
'baud_rate': 9600,
'data_bits': 8,
'parity': 0,
'stop_bits': 20,
},
preprocess_reply=lambda v: v[1:],
**kwargs,
)

super().__init__(
adapter=adapter,
name=name,
includeSCPI=False,
**kwargs,
)

# Clear the buffer in order to prevent communication problems
Expand Down
12 changes: 6 additions & 6 deletions pymeasure/instruments/oxfordinstruments/ps120_10.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@


def PS_custom_get_process(v):
"""convert string to proper float value, for working with the PS 120-10 """
return float(v[1:]) * 1e-2
"""Adjust the received value, for working with the PS 120-10 """
return v * 1e-2


def PS_custom_set_process(v):
"""convert float to proper int value, for working with the PS 120-10 """
"""Convert float to proper int value, for working with the PS 120-10 """
return int(v * 1e2)


Expand Down Expand Up @@ -100,12 +100,12 @@ def __init__(self,

current_setpoint_get_process = PS_custom_get_process
current_setpoint_set_process = PS_custom_set_process
current_setpoint_set_command = "J%d"
current_setpoint_set_command = "I%d"

field_setpoint_get_process = PS_custom_get_process
field_setpoint_set_process = PS_custom_set_process
field_setpoint_set_command = "T%d"
field_setpoint_set_command = "J%d"

sweep_rate_get_process = PS_custom_get_process
sweep_rate_set_process = PS_custom_set_process
sweep_rate_set_command = "A%d"
sweep_rate_set_command = "T%d"
47 changes: 47 additions & 0 deletions tests/instruments/oxfordinstruments/test_base_instrument.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# This file is part of the PyMeasure package.
#
# Copyright (c) 2013-2023 PyMeasure Developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

import pytest

from pymeasure.test import expected_protocol


from pymeasure.instruments.oxfordinstruments.base import OxfordInstrumentsBase, OxfordVISAError


def test_wrong_response():
with expected_protocol(OxfordInstrumentsBase,
[("A", "B"), (None, "")],
max_attempts=1,
) as inst:
with pytest.raises(OxfordVISAError):
inst.ask("A")


def test_write_not_understood_command():
with expected_protocol(OxfordInstrumentsBase,
[("A", "?B")],
) as inst:
with pytest.raises(OxfordVISAError):
inst.write("A")
77 changes: 77 additions & 0 deletions tests/instruments/oxfordinstruments/test_ips120_10.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#
# This file is part of the PyMeasure package.
#
# Copyright (c) 2013-2023 PyMeasure Developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

from pymeasure.test import expected_protocol


from pymeasure.instruments.oxfordinstruments.ips120_10 import IPS120_10


def test_version():
with expected_protocol(IPS120_10,
[("V", "IPS120-10 Version 3.04 @ Oxford Instruments 1996")]
) as inst:
assert inst.version == "IPS120-10 Version 3.04 @ Oxford Instruments 1996"


def test_activity_getter():
with expected_protocol(IPS120_10,
[("X", "X00A0C0M00P00")]
) as inst:
assert inst.activity == "hold"


def test_activity_setter():
with expected_protocol(IPS120_10,
[("A0", "A")]
) as inst:
inst.activity = "hold"


def test_current_setpoint_getter():
with expected_protocol(IPS120_10,
[("R0", "R+1.3")]
) as inst:
assert inst.current_setpoint == 1.3


def test_current_setpoint_setter():
with expected_protocol(IPS120_10,
[("I1.300000", "I")]
) as inst:
inst.current_setpoint = 1.3


def test_control_mode_getter():
with expected_protocol(IPS120_10,
[("X", "X00A0C1M00P00")]
) as inst:
assert inst.control_mode == "RL"


def test_control_mode_setter():
with expected_protocol(IPS120_10,
[("C1", "C")]
) as inst:
inst.control_mode = "RL"

0 comments on commit a24a167

Please sign in to comment.