quantnet-agent 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantnet_agent/__init__.py +1 -0
- quantnet_agent/cli.py +80 -0
- quantnet_agent/common/__init__.py +2 -0
- quantnet_agent/common/calibration_status.py +7 -0
- quantnet_agent/common/config.py +135 -0
- quantnet_agent/common/constants.py +28 -0
- quantnet_agent/common/logging.py +60 -0
- quantnet_agent/hal/HAL.py +231 -0
- quantnet_agent/hal/__init__.py +0 -0
- quantnet_agent/hal/driver/OZoptics.py +46 -0
- quantnet_agent/hal/driver/Thorlabs.py +39 -0
- quantnet_agent/hal/driver/__init__.py +0 -0
- quantnet_agent/hal/driver/artiq.py +124 -0
- quantnet_agent/hal/driver/dummy.py +373 -0
- quantnet_agent/hal/driver/overlayepc.py +27 -0
- quantnet_agent/hal/driver/overlayexpframework.py +47 -0
- quantnet_agent/hal/driver/overlaylightsrc.py +125 -0
- quantnet_agent/hal/driver/overlaymeter.py +58 -0
- quantnet_agent/hal/driver/simulator.py +345 -0
- quantnet_agent/hal/hwclasses.py +148 -0
- quantnet_agent/hal/interpreter/__init__.py +0 -0
- quantnet_agent/hal/interpreter/calibration.py +101 -0
- quantnet_agent/hal/interpreter/calibration_interpreter.py +43 -0
- quantnet_agent/hal/interpreter/dummy_cavity_calibration.py +16 -0
- quantnet_agent/hal/interpreter/dummy_lsrc_calibration.py +16 -0
- quantnet_agent/hal/interpreter/exp_framework.py +75 -0
- quantnet_agent/hal/interpreter/scheduler.py +35 -0
- quantnet_agent/hal/local_task_manager.py +249 -0
- quantnet_agent/hal/node.py +205 -0
- quantnet_agent/interpreter/test_interpreter.py +32 -0
- quantnet_agent/scheduler/__init__.py +0 -0
- quantnet_agent/scheduler/scheduler.py +483 -0
- quantnet_agent/service/__init__.py +1 -0
- quantnet_agent/service/agent.py +98 -0
- quantnet_agent/service/register.py +77 -0
- quantnet_agent-1.0.0.dist-info/METADATA +83 -0
- quantnet_agent-1.0.0.dist-info/RECORD +41 -0
- quantnet_agent-1.0.0.dist-info/WHEEL +5 -0
- quantnet_agent-1.0.0.dist-info/entry_points.txt +2 -0
- quantnet_agent-1.0.0.dist-info/licenses/LICENSE +41 -0
- quantnet_agent-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
quantnet_agent/cli.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import click
|
|
3
|
+
import sys
|
|
4
|
+
from quantnet_agent.service import QuantnetAgent
|
|
5
|
+
from quantnet_agent.common import Constants, Config
|
|
6
|
+
from quantnet_agent.common.logging import setup_logging
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command("Quantnet Agent")
|
|
10
|
+
@click.option("-c",
|
|
11
|
+
"--config",
|
|
12
|
+
default=Constants.DEFAULT_CONFIG_FILE,
|
|
13
|
+
help="Main configuration file",
|
|
14
|
+
show_default=True)
|
|
15
|
+
@click.option("-n",
|
|
16
|
+
"--node-config",
|
|
17
|
+
default=None,
|
|
18
|
+
help="Node configuration file",
|
|
19
|
+
show_default=True)
|
|
20
|
+
@click.option("-a",
|
|
21
|
+
"--agent_id",
|
|
22
|
+
default=socket.getfqdn(),
|
|
23
|
+
help="Specify an agent identifier",
|
|
24
|
+
show_default=True)
|
|
25
|
+
@click.option("--mq-broker-host",
|
|
26
|
+
"mq_broker_host",
|
|
27
|
+
type=str,
|
|
28
|
+
help="Message queue broker host",
|
|
29
|
+
show_default=True)
|
|
30
|
+
@click.option("--mq-broker-port",
|
|
31
|
+
"mq_broker_port",
|
|
32
|
+
type=int,
|
|
33
|
+
help="Message queue broker port",
|
|
34
|
+
show_default=True)
|
|
35
|
+
@click.option("-d",
|
|
36
|
+
"--debug",
|
|
37
|
+
is_flag=True,
|
|
38
|
+
show_default=True,
|
|
39
|
+
default=False,
|
|
40
|
+
help="Enable debug logging")
|
|
41
|
+
@click.option("--interpreter-path",
|
|
42
|
+
"interpreter_path",
|
|
43
|
+
type=str,
|
|
44
|
+
help="Location of additional command interpreters",
|
|
45
|
+
show_default=True)
|
|
46
|
+
@click.option("--schema-path",
|
|
47
|
+
"schema_path",
|
|
48
|
+
type=str,
|
|
49
|
+
help="Specify a path containing additional schema files",
|
|
50
|
+
show_default=True
|
|
51
|
+
)
|
|
52
|
+
def main(config,
|
|
53
|
+
agent_id,
|
|
54
|
+
node_config,
|
|
55
|
+
mq_broker_host,
|
|
56
|
+
mq_broker_port,
|
|
57
|
+
debug,
|
|
58
|
+
interpreter_path,
|
|
59
|
+
schema_path):
|
|
60
|
+
cobj = Config(config,
|
|
61
|
+
node_config,
|
|
62
|
+
debug,
|
|
63
|
+
agent_id,
|
|
64
|
+
mq_broker_host,
|
|
65
|
+
mq_broker_port,
|
|
66
|
+
interpreter_path,
|
|
67
|
+
schema_path)
|
|
68
|
+
|
|
69
|
+
if cobj.node_file is None:
|
|
70
|
+
print("No node configuration file specified. Use --node-config (-n) to provide one.")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
setup_logging(cobj)
|
|
74
|
+
|
|
75
|
+
agent = QuantnetAgent(cobj)
|
|
76
|
+
agent.run()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
sys.exit(main()) # pragma: no cover
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import configobj
|
|
4
|
+
import json
|
|
5
|
+
from quantnet_agent.common.constants import Constants
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_config(config_file):
|
|
11
|
+
config_files = []
|
|
12
|
+
if config_file:
|
|
13
|
+
config_files.append(config_file)
|
|
14
|
+
if "QUANTNET_HOME" in os.environ:
|
|
15
|
+
config_files.append(f"{os.environ['QUANTNET_HOME']}/etc/agent.cfg")
|
|
16
|
+
else:
|
|
17
|
+
config_files.append("/etc/quantnet/agent.cfg")
|
|
18
|
+
|
|
19
|
+
for cf in config_files:
|
|
20
|
+
try:
|
|
21
|
+
config = configobj.ConfigObj(cf)
|
|
22
|
+
except IOError:
|
|
23
|
+
continue
|
|
24
|
+
return config
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Config:
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
config_file: str = None,
|
|
31
|
+
node_file: str = None,
|
|
32
|
+
debug: bool = False,
|
|
33
|
+
agent_id: str = None,
|
|
34
|
+
mq_broker_host: str = None,
|
|
35
|
+
mq_broker_port: int = None,
|
|
36
|
+
interpreter_path: str = None,
|
|
37
|
+
schema_path: str = None,
|
|
38
|
+
):
|
|
39
|
+
self._config = None
|
|
40
|
+
self.devices = {}
|
|
41
|
+
|
|
42
|
+
self._config = get_config(config_file)
|
|
43
|
+
|
|
44
|
+
if not self._config:
|
|
45
|
+
log.warning("No configuration file found, continuing with defaults")
|
|
46
|
+
|
|
47
|
+
if node_file:
|
|
48
|
+
self.node_file = node_file
|
|
49
|
+
else:
|
|
50
|
+
self.node_file = self.config_get("agent", "node_file", raise_exception=False)
|
|
51
|
+
|
|
52
|
+
if mq_broker_host:
|
|
53
|
+
self.mq_broker_host = mq_broker_host
|
|
54
|
+
else:
|
|
55
|
+
self.mq_broker_host = self.config_get("mq", "host", default="127.0.0.1")
|
|
56
|
+
|
|
57
|
+
if mq_broker_port:
|
|
58
|
+
self.mq_broker_port = mq_broker_port
|
|
59
|
+
else:
|
|
60
|
+
self.mq_broker_port = self.config_get("mq", "port", default="1883")
|
|
61
|
+
|
|
62
|
+
self.threads = int(self.config_get("agent", "threads", default=8))
|
|
63
|
+
|
|
64
|
+
if debug:
|
|
65
|
+
self.debug = debug
|
|
66
|
+
else:
|
|
67
|
+
self.config_get("agent", "debug", default=False)
|
|
68
|
+
|
|
69
|
+
if agent_id:
|
|
70
|
+
self.cid = agent_id
|
|
71
|
+
else:
|
|
72
|
+
try:
|
|
73
|
+
self.cid = self.config_get("agent", "agent_id")
|
|
74
|
+
except Exception:
|
|
75
|
+
self.cid = None
|
|
76
|
+
|
|
77
|
+
if interpreter_path:
|
|
78
|
+
self.interpreter_path = interpreter_path
|
|
79
|
+
else:
|
|
80
|
+
self.interpreter_path = self.config_get("interpreters", "path", raise_exception=False)
|
|
81
|
+
|
|
82
|
+
if "protocols" in self._config and len(self._config["protocols"]) > 0:
|
|
83
|
+
if self.interpreter_path is None:
|
|
84
|
+
raise Exception("Interpreter location for protocols is not found")
|
|
85
|
+
self.proto_plugins = self._config["protocols"]
|
|
86
|
+
else:
|
|
87
|
+
self.proto_plugins = {}
|
|
88
|
+
|
|
89
|
+
if schema_path:
|
|
90
|
+
self.schema_path = schema_path
|
|
91
|
+
else:
|
|
92
|
+
self.schema_path = self.config_get("schemas", "path", raise_exception=False)
|
|
93
|
+
|
|
94
|
+
if "devices" in self._config:
|
|
95
|
+
self.devices = self._config["devices"]
|
|
96
|
+
|
|
97
|
+
self.tasks = []
|
|
98
|
+
self.task_properties = {}
|
|
99
|
+
|
|
100
|
+
if "tasks" in self._config:
|
|
101
|
+
for task, property in self._config["tasks"].items():
|
|
102
|
+
try:
|
|
103
|
+
if type(property) is configobj.Section:
|
|
104
|
+
with open(os.path.join(Constants.DEFAULT_TASK_PATH, property["path"]), "r") as file:
|
|
105
|
+
calibration_task = json.load(file)
|
|
106
|
+
if float(calibration_task["Periodicity"]) <= Constants.SLOTSIZE.total_seconds():
|
|
107
|
+
raise Exception(
|
|
108
|
+
f"Task {task} interval is too short."
|
|
109
|
+
"It should be larger than the TDMA slot size "
|
|
110
|
+
f"{Constants.SLOTSIZE.total_seconds() * 1e3 }"
|
|
111
|
+
)
|
|
112
|
+
self.tasks.append(calibration_task)
|
|
113
|
+
else:
|
|
114
|
+
self.task_properties[task] = property
|
|
115
|
+
except Exception as e:
|
|
116
|
+
log.error(f"Cannot load local task {task} : {e}")
|
|
117
|
+
|
|
118
|
+
def config_get(self, section, option, raise_exception=True, default=None, check_config_table=True):
|
|
119
|
+
"""
|
|
120
|
+
Return the string value for a given option in a section
|
|
121
|
+
|
|
122
|
+
:param section: the named section.
|
|
123
|
+
:param option: the named option.
|
|
124
|
+
:param raise_exception: Boolean to raise or not NoOptionError or NoSectionError.
|
|
125
|
+
:param default: the default value if not found.
|
|
126
|
+
:param check_config_table: if not set, avoid looking at config table
|
|
127
|
+
.
|
|
128
|
+
:returns: the configuration value.
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
return self._config[section][option]
|
|
132
|
+
except (configobj.ConfigObjError, KeyError) as err:
|
|
133
|
+
if raise_exception and default is None:
|
|
134
|
+
raise err
|
|
135
|
+
return default
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
import quantnet_agent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Constants:
|
|
7
|
+
DEFAULT_CONFIG_FILE = "./config/agent.cfg"
|
|
8
|
+
DEFAULT_NODE_CONFIG_FILE = "./config/conf-alice.json"
|
|
9
|
+
DEFAULT_LOGGING_CONFIG_FILE = "./config/logging.conf"
|
|
10
|
+
DEFAULT_INTERPRETERS = {
|
|
11
|
+
"scheduler": "scheduler.py",
|
|
12
|
+
"calibration": "calibration.py",
|
|
13
|
+
"experiment": "exp_framework.py",
|
|
14
|
+
}
|
|
15
|
+
DEFAULT_TASK_PATH = os.path.join(os.path.dirname(os.path.dirname(quantnet_agent.__file__)), "config/")
|
|
16
|
+
DEFAULT_TASK_INTERPRETER = os.path.join(
|
|
17
|
+
os.path.dirname(quantnet_agent.__file__), "hal/interpreter/calibration_interpreter.py"
|
|
18
|
+
)
|
|
19
|
+
DEFAULT_TASK_NS = "quantnet_agent.task"
|
|
20
|
+
HEARTBEAT_INTERVAL = 10
|
|
21
|
+
REGISTRATION_RETRY_INTERVAL = 10
|
|
22
|
+
MAX_TIMESLOTS = 20000
|
|
23
|
+
SLOTSIZE = timedelta(milliseconds=100)
|
|
24
|
+
SCHEDULER_GRACE_PERIOD = timedelta(milliseconds=50)
|
|
25
|
+
# TODO: Check SCHEDULER_GRACE_PERIOD < SLOTSIZE * MAX_TIMESLOTS
|
|
26
|
+
UPDATE_INTERVAL = SLOTSIZE * MAX_TIMESLOTS / 2
|
|
27
|
+
# TODO: Check if update_interval is appropriate
|
|
28
|
+
DAG_CHECK_INTERVAL = timedelta(milliseconds=1000)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import logging
|
|
4
|
+
import logging.config
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def quantnet_log_formatter(cobj=None):
|
|
8
|
+
config_logformat = cobj.config_get(
|
|
9
|
+
"common",
|
|
10
|
+
"logformat",
|
|
11
|
+
raise_exception=False,
|
|
12
|
+
default="{asctime} {name:<29} {process} {levelname:>8} {message}",
|
|
13
|
+
)
|
|
14
|
+
return logging.Formatter(fmt=config_logformat, style='{')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def setup_default_logging(cobj=None):
|
|
18
|
+
"""
|
|
19
|
+
Configures the logging by setting the output stream to stdout and
|
|
20
|
+
configures log level and log format.
|
|
21
|
+
"""
|
|
22
|
+
config_loglevel = getattr(logging, cobj.config_get("common", "loglevel",
|
|
23
|
+
raise_exception=False, default="INFO").upper())
|
|
24
|
+
|
|
25
|
+
stdouthandler = logging.StreamHandler(stream=sys.stdout)
|
|
26
|
+
stdouthandler.setFormatter(quantnet_log_formatter(cobj))
|
|
27
|
+
stdouthandler.setLevel(config_loglevel)
|
|
28
|
+
logging.basicConfig(level=config_loglevel, handlers=[stdouthandler])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def setup_logging(cobj=None):
|
|
32
|
+
"""
|
|
33
|
+
Configures the logging by setting the output stream to stdout and
|
|
34
|
+
configures log level and log format.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
configfiles = list()
|
|
38
|
+
|
|
39
|
+
if cobj:
|
|
40
|
+
logging_config_path = cobj.config_get("common", "logging_config", raise_exception=False, default=None)
|
|
41
|
+
if logging_config_path:
|
|
42
|
+
configfiles.append(logging_config_path)
|
|
43
|
+
|
|
44
|
+
for i in ["QUANTNET_HOME", "VIRTUAL_ENV"]:
|
|
45
|
+
if i in os.environ:
|
|
46
|
+
configfiles.append(f"{os.environ[i]}/etc/logging.conf")
|
|
47
|
+
configfiles.append("/opt/quantnet/etc/logging.conf")
|
|
48
|
+
|
|
49
|
+
has_config = False
|
|
50
|
+
for configfile in configfiles:
|
|
51
|
+
try:
|
|
52
|
+
logging.config.fileConfig(configfile, disable_existing_loggers=False)
|
|
53
|
+
has_config = True
|
|
54
|
+
except Exception:
|
|
55
|
+
has_config = False
|
|
56
|
+
if has_config:
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
if not has_config and cobj:
|
|
60
|
+
setup_default_logging(cobj)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import ast
|
|
3
|
+
import os
|
|
4
|
+
import importlib
|
|
5
|
+
import sys
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
import traceback
|
|
8
|
+
from pydantic import TypeAdapter
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger(__name__)
|
|
11
|
+
driver_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "driver")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_driver_module(classname):
|
|
15
|
+
for filename in os.listdir(driver_path):
|
|
16
|
+
f_path = os.path.join(driver_path, filename)
|
|
17
|
+
if not os.path.isfile(f_path):
|
|
18
|
+
continue
|
|
19
|
+
with open(f_path) as file:
|
|
20
|
+
node = ast.parse(file.read())
|
|
21
|
+
classes = [n for n in node.body if isinstance(n, ast.ClassDef)]
|
|
22
|
+
for class_ in classes:
|
|
23
|
+
if classname == class_.name:
|
|
24
|
+
try:
|
|
25
|
+
spec = importlib.util.spec_from_file_location(
|
|
26
|
+
os.path.splitext(filename)[0], os.path.join(driver_path, filename)
|
|
27
|
+
)
|
|
28
|
+
module = importlib.util.module_from_spec(spec)
|
|
29
|
+
sys.modules["module.name"] = module
|
|
30
|
+
spec.loader.exec_module(module)
|
|
31
|
+
mod = getattr(module, classname)
|
|
32
|
+
return mod
|
|
33
|
+
except Exception:
|
|
34
|
+
log.error(f"Cannot load driver class {class_.name}")
|
|
35
|
+
log.error(traceback.format_exc())
|
|
36
|
+
return
|
|
37
|
+
log.error(f"Cannot find class {classname} from plugin dir")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class HardwareAbstractionLayer:
|
|
41
|
+
def __init__(self, config, msgclient):
|
|
42
|
+
|
|
43
|
+
self.devs = {}
|
|
44
|
+
self._msgclient = msgclient
|
|
45
|
+
self._config = config
|
|
46
|
+
|
|
47
|
+
for device, property in config.devices.items():
|
|
48
|
+
enabled = property.get("enabled")
|
|
49
|
+
if enabled and not TypeAdapter(bool).validate_python(enabled):
|
|
50
|
+
continue
|
|
51
|
+
if device in self.devs:
|
|
52
|
+
log.error(f"Duplicated Device {device}")
|
|
53
|
+
continue
|
|
54
|
+
dev_cls = get_driver_module(property["driver"])
|
|
55
|
+
if dev_cls is None:
|
|
56
|
+
continue
|
|
57
|
+
try:
|
|
58
|
+
dev = dev_cls(property,
|
|
59
|
+
config.node_file,
|
|
60
|
+
config.mq_broker_host,
|
|
61
|
+
config.mq_broker_port)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
log.error(f"Cannot instantiate driver class {dev_cls}: {e}")
|
|
64
|
+
continue
|
|
65
|
+
self.devs[device] = dev
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Interpreter(ABC):
|
|
69
|
+
"""Abstract base class for the Agent Interpreter module.
|
|
70
|
+
|
|
71
|
+
Example::
|
|
72
|
+
|
|
73
|
+
i = Interpreter(hal)
|
|
74
|
+
|
|
75
|
+
:param hal: :doc:`HAL </hal>` object created by :class:`~quantnet_agent.hal.node`.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, hal):
|
|
79
|
+
self.hal = hal
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CoreInterpreter(Interpreter):
|
|
83
|
+
"""Abstract base class for the built-in Agent Interpreter module (e.g.,
|
|
84
|
+
:class:`~quantnet_agent.hal.interpreter.scheduler`).
|
|
85
|
+
|
|
86
|
+
Example::
|
|
87
|
+
|
|
88
|
+
i = CoreInterpreter(node)
|
|
89
|
+
|
|
90
|
+
:param node: :class:`~quantnet_agent.hal.node` object created by :class:`~quantnet_agent.service.node`.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, node) -> None:
|
|
94
|
+
self.node = node
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def get_commands(self):
|
|
98
|
+
"""Returns a dictionary of RPC handlers for a built-in interpreter to register with the RPC server
|
|
99
|
+
in the controller. The dictionary keys are the names of RPC endpoints, and the values are lists composed
|
|
100
|
+
of an RPC handler pointer and a schema model corresponding to the RPC.
|
|
101
|
+
|
|
102
|
+
Example::
|
|
103
|
+
|
|
104
|
+
def get_commands(self):
|
|
105
|
+
commands = {
|
|
106
|
+
"scheduler.getSchedule": [self.get_schedule, "quantnet_mq.schema.models.scheduler.getSchedule"]
|
|
107
|
+
}
|
|
108
|
+
return commands
|
|
109
|
+
"""
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CMDInterpreter(Interpreter):
|
|
114
|
+
"""Abstract base class for the Agent command Interpreter module (e.g., Link calibration module
|
|
115
|
+
:class:`~quantnet_agent.hal.interpreter.calibration`).
|
|
116
|
+
|
|
117
|
+
Example::
|
|
118
|
+
|
|
119
|
+
i = CMDInterpreter(hal)
|
|
120
|
+
|
|
121
|
+
:param hal: :doc:`HAL </hal>` object created by :class:`~quantnet_agent.hal.node`.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, hal) -> None:
|
|
125
|
+
super().__init__(hal)
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def get_commands(self):
|
|
129
|
+
"""Returns a dictionary of RPC handlers for a command interpreter to register with the RPC server
|
|
130
|
+
in the controller. The dictionary keys are the names of RPC endpoints, and the values are lists
|
|
131
|
+
composed of an RPC handler pointer and a schema model corresponding to the RPC.
|
|
132
|
+
|
|
133
|
+
Example::
|
|
134
|
+
|
|
135
|
+
def get_commands(self):
|
|
136
|
+
commands = {
|
|
137
|
+
"calibration.calibration": [self.measure, "quantnet_mq.schema.models.calibration.calibration"]
|
|
138
|
+
}
|
|
139
|
+
return commands
|
|
140
|
+
"""
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class ScheduleableInterpreter(CMDInterpreter):
|
|
145
|
+
"""Abstract base class for a schedulable Agent command Interpreter module (e.g.,
|
|
146
|
+
:class:`~quantnet_agent.hal.interpreter.ExperimentFramework`).
|
|
147
|
+
|
|
148
|
+
Example::
|
|
149
|
+
|
|
150
|
+
i = ScheduleableInterpreter(hal)
|
|
151
|
+
|
|
152
|
+
:param hal: :doc:`HAL </hal>` object created by :class:`~quantnet_agent.hal.node`.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(self, hal) -> None:
|
|
156
|
+
super().__init__(hal)
|
|
157
|
+
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def get_commands(self):
|
|
160
|
+
"""Returns a dictionary of RPC handlers for a schedulable command interpreter to register with the RPC server
|
|
161
|
+
in the controller. The dictionary keys are the names of RPC endpoints, and the values are lists composed
|
|
162
|
+
of an RPC handler pointer and a schema model corresponding to the RPC.
|
|
163
|
+
|
|
164
|
+
Example::
|
|
165
|
+
|
|
166
|
+
def get_commands(self):
|
|
167
|
+
commands = {
|
|
168
|
+
"experiment.getState": [self.get_state, "quantnet_mq.schema.models.experiment.getState"]
|
|
169
|
+
}
|
|
170
|
+
return commands
|
|
171
|
+
"""
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
@abstractmethod
|
|
175
|
+
def get_schedulable_commands(self):
|
|
176
|
+
"""Returns a dictionary of RPC handlers for a schedulable command interpreter to register with the RPC server
|
|
177
|
+
in the two-level scheduler. The RPC endpoints returned by this function are used by the agent scheduler
|
|
178
|
+
instead of the agent node, enabling the scheduler to allocate a handler to an available timeslot when
|
|
179
|
+
requested by the global scheduler.
|
|
180
|
+
|
|
181
|
+
Keys in the dictionary are the names of the RPC endpoints, and the values are lists composed of
|
|
182
|
+
an RPC handler pointer, a schema model corresponding to the RPC, and a schema model for the response.
|
|
183
|
+
|
|
184
|
+
Example::
|
|
185
|
+
|
|
186
|
+
def get_schedulable_commands(self):
|
|
187
|
+
commands = {
|
|
188
|
+
"experiment.submit": [
|
|
189
|
+
self.submit,
|
|
190
|
+
"quantnet_mq.schema.models.experiment.submit",
|
|
191
|
+
experiment.submitResponse,
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
return commands
|
|
195
|
+
"""
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class LocalTaskInterpreter(Interpreter):
|
|
200
|
+
"""Abstract base class for a local Agent command Interpreter module (e.g., local device calibration).
|
|
201
|
+
|
|
202
|
+
Example::
|
|
203
|
+
|
|
204
|
+
i = LocalTaskInterpreter(hal)
|
|
205
|
+
|
|
206
|
+
:param hal: :doc:`HAL </hal>` object created by :class:`~quantnet_agent.hal.node`.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, hal):
|
|
210
|
+
super().__init__(hal)
|
|
211
|
+
|
|
212
|
+
@abstractmethod
|
|
213
|
+
def run(self, *args, **kwargs):
|
|
214
|
+
"""Execute a local task (e.g., device calibration).
|
|
215
|
+
|
|
216
|
+
:param args: Parameters to use for running the local task.
|
|
217
|
+
:type args: list
|
|
218
|
+
:param kwargs: Keyword parameter to use for running the local task.
|
|
219
|
+
:type kwargs: dict
|
|
220
|
+
"""
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
@abstractmethod
|
|
224
|
+
def stop(self):
|
|
225
|
+
"""Stops a local task started with the `run` method."""
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
@abstractmethod
|
|
229
|
+
async def receive(self, *args, **kwargs):
|
|
230
|
+
"""Receive result of the `run` method."""
|
|
231
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import serial
|
|
2
|
+
from quantnet_agent.hal.hwclasses import Filter
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
log = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OZOptics(Filter):
|
|
9
|
+
def __init__(self, config):
|
|
10
|
+
self.ser = serial.Serial(
|
|
11
|
+
port=config["port"],
|
|
12
|
+
baudrate=config["baudrate"],
|
|
13
|
+
timeout=1,
|
|
14
|
+
bytesize=serial.EIGHTBITS,
|
|
15
|
+
stopbits=serial.STOPBITS_ONE,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
log.info(f"Initialized OZOptics EPC with port={config['port']}")
|
|
19
|
+
self.buflen = 2048
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def okay(self):
|
|
23
|
+
return True if self.ser else False
|
|
24
|
+
|
|
25
|
+
def __ask(self, cmd):
|
|
26
|
+
if self.ser:
|
|
27
|
+
self.ser.write(f"{cmd}\r\n".encode())
|
|
28
|
+
return self.ser.read(self.buflen).decode()
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def help(self):
|
|
32
|
+
return self.__ask("?")
|
|
33
|
+
|
|
34
|
+
def _mode_ac(self):
|
|
35
|
+
self.__ask("MAC")
|
|
36
|
+
|
|
37
|
+
def _mode_dc(self):
|
|
38
|
+
self.__ask("MDC")
|
|
39
|
+
|
|
40
|
+
def dst_init(self, request):
|
|
41
|
+
log.info("Initializing for Calibration")
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
def calibrate(self, request):
|
|
45
|
+
log.info("Starting Calibration")
|
|
46
|
+
return 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from ThorlabsPM100 import ThorlabsPM100, USBTMC
|
|
2
|
+
import numpy as np
|
|
3
|
+
from quantnet_agent.hal.hwclasses import LightMeasurement
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
log = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PM100D(LightMeasurement):
|
|
10
|
+
def __init__(self, config):
|
|
11
|
+
inst = USBTMC(device=config["device"])
|
|
12
|
+
self.dev = ThorlabsPM100(inst=inst)
|
|
13
|
+
self.pd_bandwidth = self.dev.input.pdiode.filter.lpass.state
|
|
14
|
+
self.avg_count = self.dev.sense.average.count
|
|
15
|
+
log.info(f"Initialized PM100D with bw={self.pd_bandwidth} avr={self.avg_count} wavelength={self.wavelength}")
|
|
16
|
+
log.info(f"Configuration: {self.getconfigure}")
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def read(self):
|
|
20
|
+
return self.dev.read
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def wavelength(self):
|
|
24
|
+
return self.dev.sense.correction.wavelength
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def power(self, count=10):
|
|
28
|
+
request = type('', (), {})()
|
|
29
|
+
request.count = count
|
|
30
|
+
self.measure(request)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def getconfigure(self):
|
|
34
|
+
return self.dev.getconfigure
|
|
35
|
+
|
|
36
|
+
def measure(self, request):
|
|
37
|
+
log.info(f"Reading: {request.count} measurements")
|
|
38
|
+
power = np.array([self.dev.read for _ in range(request.count)])
|
|
39
|
+
return power
|
|
File without changes
|