Skip to content

Commit

Permalink
Merge pull request #33 from ralph-group/dev/validators
Browse files Browse the repository at this point in the history
Validator implementation for Instrument.control
  • Loading branch information
cjermain committed Jul 28, 2016
2 parents 738671e + 4968764 commit ff47bdb
Show file tree
Hide file tree
Showing 20 changed files with 625 additions and 331 deletions.
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@
# built documents.
#
# The short X.Y version.
version = '0.3'
version = '0.4'
# The full version, including alpha/beta/rc tags.
release = '0.3'
release = '0.4'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion pymeasure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
# THE SOFTWARE.
#

__version__ = '0.3'
__version__ = '0.4'
59 changes: 39 additions & 20 deletions pymeasure/adapters/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#

import numpy as np
from copy import copy


class Adapter(object):
Expand Down Expand Up @@ -58,15 +59,23 @@ def read(self):
"""
raise NameError("Adapter (sub)class has not implemented reading")

def values(self, command):
def values(self, command, separator=',', cast=float):
""" Writes a command to the instrument and returns a list of formatted
values from the result
:param command: SCPI command to be sent to the instrument
:returns: String ASCII response of the instrument
:param separator: A separator character to split the string into a list
:param cast: A type to cast the result
:returns: A list of the desired type, or strings where the casting fails
"""
raise NameError("Adapter (sub)class has not implemented the "
"values method")
results = str(self.ask(command)).strip()
results = results.split(separator)
for i, result in enumerate(results):
try:
results[i] = cast(result)
except:
pass # Keep as string
return results

def binary_values(self, command, header_bytes=0, dtype=np.float32):
""" Returns a numpy array from a query for binary data
Expand All @@ -81,28 +90,38 @@ def binary_values(self, command, header_bytes=0, dtype=np.float32):


class FakeAdapter(Adapter):
"""The Fake adapter class is provided for debugging purposes,
which returns valid data for each Adapter method"""
"""Provides a fake adapter for debugging purposes,
which bounces back the command so that arbitrary values
testing is possible.
def read(self):
""" Returns a fake string for debugging purposes
"""
return "Fake string!"
.. code-block:: python
def write(self, command):
""" Fakes the writing of a command for debugging purposes
"""
pass
a = FakeAdapter()
assert a.read() == ""
a.write("5")
assert a.read() == "5"
assert a.read() == ""
assert a.ask("10") == "10"
assert a.values("10") == [10]
def values(self, command):
""" Returns a list of fake values for debugging purposes
"""

_buffer = ""

def read(self):
""" Returns the last commands given after the
last read call.
"""
return [1.0, 2.0, 3.0]
result = copy(self._buffer)
# Reset the buffer
self._buffer = ""
return result

def binary_values(self, command, header_bytes=0, dtype=np.float32):
""" Returns a list of fake values in a NumPy array
def write(self, command):
""" Writes the command to a buffer, so that it can
be read back.
"""
return np.array([2, 3, 7, 8, 1], dtype=dtype)
self._buffer += command

def __repr__(self):
return "<FakeAdapter>"
16 changes: 0 additions & 16 deletions pymeasure/adapters/serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,6 @@ def read(self):
"""
return b"\n".join(self.connection.readlines()).decode()

def values(self, command):
""" Writes a command to the instrument and returns a list of formatted
values from the result
:param command: SCPI command to be sent to the instrument
:returns: String ASCII response of the instrument
"""
results = str(self.ask(command)).strip()
results = results.split(',')
for result in results:
try:
result = float(result)
except:
pass # Keep as string
return results

def binary_values(self, command, header_bytes=0, dtype=np.float32):
""" Returns a numpy array from a query for binary data
Expand Down
34 changes: 34 additions & 0 deletions pymeasure/adapters/tests/test_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#
# This file is part of the PyMeasure package.
#
# Copyright (c) 2013-2016 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.adapters import FakeAdapter


def test_adapter_values():
a = FakeAdapter()
assert a.values("5,6,7") == [5, 6, 7]
assert a.values("5,6,7", cast=str) == ['5', '6', '7']
assert a.values("X,Y,Z") == ['X', 'Y', 'Z']
assert a.values("X,Y,Z", cast=str) == ['X', 'Y', 'Z']
assert a.values("X.Y.Z", separator='.') == ['X', 'Y', 'Z']
20 changes: 0 additions & 20 deletions pymeasure/adapters/visa.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,6 @@ def read(self):
:returns: String ASCII response of the instrument.
"""
return self.connection.read()

def values(self, command, separator = ','):
""" Writes a command to the instrument and returns a list of numerical
values from the result.
:param command: SCPI command to be sent to the instrument.
:returns: A list of numerical values.
"""
results = str(self.ask(command)).strip()
results = results.split(separator)

def try_float(num):
""" Try to convert to float """
try:
num = float(num)
except:
pass # Keep as string
return num

return [try_float(result) for result in results]

def binary_values(self, command, header_bytes=0, dtype=np.float32):
""" Returns a numpy array from a query for binary data
Expand Down
14 changes: 6 additions & 8 deletions pymeasure/instruments/danfysik/danfysik8500.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class Danfysik8500(Instrument):
The device will be accessible through the port :code:`/dev/danfysik`.
"""

id = Instrument.measurement(
"PRINT", """ Reads the idenfitication information. """
)

def __init__(self, port):
super(Danfysik8500, self).__init__(
DanfysikAdapter(port),
Expand All @@ -62,12 +66,6 @@ def __init__(self, port):
self.write("ERRT") # Use text error messages
self.write("UNLOCK") # Unlock from remote or local mode

@property
def id(self):
""" The identification of the instrument.
"""
return self.ask("PRINT")

def local(self):
""" Sets the instrument in local mode, where the front
panel can be used.
Expand All @@ -93,7 +91,7 @@ def polarity(self, value):
polarity = "+" if value > 0 else "-"
self.write("PO %s" % polarity)

