Source code for instruments.qubitekk.cc1

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Provides support for the Qubitekk CC1 Coincidence Counter instrument.

CC1 Class originally contributed by Catherine Holloway.
"""

# IMPORTS #####################################################################

from __future__ import absolute_import
from __future__ import division
from builtins import range, map

from enum import Enum
import quantities as pq

from instruments.generic_scpi.scpi_instrument import SCPIInstrument
from instruments.util_fns import (
    ProxyList, assume_units, split_unit_str
)

# CLASSES #####################################################################


[docs]class CC1(SCPIInstrument): """ The CC1 is a hand-held coincidence counter. It has two setting values, the dwell time and the coincidence window. The coincidence window determines the amount of time (in ns) that the two detections may be from each other and still be considered a coincidence. The dwell time is the amount of time that passes before the counter will send the clear signal. More information can be found at : http://www.qubitekk.com """ def __init__(self, filelike): super(CC1, self).__init__(filelike) self.terminator = "\n" self._channel_count = 3 self._firmware = None self._ack_on = False self.sendcmd(":ACKN OF") # a readline is required because if the firmware is prior to 2.2, # the cc1 will respond with 'Unknown Command'. After # 2.2, it will either respond by acknowledging the command (turning # acknowledgements off does not take place until after the current # exchange has been completed), or not acknowledging it (if the # acknowledgements are off). The try/except block is required to # handle the case in which acknowledgements are off. try: self.read(-1) except OSError: pass _ = self.firmware # prime the firmware if self.firmware[0] >= 2 and self.firmware[1] > 1: self._bool = ("ON", "OFF") self._set_fmt = ":{}:{}" self.TriggerMode = self._TriggerModeNew else: self._bool = ("1", "0") self._set_fmt = ":{} {}" self.TriggerMode = self._TriggerModeOld def _ack_expected(self, msg=""): return msg if self._ack_on and self.firmware[0] >= 2 and \ self.firmware[1] > 1 else None # ENUMS # class _TriggerModeNew(Enum): """ Enum containing valid trigger modes for the CC1 """ continuous = "MODE CONT" start_stop = "MODE STOP" class _TriggerModeOld(Enum): """ Enum containing valid trigger modes for the CC1 """ continuous = "0" start_stop = "1" # INNER CLASSES #
[docs] class Channel(object): """ Class representing a channel on the Qubitekk CC1. """ __CHANNEL_NAMES = { 1: 'C1', 2: 'C2', 3: 'CO' } def __init__(self, cc1, idx): self._cc1 = cc1 # Use zero-based indexing for the external API, but one-based # for talking to the instrument. self._idx = idx + 1 self._chan = self.__CHANNEL_NAMES[self._idx] self._count = 0 # PROPERTIES # @property def count(self): """ Gets the counts of this channel. :rtype: `int` """ count = self._cc1.query("COUN:{0}?".format(self._chan)) # FIXME: Does this property actually work? The try block seems # wrong. try: count = int(count) except ValueError: # pragma: no cover count = None while count is None: # try to read again try: count = int(self._cc1.read(-1)) except ValueError: count = None self._count = count return self._count
# PROPERTIES # @property def acknowledge(self): """ Gets/sets the acknowledge message state. If True, the CC1 will echo back every command sent, then print the response (either Unable to comply, Unknown command or the response to a query). If False, the CC1 will only print the response. :units: None :type: boolean """ return self._ack_on @acknowledge.setter def acknowledge(self, new_val): if self.firmware[0] >= 2 and self.firmware[1] > 1: if self._ack_on and not new_val: self.sendcmd(":ACKN OF") self._ack_on = False elif not self._ack_on and new_val: self.sendcmd(":ACKN ON") self._ack_on = True else: raise NotImplementedError("Acknowledge message not implemented in " "this version.") @property def gate(self): """ Gets/sets the gate enable status :type: `bool` """ return self.query("GATE?").strip() == self._bool[0] @gate.setter def gate(self, newval): if not isinstance(newval, bool): raise TypeError("Bool properties must be specified with a " "boolean value") self.sendcmd( self._set_fmt.format("GATE", self._bool[0] if newval else self._bool[1]) ) @property def subtract(self): """ Gets/sets the subtract enable status :type: `bool` """ return self.query("SUBT?").strip() == self._bool[0] @subtract.setter def subtract(self, newval): if not isinstance(newval, bool): raise TypeError("Bool properties must be specified with a " "boolean value") self.sendcmd( self._set_fmt.format("SUBT", self._bool[0] if newval else self._bool[1]) ) @property def trigger_mode(self): """ Gets/sets the trigger mode setting for the CC1. This can be set to ``continuous`` or ``start/stop`` modes. :type: `CC1.TriggerMode` """ return self.TriggerMode(self.query("TRIG?").strip()) @trigger_mode.setter def trigger_mode(self, newval): try: # First assume newval is Enum.value newval = self.TriggerMode[newval] except KeyError: # Check if newval is Enum.name instead try: newval = self.TriggerMode(newval) except ValueError: raise ValueError("Enum property new value not in enum.") self.sendcmd(self._set_fmt.format("TRIG", self.TriggerMode(newval).value)) @property def window(self): """ Gets/sets the length of the coincidence window between the two signals. :units: As specified (if a `~quantities.Quantity`) or assumed to be of units nanoseconds. :type: `~quantities.Quantity` """ return pq.Quantity(*split_unit_str(self.query("WIND?"), "ns")) @window.setter def window(self, new_val): new_val_mag = int(assume_units(new_val, pq.ns).rescale(pq.ns).magnitude) if new_val_mag < 0 or new_val_mag > 7: raise ValueError("Window is out of range.") # window must be an integer! self.sendcmd(":WIND {}".format(new_val_mag)) @property def delay(self): """ Get/sets the delay value (in nanoseconds) on Channel 1. When setting, ``N`` may be ``0, 2, 4, 6, 8, 10, 12, or 14ns``. :rtype: quantities.ns :return: the delay value """ return pq.Quantity(*split_unit_str(self.query("DELA?"), "ns")) @delay.setter def delay(self, new_val): new_val = assume_units(new_val, pq.ns).rescale(pq.ns) if new_val < 0*pq.ns or new_val > 14*pq.ns: raise ValueError("New delay value is out of bounds.") if new_val.magnitude % 2 != 0: raise ValueError("New magnitude must be an even number") self.sendcmd(":DELA "+str(int(new_val.magnitude))) @property def dwell_time(self): """ Gets/sets the length of time before a clear signal is sent to the counters. :units: As specified (if a `~quantities.Quantity`) or assumed to be of units seconds. :type: `~quantities.Quantity` """ # the older versions of the firmware erroneously report the units of the # dwell time as being seconds rather than ms dwell_time = pq.Quantity(*split_unit_str(self.query("DWEL?"), "s")) if self.firmware[0] <= 2 and self.firmware[1] <= 1: return dwell_time/1000.0 return dwell_time @dwell_time.setter def dwell_time(self, new_val): new_val_mag = assume_units(new_val, pq.s).rescale(pq.s).magnitude if new_val_mag < 0: raise ValueError("Dwell time cannot be negative.") self.sendcmd(":DWEL {}".format(new_val_mag)) @property def firmware(self): """ Gets the firmware version :rtype: `tuple`(Major:`int`, Minor:`int`, Patch`int`) """ # the firmware is assumed not to change while the device is active # firmware is stored locally as it will be gotten often # pylint: disable=no-member if self._firmware is None: while self._firmware is None: self._firmware = self.query("FIRM?") if self._firmware.find("Unknown") >= 0: self._firmware = None else: value = self._firmware.replace("Firmware v", "").split(".") if len(value) < 3: for _ in range(3-len(value)): value.append(0) value = tuple(map(int, value)) self._firmware = value return self._firmware @property def channel(self): """ Gets a specific channel object. The desired channel is specified like one would access a list. For instance, this would print the counts of the first channel:: >>> cc = ik.qubitekk.CC1.open_serial('COM8', 19200, timeout=1) >>> print(cc.channel[0].count) :rtype: `CC1.Channel` """ return ProxyList(self, CC1.Channel, range(self._channel_count)) # METHODS #
[docs] def clear_counts(self): """ Clears the current total counts on the counters. """ self.sendcmd("CLEA")