Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fbcdd2d
Added environment.yml file
Aug 7, 2017
3757cdc
Added requirements to environment.yml
Aug 7, 2017
d4c8ed1
Added requirements to environment.yml
Aug 7, 2017
1397535
Works under linux now
sohailc Aug 7, 2017
2888633
1) added american instrument driver from the main qcodes repo
Aug 8, 2017
0927761
the mock IP instrument now remembers the set point
Aug 8, 2017
478d8ec
added test for spherical coordinates. Making test for cylindrical coo…
Aug 8, 2017
f726a31
Added a module for handy converting between spherical, cartesian and …
Aug 10, 2017
ef19eff
additional work
Aug 10, 2017
c0095af
resolved merge conflict
Aug 10, 2017
c591196
1) The driver is adhering to the qcodes specs now
Aug 11, 2017
eb2ab7d
1) The driver is adhering to the qcodes specs now
Aug 14, 2017
9f321c3
1) Made tests to ensure that the set points correctly reach the instr…
Aug 14, 2017
cc6073d
Added a test to verify that ramp downs are performed first
Aug 15, 2017
4f0fd7b
Merge branch 'master' of git://github.com/qcodes/qcodes
Aug 15, 2017
5838d0d
Added the ability to define more complex regions for safe set points.…
Aug 15, 2017
1ef9f8e
Re-wrote the instrument mocker so we have a cleaner design.
Aug 17, 2017
f245439
Merge branch 'master' of git://github.com/qcodes/qcodes
Aug 17, 2017
dd0aee0
changing how mocking works
Aug 17, 2017
ccabe4e
Mocking no longer requires sockets and threading. Major rewrite of th…
Aug 18, 2017
d69583c
Mocking no longer requires sockets and threading. Major rewrite of th…
Aug 18, 2017
590925e
Merge branch 'master' of git://github.com/qcodes/qcodes
Aug 18, 2017
14f683e
further clean up of the mocking mechanism.
Aug 18, 2017
013c38b
Solved a bug which raised a value error when asking for spherical coo…
Aug 23, 2017
b2f7203
Merge branch 'master' of git://github.com/qcodes/qcodes
Aug 23, 2017
f52052d
1) Added tests for the field vector
Aug 24, 2017
6423afa
Added a notebook showing the results of the testing of the new AMI430…
Aug 24, 2017
0809e6d
* Processed Jens comments, except changing the doc string format and …
Aug 30, 2017
1c93c8e
Merge branch 'master' of git://github.com/qcodes/qcodes
Aug 30, 2017
77a55e8
Processed the rest of Jens comments
Aug 31, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

454 changes: 454 additions & 0 deletions docs/examples/driver_examples/Qcodes example with AMI430.ipynb

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: qcodes
channels:
- defaults
- conda-forge
dependencies:
- h5py=2.6.0
- matplotlib=2.0.2
- pyqtgraph
- python=3.6
- jsonschema
- pyvisa
- QtPy=1.2.0
- sip
- pyqt=5.6.0
- numpy
- jupyter=1.0.0
- typing
- hypothesis
- pytest
- pytest-runner
64 changes: 50 additions & 14 deletions qcodes/instrument/base.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
"""Instrument base class."""
import logging
import numpy as np
import time
import warnings
import weakref
from typing import Sequence

from qcodes.utils.metadata import Metadatable
import numpy as np

from qcodes.utils.helpers import DelegateAttributes, strip_attrs, full_class
from qcodes.utils.metadata import Metadatable
from qcodes.utils.validators import Anything
from .parameter import StandardParameter
from .function import Function
from .parameter import StandardParameter


class InstrumentBase(Metadatable, DelegateAttributes):
Expand Down Expand Up @@ -38,13 +39,38 @@ class InstrumentBase(Metadatable, DelegateAttributes):
such as channel lists or logical groupings of parameters.
Usually populated via ``add_submodule``
"""
def __init__(self, name, **kwargs):

def __init__(self, name, testing=False, **kwargs):
self.name = str(name)
self._testing = testing

if testing:
if hasattr(type(self), "mocker_class"):
mocker_class = type(self).mocker_class
self.mocker = mocker_class(name)
else:
raise ValueError("Testing turned on but no mocker class defined")

self.parameters = {}
self.functions = {}
self.submodules = {}
super().__init__(**kwargs)

def is_testing(self):
"""Return True if we are testing"""
return self._testing

def get_mock_messages(self):
"""
For testing purposes we might want to get log messages from the mocker.