def resetInterlocks(self):
def reset_interlocks(self):
""" Resets the instrument interlocks.
"""
self.write("RS")
Expand All @@ -108,7 +106,7 @@ def disable(self):
"""
self.write("F")

def isEnabled(self):
def is_enabled(self):
""" Returns True if the current supply is enabled.
"""
return self.status_hex & 0x800000 == 0
Expand Down
88 changes: 42 additions & 46 deletions pymeasure/instruments/fwbell/fwbell5080.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#

from pymeasure.instruments import Instrument, RangeException
from pymeasure.instruments.validators import truncated_discrete_set
from pymeasure.adapters import SerialAdapter
from numpy import array, float64

Expand All @@ -35,17 +36,42 @@ class FWBell5080(Instrument):
:param port: The serial port of the instrument
"""

id = Instrument.measurement(
"*IDN?", """ Reads the idenfitication information. """
)
units = Instrument.measurement(
":UNIT:FLUX?",
""" Reads the units being used
"""
)
range = Instrument.control(
":SENS:FLUX:RANG?", ":SENS:FLUX:RANG %d",
""" A floating point property that controls the maximum field
range in Gauss, which can take the values 300, 3,000, and
30,000 G. Values outside these are truncated to the closest
valid value. This property can be set. """,
validator=truncated_discrete_set,
values={300:0, 3000:1, 30000:2},
map_values=True
)

def __init__(self, port):
super(FWBell5080, self).__init__(
SerialAdapter(port, 2400, timeout=0.5),
"F.W. Bell 5080 Handheld Gaussmeter"
)

@property
def id(self):
""" The identification of the instrument.
def read(self):
""" Overwrites the standard read method to remove the last
2 characters from the output
"""
return super(FWBell5080, self).read()[:-2]

def ask(self):
""" Overwrites the standard ask method to remove the last
2 characters from the output
"""
return self.ask("*IDN?")
return super(FWBell5080, self).ask()[:-2]

def reset(self):
""" Resets the instrument.
Expand All @@ -60,7 +86,7 @@ def measure(self, averages=1):
:param averages: The number of averages to preform
"""
if averages == 1:
value = self.ask(":MEAS:FLUX?")[:-2]
value = self.ask(":MEAS:FLUX?")
if value[-1] == "G": # Field in gauss
return (float(value[:-1]), 0)
elif value[-1] == "T": # Field in tesla
Expand All @@ -74,52 +100,22 @@ def measure(self, averages=1):
data = array(data, dtype=float64)
return (data.mean(), data.std())

def set_DC_gauss_units(self):
""" Sets the meter to measure DC field in Gauss. """
def use_DC_gauss(self):
""" Sets the meter to measure DC fields in Gauss. """
self.write(":UNIT:FLUX:DC:GAUS")

def set_DC_tesla_units(self):
""" Sets the meter to measure DC field in Tesla. """
self.write(":UNIT:FLUX:DC:TESL")

def set_AC_gauss_units(self):
""" Sets the meter to measure AC field in Gauss. """
def use_AC_gauss(self):
""" Sets the meter to measure AC fields in Gauss. """
self.write(":UNIT:FLUX:AC:GAUS")

def set_AC_tesla_units(self):
""" Sets the meter to measure AC field in Telsa. """
self.write(":UNIT:FLUX:AC:TESL")
def use_DC_tesla(self):
""" Sets the meter to measure DC fields in Tesla. """
self.write(":UNIT:FLUX:DC:TESL")

def get_units(self):
""" Returns the units being used. """
return self.ask(":UNIT:FLUX?")[:-2]
def use_AC_tesla(self):
""" Sets the meter to measure AC fields in Tesla. """
self.write(":UNIT:FLUX:AC:TESL")

def set_auto_range(self):
def auto_range(self):
""" Enables the auto range functionality. """
self.write(":SENS:FLUX:RANG:AUTO")

def set_range(self, max_gauss):
""" Manually sets the range in Gauss and truncates to
an allowed range value
:param max_gauss: The maximum field of the range in Gauss
"""
if max_gauss < 3e2:
self.write(":SENS:FLUX:RANG 0")
elif max_gauss < 3e3:
self.write(":SENS:FLUX:RANG 1")
elif max_gauss < 3e4:
self.write(":SENS:FLUX:RANG 2")
else:
raise RangeException("F.W. Bell 5080 is not capable of field "
"measurements above 30 kGauss")

def get_range(self):
""" Returns the maximum field of the range in Gauss """
value = int(self.ask(":SENS:FLUX:RANG?")[:-2])
if value == 0:
return 3e2
elif value == 1:
return 3e3
elif value == 2:
return 3e4
Loading

0 comments on commit ff47bdb

Please sign in to comment.