Skip to content

Commit

Permalink
Merge pull request #226 from qdev-dk/feature/raw-comm
Browse files Browse the repository at this point in the history
Raw communication channel access
  • Loading branch information
alexcjohnson authored Jun 14, 2016
2 parents 2331ac3 + 982c143 commit 2ccea6c
Show file tree
Hide file tree
Showing 8 changed files with 1,126 additions and 558 deletions.
486 changes: 251 additions & 235 deletions qcodes/instrument/base.py

Large diffs are not rendered by default.

160 changes: 124 additions & 36 deletions qcodes/instrument/ip.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
"""Ethernet instrument driver class based on sockets."""
import socket

from .base import Instrument


class IPInstrument(Instrument):
'''
Bare socket ethernet instrument implementation
name: what this instrument is called locally
address: the IP address or domain name, as a string
port: the IP port, as an integer
(address and port can be set later with set_address)
timeout: seconds to allow for responses (default 5)
(can be set later with set_timeout)
terminator: character(s) to terminate each send with (default '\n')
(can be set later with set_terminator)
persistent: do we leave the socket open between calls? (default True)
write_confirmation: does the instrument acknowledge writes with some
response we can read? (default True)
'''

r"""
Bare socket ethernet instrument implementation.
Args:
name (str): What this instrument is called locally.
address (Optional[str]): The IP address or name. If not given on
construction, must be provided before any communication.
port (Optional[int]): The IP port. If not given on construction, must
be provided before any communication.
timeout (number): Seconds to allow for responses. Default 5.
terminator (str): Character(s) to terminate each send. Default '\n'.
persistent (bool): Whether to leave the socket open between calls.
Default True.
write_confirmation (bool): Whether the instrument acknowledges writes
with some response we should read. Default True.
server_name (str): Name of the InstrumentServer to use. Defaults to
'IPInstruments'.
Use ``None`` to run locally - but then this instrument will not
work with qcodes Loops or other multiprocess procedures.
metadata (Optional[Dict]): additional static metadata to add to this
instrument's JSON snapshot.
See help for ``qcodes.Instrument`` for additional information on writing
instrument subclasses.
"""

def __init__(self, name, address=None, port=None, timeout=5,
terminator='\n', persistent=True, write_confirmation=True,
**kwargs):
Expand All @@ -38,9 +60,25 @@ def __init__(self, name, address=None, port=None, timeout=5,

@classmethod
def default_server_name(cls, **kwargs):
"""
Get the default server name for this instrument.
Args:
**kwargs: All the kwargs supplied in the constructor.
Returns:
str: By default all IPInstruments go on the server 'IPInstruments'.
"""
return 'IPInstruments'

def set_address(self, address=None, port=None):
"""
Change the IP address and/or port of this instrument.
Args:
address (Optional[str]): The IP address or name.
port (Optional[number]): The IP port.
"""
if address is not None:
self._address = address
elif not hasattr(self, '_address'):
Expand All @@ -56,6 +94,12 @@ def set_address(self, address=None, port=None):
self.set_persistent(self._persistent)

def set_persistent(self, persistent):
"""
Change whether this instrument keeps its socket open between calls.
Args:
persistent (bool): Set True to keep the socket open all the time.
"""
self._persistent = persistent
if persistent:
self._connect()
Expand All @@ -79,13 +123,25 @@ def _disconnect(self):
self._socket = None

def set_timeout(self, timeout=None):
if timeout is not None:
self._timeout = timeout
"""
Change the read timeout for the socket.
Args:
timeout (number): Seconds to allow for responses.
"""
self._timeout = timeout

if self._socket is not None:
self.socket.settimeout(float(self.timeout))
self._socket.settimeout(float(self._timeout))

def set_terminator(self, terminator):
r"""
Change the write terminator to use.
Args:
terminator (str): Character(s) to terminate each send.
Default '\n'.
"""
self._terminator = terminator

def _send(self, cmd):
Expand All @@ -96,29 +152,48 @@ def _recv(self):
return self._socket.recv(512).decode()

def close(self):
"""Disconnect and irreversibly tear down the instrument."""
self._disconnect()
super().close()

def write(self, cmd):
try:
with self._ensure_connection:
self._send(cmd)
if self._confirmation:
self._recv()
except Exception as e:
e.args = e.args + ('writing ' + repr(cmd) + ' to ' + repr(self),)
raise e

def ask(self, cmd):
try:
with self._ensure_connection:
self._send(cmd)
return self._recv()
except Exception as e:
e.args = e.args + ('asking ' + repr(cmd) + ' to ' + repr(self),)
raise e
def write_raw(self, cmd):
"""
Low-level interface to send a command that gets no response.
Args:
cmd (str): The command to send to the instrument.
"""

with self._ensure_connection:
self._send(cmd)
if self._confirmation:
self._recv()

def ask_raw(self, cmd):
"""
Low-level interface to send a command an read a response.
Args:
cmd (str): The command to send to the instrument.
Returns:
str: The instrument's response.
"""
with self._ensure_connection:
self._send(cmd)
return self._recv()

def snapshot_base(self, update=False):
"""
State of the instrument as a JSON-compatible dict.
Args:
update (bool): If True, update the state by querying the
instrument. If False, just use the latest values in memory.
Returns:
dict: base snapshot
"""
snap = super().snapshot_base(update=update)

snap['port'] = self._port
Expand All @@ -132,13 +207,26 @@ def snapshot_base(self, update=False):


class EnsureConnection:

"""
Context manager to ensure an instrument is connected when needed.
Uses ``instrument._persistent`` to determine whether or not to close
the connection immediately on completion.
Args:
instrument (IPInstrument): the instance to connect.
"""

def __init__(self, instrument):
self._instrument = instrument

def __enter__(self):
"""Make sure we connect when entering the context."""
if not self.instrument._persistent or self.instrument._socket is None:
self.instrument._connect()

def __exit__(self):
"""Possibly disconnect on exiting the context."""
if not self.instrument._persistent:
self.instrument._disconnect()
Loading

0 comments on commit 2ccea6c

Please sign in to comment.