Returns:
mocker_messages: list, str
"""
if not self._testing:
raise ValueError("Cannot get mock messages if not in testing mode")
return self.mocker.get_log_messages()

def add_parameter(self, name, parameter_class=StandardParameter,
**kwargs):
"""
Expand Down Expand Up @@ -150,12 +176,13 @@ def snapshot_base(self, update: bool=False,
Returns:
dict: base snapshot
"""
snap = {'functions': dict((name, func.snapshot(update=update))
for name, func in self.functions.items()),
'submodules': dict((name, subm.snapshot(update=update))
for name, subm in self.submodules.items()),
'__class__': full_class(self),
}

snap = {
"functions": {name: func.snapshot(update=update) for name, func in self.functions.items()},
"submodules": {name: subm.snapshot(update=update) for name, subm in self.submodules.items()},
"__class__": full_class(self)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have not been enforcing it but we have been trying to move towards pep8 and <79 char per line

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not exactly understand why this is giving me pep8 issues. My pycharm editor also complains about this line, but I do not understand why.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line is longer than 79 characters now? no. The wrapping was to make it shorter

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is complaining that the dictionary can be re-written as a dict literal. So literal:

some_dict = {"A": 1, "B": 2}

vs.

dict(A=1, B=1).

But as far as I can see the dictionary is literal, so I do not understand the complaint.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This link has some explaination: link, but I do not understand how this applies here. Anyway, I do not think this issue is very important and I propose to ignore it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that seems wrong to me. I think its safe to ignore that


snap['parameters'] = {}
for name, param in self.parameters.items():
update = update
Expand Down Expand Up @@ -335,12 +362,12 @@ class Instrument(InstrumentBase):

_all_instruments = {}

def __init__(self, name, **kwargs):
def __init__(self, name, testing=False, **kwargs):
self._t0 = time.time()
if kwargs.pop('server_name', False):
warnings.warn("server_name argument not supported any more",
stacklevel=0)
super().__init__(name, **kwargs)
super().__init__(name, testing=testing, **kwargs)

self.add_parameter('IDN', get_cmd=self.get_idn,
vals=Anything())
Expand Down Expand Up @@ -576,7 +603,10 @@ def write(self, cmd):
including the command and the instrument.
"""
try:
self.write_raw(cmd)
if self._testing:
self.mocker.write(cmd)
else:
self.write_raw(cmd)
except Exception as e:
e.args = e.args + ('writing ' + repr(cmd) + ' to ' + repr(self),)
raise e
Expand Down Expand Up @@ -615,7 +645,13 @@ def ask(self, cmd):
including the command and the instrument.
"""
try:
return self.ask_raw(cmd)
if self._testing:
answer = self.mocker.ask(cmd)
else:
answer = self.ask_raw(cmd)

return answer

except Exception as e:
e.args = e.args + ('asking ' + repr(cmd) + ' to ' + repr(self),)
raise e
Expand Down
11 changes: 9 additions & 2 deletions qcodes/instrument/ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ class IPInstrument(Instrument):
"""

def __init__(self, name, address=None, port=None, timeout=5,
terminator='\n', persistent=True, write_confirmation=True,
terminator='\n', persistent=True, write_confirmation=True, testing=False,
**kwargs):
super().__init__(name, **kwargs)
super().__init__(name, testing=testing, **kwargs)

self._address = address
self._port = port
Expand Down Expand Up @@ -88,7 +88,14 @@ def set_persistent(self, persistent):
else:
self._disconnect()

def flush_connection(self):
if not self._testing:
self._recv()

def _connect(self):
if self._testing:
return

if self._socket is not None:
self._disconnect()

Expand Down
Empty file.
217 changes: 217 additions & 0 deletions qcodes/instrument/mockers/ami430.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import re
import time
from datetime import datetime


class MockAMI430:
states = {
"RAMPING to target field/current": "1",
"HOLDING at the target field/current": "2",
"PAUSED": "3",
"Ramping in MANUAL UP mode": "4",
"Ramping in MANUAL DOWN mode": "5",
"ZEROING CURRENT (in progress)": "6",
"Quench detected": "7",
"At ZERO current": "8",
"Heating persistent switch": "9",
"Cooling persistent switch": "10"
}

field_units = {
"tesla": "1",
"kilogauss": "0"
}

ramp_rate_units = {
"A/s": "0",
"A/min": "1"
}

quench_state = {False: "0", True: "1"}

def __init__(self, name):

self.name = name
self.log_messages = []

self._field_mag = 0
self._field_target = 0
self._state = MockAMI430.states["HOLDING at the target field/current"]

self.handlers = {
"RAMP:RATE:UNITS": {
"get": MockAMI430.ramp_rate_units["A/s"],
"set": None
},
"FIELD:UNITS": {
"get": MockAMI430.field_units["tesla"],
"set": None
},
"*IDN": {
"get": "v0.1 Mock",
"set": None
},
"STATE": {
"get": self._getter("_state"),
"set": self._setter("_state")
},
"FIELD:MAG": {
"get": self._getter("_field_mag"),
"set": None
},
"QU": {
"get": MockAMI430.quench_state[False], # We are never in a quenching state so always return the
# same value
"set": None
},
"PERS": {
"get": "0",
"set": None
},
"PAUSE": {
"get": self._is_paused,
"set": self._do_pause
},
"CONF:FIELD:TARG": {
"get": None, # To get the field target, send a message "FIELD:TARG?"
"set": self._setter("_field_target")
},
"FIELD:TARG": {
"get": self._getter("_field_target"),
"set": None
},
"PS": {
"get": "0", # The heater is off
"set": None
},
"RAMP": {
"set": self._do_ramp,
"get": None
},
"RAMP:RATE:CURRENT": {
"get": "0.1000,50.0000",
"set": None
},
"COIL": {
"get": "1",
"set": None
}
}

@staticmethod
def message_parser(gs, msg_str, key):
"""
* If gs = "get":
Let suppose key = "RAMP:RATE:UNITS", then if we get msg_str = "RAMP:RATE:UNITS?" then match will be True and
args = None. If msg_str = "RAMP:RATE:UNITS:10?" then match = True and args = "10". On the other hand if
key = "RAMP" then both "RAMP:RATE:UNITS?" and "RAMP:RATE:UNITS:10?" will cause match to be False

* If gs = "set"
If key = "STATE" and msg_str = "STATE 2,1" then match = True and args = "2,1". If key="STATE" and
msg_str = STATE:ELSE 2,1 then match is False.

Consult [1] for a complete description of the AMI430 protocol.

[1] http://www.americanmagnetics.com/support/manuals/mn-4Q06125PS-430.pdf

Parameters:
gs (string): "get", or "set"
msg_str (string): the message string the mock instrument gets.
key (string): one of the keys in self.handlers

Returns:
match (bool): if the key and the msg_str match, then match = True
args (string): if any arguments are present in the message string these will be passed along. This is
always None when match = False

"""
if msg_str == key: # If the message string matches a key exactly we have a match with no arguments
return True, None

# We use regular expressions to find out if the message string and the key match. We need to replace reserved
# regular expression characters in the key. For instance replace "*IDN" with "\*IDN".
reserved_re_characters = "\^${}[]().*+?|<>-&"
for c in reserved_re_characters:
key = key.replace(c, "\{}".format(c))

s = {"get": "(:[^:]*)?\?$", "set": "([^:]+)"}[gs] # Get and set messages use different regular expression
# patterns to determine a match
search_string = "^" + key + s
r = re.search(search_string, msg_str)
match = r is not None

args = None
if match:
args = r.groups()[0]
if args is not None:
args = args.strip(":")

return match, args

def _getter(self, attribute):
return lambda _: getattr(self, attribute)

def _setter(self, attribute):
return lambda value: setattr(self, attribute, value)

def _log(self, msg):

now = datetime.now()
log_line = "[{}] {}: {}".format(now.strftime("%d:%m:%Y-%H:%M:%S.%f"), self.name, msg)
self.log_messages.append(log_line)

def _handle_messages(self, msg):
"""
Parameters:
msg (string): a message received through the socket communication layer

Returns:
rval (string or None): If the type of message requests a value (a get message) then this value is returned
by this function. A set message will return a None value.
"""

gs = {True: "get", False: "set"}[msg.endswith("?")] # A "get" message ends with a "?" and will invoke the get
# part of the handler defined in self.handlers.

rval = None
handler = None

for key in self.handlers: # Find which handler is suitable to handle the message
match, args = MockAMI430.message_parser(gs, msg, key)
if not match:
continue

handler = self.handlers[key][gs]
if callable(handler):
rval = handler(args)
else:
rval = handler

break

if handler is None:
self._log("Command {} unknown".format(msg))

return rval

def _do_pause(self, _):
self._state = MockAMI430.states["PAUSED"]

def _is_paused(self):
return self._state == MockAMI430.states["PAUSED"]

def _do_ramp(self, _):
self._log("Ramping to {}".format(self._field_target))
self._state = MockAMI430.states["RAMPING to target field/current"]
time.sleep(0.1) # Lets pretend to be ramping for a bit
self._field_mag = self._field_target
self._state = MockAMI430.states["HOLDING at the target field/current"]

def get_log_messages(self):
return self.log_messages

def ask(self, msg):
return self._handle_messages(msg)

def write(self, msg):
self._handle_messages(msg)
Loading