mirror of https://github.com/m-labs/artiq.git
move lda out of tree (#887)
This commit is contained in:
parent
d4781e9a8a
commit
eaec519ac8
|
@ -1,229 +0,0 @@
|
||||||
import logging
|
|
||||||
import ctypes
|
|
||||||
import struct
|
|
||||||
|
|
||||||
from artiq.language.units import dB
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("lda")
|
|
||||||
|
|
||||||
|
|
||||||
class HidError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Ldasim:
|
|
||||||
"""Lab Brick Digital Attenuator simulation driver."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._attenuation = None
|
|
||||||
self._att_max = 63*dB
|
|
||||||
self._att_step_size = 0.25*dB
|
|
||||||
|
|
||||||
def get_att_max(self):
|
|
||||||
return self._att_max
|
|
||||||
|
|
||||||
def get_att_step_size(self):
|
|
||||||
return self._att_step_size
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Close the device."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_attenuation(self):
|
|
||||||
"""Reads last attenuation value set to the simulated device.
|
|
||||||
|
|
||||||
:return: Returns the attenuation value in dB, or None if it was
|
|
||||||
never set.
|
|
||||||
:rtype: float
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._attenuation
|
|
||||||
|
|
||||||
def set_attenuation(self, attenuation):
|
|
||||||
"""Stores the new attenuation value.
|
|
||||||
|
|
||||||
:param attenuation: The attenuation value in dB.
|
|
||||||
"""
|
|
||||||
|
|
||||||
step = self.get_att_step_size()
|
|
||||||
att = round(attenuation/step)*step
|
|
||||||
|
|
||||||
if att > self.get_att_max():
|
|
||||||
raise ValueError("Cannot set attenuation {} > {}"
|
|
||||||
.format(att, self.get_att_max()))
|
|
||||||
elif att < 0*dB:
|
|
||||||
raise ValueError("Cannot set attenuation {} < 0".format(att))
|
|
||||||
else:
|
|
||||||
att = round(att*4)/4. * dB
|
|
||||||
self._attenuation = att
|
|
||||||
|
|
||||||
def ping(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class Lda:
|
|
||||||
"""Lab Brick Digital Attenuator driver.
|
|
||||||
|
|
||||||
This driver depends on the hidapi library.
|
|
||||||
|
|
||||||
On Linux you should install hidapi-libusb shared library in a directory
|
|
||||||
listed in your LD_LIBRARY_PATH or in the conventional places (/usr/lib,
|
|
||||||
/lib, /usr/local/lib). This can be done either from hidapi sources
|
|
||||||
or by installing the libhidapi-libusb0 binary package on Debian-like OS.
|
|
||||||
|
|
||||||
On Windows you should put hidapi.dll shared library in the
|
|
||||||
artiq\\\\devices\\\\lda folder.
|
|
||||||
|
|
||||||
"""
|
|
||||||
_vendor_id = 0x041f
|
|
||||||
_product_ids = {
|
|
||||||
"LDA-102": 0x1207,
|
|
||||||
"LDA-602": 0x1208,
|
|
||||||
"LDA-302P-1": 0x120E,
|
|
||||||
}
|
|
||||||
_att_max = {
|
|
||||||
"LDA-102": 63*dB,
|
|
||||||
"LDA-602": 63*dB,
|
|
||||||
"LDA-302P-1": 63*dB
|
|
||||||
}
|
|
||||||
_att_step_size = {
|
|
||||||
"LDA-102": 0.5*dB,
|
|
||||||
"LDA-602": 0.5*dB,
|
|
||||||
"LDA-302P-1": 1.0*dB
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, serial=None, product="LDA-102"):
|
|
||||||
"""
|
|
||||||
:param serial: The serial number.
|
|
||||||
:param product: The product identifier string: LDA-102, LDA-602.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from artiq.devices.lda.hidapi import hidapi
|
|
||||||
self.hidapi = hidapi
|
|
||||||
self.product = product
|
|
||||||
self.serial = serial
|
|
||||||
|
|
||||||
if self.serial is None:
|
|
||||||
self.serial = next(self.enumerate(self.product))
|
|
||||||
self._dev = self.hidapi.hid_open(self._vendor_id,
|
|
||||||
self._product_ids[self.product],
|
|
||||||
self.serial)
|
|
||||||
if not self._dev:
|
|
||||||
raise IOError("Device not found")
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Close the device."""
|
|
||||||
self.hidapi.hid_close(self._dev)
|
|
||||||
|
|
||||||
def get_att_step_size(self):
|
|
||||||
return self._att_step_size[self.product]
|
|
||||||
|
|
||||||
def get_att_max(self):
|
|
||||||
return self._att_max[self.product]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def enumerate(cls, product):
|
|
||||||
from artiq.devices.lda.hidapi import hidapi
|
|
||||||
devs = hidapi.hid_enumerate(cls._vendor_id,
|
|
||||||
cls._product_ids[product])
|
|
||||||
try:
|
|
||||||
dev = devs
|
|
||||||
while dev:
|
|
||||||
yield dev[0].serial
|
|
||||||
dev = dev[0].next
|
|
||||||
finally:
|
|
||||||
hidapi.hid_free_enumeration(devs)
|
|
||||||
|
|
||||||
def _check_error(self, ret):
|
|
||||||
if ret < 0:
|
|
||||||
err = self.hidapi.hid_error(self._dev)
|
|
||||||
raise HidError("{}: {}".format(ret, err))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def write(self, command, length, data=bytes()):
|
|
||||||
"""Writes a command to the Lab Brick device.
|
|
||||||
|
|
||||||
:param command: command ID.
|
|
||||||
:param length: number of meaningful bytes in the data array.
|
|
||||||
:param data: a byte array containing the payload of the command.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 0 is report id/padding
|
|
||||||
buf = struct.pack("BBB6s", 0, command, length, data)
|
|
||||||
res = self._check_error(self.hidapi.hid_write(self._dev, buf,
|
|
||||||
len(buf)))
|
|
||||||
if res != len(buf):
|
|
||||||
raise IOError
|
|
||||||
|
|
||||||
def set(self, command, data):
|
|
||||||
"""Sends a SET command to the Lab Brick device.
|
|
||||||
|
|
||||||
:param command: command ID, must have most significant bit set.
|
|
||||||
:param data: payload of the command.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
raise ValueError("Data is empty")
|
|
||||||
if not (command & 0x80):
|
|
||||||
raise ValueError("Set commands must have most significant bit set")
|
|
||||||
self.write(command, len(data), data)
|
|
||||||
|
|
||||||
def get(self, command, length, timeout=1000):
|
|
||||||
"""Sends a GET command to read back some value of the Lab Brick device.
|
|
||||||
|
|
||||||
:param command: Command ID, most significant bit must be cleared.
|
|
||||||
:param length: Length of the command, "count" in the datasheet.
|
|
||||||
:param timeout: Timeout of the HID read in ms.
|
|
||||||
:return: Returns the value read from the device.
|
|
||||||
:rtype: bytes
|
|
||||||
"""
|
|
||||||
|
|
||||||
if command & 0x80:
|
|
||||||
raise ValueError("Get commands must not have most significant bit"
|
|
||||||
" set")
|
|
||||||
status = None
|
|
||||||
self.write(command, length)
|
|
||||||
buf = ctypes.create_string_buffer(8)
|
|
||||||
while status != command:
|
|
||||||
res = self._check_error(self.hidapi.hid_read_timeout(self._dev,
|
|
||||||
buf, len(buf), timeout))
|
|
||||||
if res != len(buf):
|
|
||||||
raise IOError
|
|
||||||
status, length, data = struct.unpack("BB6s", buf.raw)
|
|
||||||
data = data[:length]
|
|
||||||
logger.info("%s %s %r", command, length, data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_attenuation(self):
|
|
||||||
"""Reads attenuation value from Lab Brick device.
|
|
||||||
|
|
||||||
:return: Returns the attenuation value in dB.
|
|
||||||
:rtype: float
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (ord(self.get(0x0d, 1))/4.) * dB
|
|
||||||
|
|
||||||
def set_attenuation(self, attenuation):
|
|
||||||
"""Sets attenuation value of the Lab Brick device.
|
|
||||||
|
|
||||||
:param attenuation: Attenuation value in dB.
|
|
||||||
"""
|
|
||||||
|
|
||||||
step = self.get_att_step_size()
|
|
||||||
att = round(attenuation/step)*step
|
|
||||||
|
|
||||||
if att > self.get_att_max():
|
|
||||||
raise ValueError("Cannot set attenuation {} > {}"
|
|
||||||
.format(att, self.get_att_max()))
|
|
||||||
elif att < 0*dB:
|
|
||||||
raise ValueError("Cannot set attenuation {} < 0".format(att))
|
|
||||||
else:
|
|
||||||
self.set(0x8d, bytes([int(round(att*4))]))
|
|
||||||
|
|
||||||
def ping(self):
|
|
||||||
try:
|
|
||||||
self.get_attenuation()
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
return True
|
|
|
@ -1,58 +0,0 @@
|
||||||
import os
|
|
||||||
import atexit
|
|
||||||
import ctypes
|
|
||||||
import ctypes.util
|
|
||||||
|
|
||||||
if "." not in os.environ["PATH"].split(";"):
|
|
||||||
os.environ["PATH"] += ";."
|
|
||||||
dir = os.path.split(__file__)[0]
|
|
||||||
if dir not in os.environ["PATH"].split(";"):
|
|
||||||
os.environ["PATH"] += ";{}".format(dir)
|
|
||||||
|
|
||||||
for n in "hidapi-libusb hidapi-hidraw hidapi".split():
|
|
||||||
path = ctypes.util.find_library(n)
|
|
||||||
if path:
|
|
||||||
break
|
|
||||||
if not path:
|
|
||||||
raise ImportError("no hidapi library found")
|
|
||||||
hidapi = ctypes.CDLL(path)
|
|
||||||
|
|
||||||
|
|
||||||
class HidDeviceInfo(ctypes.Structure):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
HidDeviceInfo._fields_ = [
|
|
||||||
("path", ctypes.c_char_p),
|
|
||||||
("vendor_id", ctypes.c_ushort),
|
|
||||||
("product_id", ctypes.c_ushort),
|
|
||||||
("serial", ctypes.c_wchar_p),
|
|
||||||
("release", ctypes.c_ushort),
|
|
||||||
("manufacturer", ctypes.c_wchar_p),
|
|
||||||
("product", ctypes.c_wchar_p),
|
|
||||||
("usage_page", ctypes.c_ushort),
|
|
||||||
("usage", ctypes.c_ushort),
|
|
||||||
("interface", ctypes.c_int),
|
|
||||||
("next", ctypes.POINTER(HidDeviceInfo)),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
hidapi.hid_enumerate.argtypes = [ctypes.c_ushort, ctypes.c_ushort]
|
|
||||||
hidapi.hid_enumerate.restype = ctypes.POINTER(HidDeviceInfo)
|
|
||||||
hidapi.hid_free_enumeration.argtypes = [ctypes.POINTER(HidDeviceInfo)]
|
|
||||||
hidapi.hid_open.argtypes = [ctypes.c_ushort, ctypes.c_ushort,
|
|
||||||
ctypes.c_wchar_p]
|
|
||||||
hidapi.hid_open.restype = ctypes.c_void_p
|
|
||||||
hidapi.hid_close.argtypes = [ctypes.c_void_p]
|
|
||||||
hidapi.hid_read_timeout.argtypes = [ctypes.c_void_p, ctypes.c_char_p,
|
|
||||||
ctypes.c_size_t, ctypes.c_int]
|
|
||||||
hidapi.hid_read.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t]
|
|
||||||
hidapi.hid_write.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t]
|
|
||||||
hidapi.hid_send_feature_report.argtypes = [ctypes.c_void_p, ctypes.c_char_p,
|
|
||||||
ctypes.c_size_t]
|
|
||||||
hidapi.hid_get_feature_report.argtypes = [ctypes.c_void_p, ctypes.c_char_p,
|
|
||||||
ctypes.c_size_t]
|
|
||||||
hidapi.hid_error.argtypes = [ctypes.c_void_p]
|
|
||||||
hidapi.hid_error.restype = ctypes.c_wchar_p
|
|
||||||
|
|
||||||
atexit.register(hidapi.hid_exit)
|
|
|
@ -1,44 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
from artiq.devices.lda.driver import Lda, Ldasim
|
|
||||||
from artiq.protocols.pc_rpc import simple_server_loop
|
|
||||||
from artiq.tools import *
|
|
||||||
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="ARTIQ controller for the Lab Brick Digital Attenuator")
|
|
||||||
parser.add_argument("-P", "--product", default="LDA-102",
|
|
||||||
help="product type (default: %(default)s)",
|
|
||||||
choices=["LDA-102", "LDA-602"])
|
|
||||||
simple_network_args(parser, 3253)
|
|
||||||
parser.add_argument("-d", "--device", default=None,
|
|
||||||
help="USB serial number of the device. "
|
|
||||||
"The serial number is written on a sticker under "
|
|
||||||
"the device, you should write for example "
|
|
||||||
"-d \"SN:03461\". You must prepend enough 0s for "
|
|
||||||
"it to be 5 digits. If omitted, the first "
|
|
||||||
"available device will be used.")
|
|
||||||
parser.add_argument("--simulation", action="store_true",
|
|
||||||
help="Put the driver in simulation mode.")
|
|
||||||
add_common_args(parser)
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
init_logger(args)
|
|
||||||
if args.simulation:
|
|
||||||
lda = Ldasim()
|
|
||||||
else:
|
|
||||||
lda = Lda(args.device, args.product)
|
|
||||||
try:
|
|
||||||
simple_server_loop({"lda": lda},
|
|
||||||
bind_address_from_args(args), args.port)
|
|
||||||
finally:
|
|
||||||
lda.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -60,13 +60,13 @@ class ControllerCase(unittest.TestCase):
|
||||||
entry = {
|
entry = {
|
||||||
"type": "controller",
|
"type": "controller",
|
||||||
"host": "::1",
|
"host": "::1",
|
||||||
"port": 3253,
|
"port": 1068,
|
||||||
"command": (sys.executable.replace("\\", "\\\\")
|
"command": (sys.executable.replace("\\", "\\\\")
|
||||||
+ " -m artiq.frontend.aqctl_lda "
|
+ " -m artiq.frontend.aqctl_corelog "
|
||||||
+ "-p {port} --simulation")
|
+ "-p {port} --simulation foo")
|
||||||
}
|
}
|
||||||
async def test():
|
async def test():
|
||||||
await self.start("lda_sim", entry)
|
await self.start("corelog", entry)
|
||||||
remote = await self.get_client(entry["host"], entry["port"])
|
remote = await self.get_client(entry["host"], entry["port"])
|
||||||
await remote.ping()
|
await remote.ping()
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ class ControllerCase(unittest.TestCase):
|
||||||
entry = {
|
entry = {
|
||||||
"type": "controller",
|
"type": "controller",
|
||||||
"host": "::1",
|
"host": "::1",
|
||||||
"port": 3253
|
"port": 1068
|
||||||
}
|
}
|
||||||
with expect_no_log_messages(logging.ERROR):
|
with expect_no_log_messages(logging.ERROR):
|
||||||
self.controllers["lda_sim"] = entry
|
self.controllers["corelog"] = entry
|
||||||
self.assertTrue(self.controllers.queue.empty())
|
self.assertTrue(self.controllers.queue.empty())
|
||||||
|
|
|
@ -10,7 +10,7 @@ class TestFrontends(unittest.TestCase):
|
||||||
# Skip tests for GUI programs on headless CI environments.
|
# Skip tests for GUI programs on headless CI environments.
|
||||||
commands = {
|
commands = {
|
||||||
"aqctl": [
|
"aqctl": [
|
||||||
"corelog", "lda", "thorlabs_tcube"
|
"corelog", "thorlabs_tcube"
|
||||||
],
|
],
|
||||||
"artiq": [
|
"artiq": [
|
||||||
"client", "compile", "coreanalyzer", "coremgmt", "ctlmgr",
|
"client", "compile", "coreanalyzer", "coremgmt", "ctlmgr",
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import unittest
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from artiq.devices.lda.driver import Ldasim
|
|
||||||
from artiq.language.units import dB
|
|
||||||
from artiq.test.hardware_testbench import GenericControllerCase, ControllerCase
|
|
||||||
|
|
||||||
|
|
||||||
class GenericLdaTest:
|
|
||||||
def test_attenuation(self):
|
|
||||||
step = self.cont.get_att_step_size()
|
|
||||||
attmax = self.cont.get_att_max()
|
|
||||||
test_vector = [i*step*dB for i in range(0, int(attmax*int(1/step)+1))]
|
|
||||||
for i in test_vector:
|
|
||||||
with self.subTest(i=i):
|
|
||||||
self.cont.set_attenuation(i)
|
|
||||||
j = self.cont.get_attenuation()
|
|
||||||
self.assertEqual(i, j)
|
|
||||||
|
|
||||||
|
|
||||||
class TestLda(ControllerCase, GenericLdaTest):
|
|
||||||
def setUp(self):
|
|
||||||
ControllerCase.setUp(self)
|
|
||||||
self.start_controller("lda")
|
|
||||||
self.cont = self.device_mgr.get("lda")
|
|
||||||
|
|
||||||
|
|
||||||
class TestLdaSim(GenericControllerCase, GenericLdaTest):
|
|
||||||
def get_device_db(self):
|
|
||||||
return {
|
|
||||||
"lda": {
|
|
||||||
"type": "controller",
|
|
||||||
"host": "::1",
|
|
||||||
"port": 3253,
|
|
||||||
"command": (sys.executable.replace("\\", "\\\\")
|
|
||||||
+ " -m artiq.frontend.aqctl_lda "
|
|
||||||
+ "-p {port} --simulation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
GenericControllerCase.setUp(self)
|
|
||||||
self.start_controller("lda")
|
|
||||||
self.cont = self.device_mgr.get("lda")
|
|
1
setup.py
1
setup.py
|
@ -37,7 +37,6 @@ console_scripts = [
|
||||||
"artiq_flash = artiq.frontend.artiq_flash:main",
|
"artiq_flash = artiq.frontend.artiq_flash:main",
|
||||||
|
|
||||||
"aqctl_corelog = artiq.frontend.aqctl_corelog:main",
|
"aqctl_corelog = artiq.frontend.aqctl_corelog:main",
|
||||||
"aqctl_lda = artiq.frontend.aqctl_lda:main",
|
|
||||||
"aqctl_thorlabs_tcube = artiq.frontend.aqctl_thorlabs_tcube:main",
|
"aqctl_thorlabs_tcube = artiq.frontend.aqctl_thorlabs_tcube:main",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue