forked from M-Labs/artiq
Compare commits
6 Commits
32f889d77e
...
fd3a2610f0
Author | SHA1 | Date |
---|---|---|
Simon Renblad | fd3a2610f0 | |
Simon Renblad | 21261c3cd5 | |
Simon Renblad | 65a57e1f8c | |
Simon Renblad | c4323e1179 | |
morgan | 609684664a | |
Simon Renblad | 7e6ed1655f |
|
@ -49,6 +49,10 @@
|
|||
"default": 125e6,
|
||||
"description": "RTIO frequency"
|
||||
},
|
||||
"enable_wrpll": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"core_addr": {
|
||||
"type": "string",
|
||||
"format": "ipv4",
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
import logging
|
||||
import asyncio
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from artiq.tools import elide
|
||||
from artiq.gui.tools import LayoutWidget
|
||||
from artiq.gui.models import DictSyncModel
|
||||
from artiq.gui.applets import EntryArea
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Model(DictSyncModel):
|
||||
def __init__(self, init):
|
||||
DictSyncModel.__init__(self,
|
||||
["Request"],
|
||||
init)
|
||||
|
||||
def convert(self, k, v, column):
|
||||
if column == 0:
|
||||
txt = ":" + v["title"] if v["title"] != "" else ""
|
||||
return elide(str(k) + txt, 25)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
def sort_key(self, k, v):
|
||||
return k
|
||||
|
||||
|
||||
class _InputRequest(QtWidgets.QWidget):
|
||||
def __init__(self, rid, title, arglist_desc):
|
||||
QtWidgets.QWidget.__init__(self)
|
||||
self.rid = rid
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
title_txt = title if title != "" else str(self.rid)
|
||||
self.title_lbl = QtWidgets.QLabel(title_txt)
|
||||
layout.addWidget(self.title_lbl)
|
||||
|
||||
self.entry_area = EntryArea()
|
||||
layout.addWidget(self.entry_area)
|
||||
|
||||
for args in arglist_desc:
|
||||
self.entry_area.setattr_argument_procdesc(*args)
|
||||
|
||||
def get_rid_and_values(self):
|
||||
return (self.rid, self.entry_area.get_values())
|
||||
|
||||
|
||||
class _InputRequestView(QtWidgets.QScrollArea):
|
||||
supplied = QtCore.pyqtSignal(int, dict)
|
||||
cancelled = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self, interactive_args_sub):
|
||||
QtWidgets.QScrollArea.__init__(self)
|
||||
self.setWidgetResizable(True)
|
||||
self.interactive_args_sub = interactive_args_sub
|
||||
|
||||
self.request_stack = QtWidgets.QStackedWidget()
|
||||
self.request_stack.addWidget(QtWidgets.QLabel("No input request selected"))
|
||||
self.setWidget(self.request_stack)
|
||||
|
||||
self.model = Model({})
|
||||
self.interactive_args_sub.add_setmodel_callback(self.setModel)
|
||||
|
||||
def setModel(self, model):
|
||||
self.model = model
|
||||
self.model.dataChanged.connect(self.update_widget)
|
||||
self.model.rowsInserted.connect(self.insert)
|
||||
self.model.rowsMoved.connect(self.move)
|
||||
self.model.rowsRemoved.connect(self.remove)
|
||||
|
||||
def _insert_widget(self, index):
|
||||
rid = self.model.row_to_key[index]
|
||||
title = self.model.backing_store[rid]["title"]
|
||||
arglist_desc = self.model.backing_store[rid]["arglist_desc"]
|
||||
input_request = _InputRequest(rid, title, arglist_desc)
|
||||
self.request_stack.insertWidget(index, input_request)
|
||||
|
||||
def update_widget(self, top_left, bottom_right, roles):
|
||||
index = top_left.row()
|
||||
widget = self.request_stack.widget(index)
|
||||
self.request_stack.removeWidget(widget)
|
||||
self._insert_widget(index)
|
||||
widget.deleteLater()
|
||||
|
||||
def insert(self, parent, first, last):
|
||||
self._insert_widget(first)
|
||||
|
||||
def move(self, src_parent, src_start, src_end, dest_parent, dest_end):
|
||||
widget = self.request_stack.widget(src_start)
|
||||
self.request_stack.insertWidget(dest_end)
|
||||
|
||||
def remove(self, parent, first, last):
|
||||
widget = self.request_stack.widget(first)
|
||||
self.request_stack.removeWidget(widget)
|
||||
widget.deleteLater()
|
||||
|
||||
def set_selected_request(self, row):
|
||||
self.request_stack.setCurrentIndex(row)
|
||||
|
||||
def supply_selected(self):
|
||||
index = self.request_stack.currentIndex()
|
||||
try:
|
||||
self.supply(index)
|
||||
except IndexError:
|
||||
logger.error("no input request selected", exc_info=True)
|
||||
|
||||
def supply(self, index):
|
||||
widget = self.request_stack.widget(index)
|
||||
rid, d = widget.get_rid_and_values()
|
||||
self.supplied.emit(rid, d)
|
||||
|
||||
def cancel_selected(self):
|
||||
index = self.request_stack.currentIndex()
|
||||
try:
|
||||
self.cancel(index)
|
||||
except IndexError:
|
||||
logger.error("no input request selected", exc_info=True)
|
||||
|
||||
def cancel(self, index):
|
||||
rid = self.request_stack.widget(index).rid
|
||||
self.cancelled.emit(rid)
|
||||
|
||||
def supply_all(self):
|
||||
rows = self.model.rowCount(QtCore.QModelIndex())
|
||||
rid_and_values = [self.request_stack.widget(row).get_rid_and_values()
|
||||
for row in range(rows)]
|
||||
for rid, value in rid_and_values:
|
||||
self.supplied.emit(rid, value)
|
||||
|
||||
def cancel_all(self):
|
||||
rows = self.model.rowCount(QtCore.QModelIndex())
|
||||
rids = [self.request_stack.widget(row).rid for row in range(rows)]
|
||||
for rid in rids:
|
||||
self.cancelled.emit(rid)
|
||||
|
||||
|
||||
class _RequestList(QtWidgets.QListView):
|
||||
selected = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self):
|
||||
QtWidgets.QListView.__init__(self)
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||
self.setMaximumWidth(200)
|
||||
|
||||
def selectionChanged(self, selected, deselected):
|
||||
indexes = selected.indexes()
|
||||
row = selected.indexes()[0].row() if len(indexes) > 0 else 0
|
||||
self.selected.emit(row)
|
||||
|
||||
|
||||
class InteractiveArgsDock(QtWidgets.QDockWidget):
|
||||
def __init__(self, interactive_args_sub, interactive_args_rpc):
|
||||
QtWidgets.QDockWidget.__init__(self, "Inputs")
|
||||
self.interactive_args_sub = interactive_args_sub
|
||||
self.interactive_args_rpc = interactive_args_rpc
|
||||
|
||||
layout = LayoutWidget(self)
|
||||
self.setWidget(layout)
|
||||
|
||||
self.request_queue = _RequestList()
|
||||
model = Model({})
|
||||
self.request_queue.setModel(model)
|
||||
layout.addWidget(self.request_queue, 0, 0)
|
||||
|
||||
self.args_area = _InputRequestView(self.interactive_args_sub)
|
||||
self.args_area.supplied.connect(self.supply)
|
||||
self.args_area.cancelled.connect(self.cancel)
|
||||
layout.addWidget(self.args_area, 0, 1, rowspan=5)
|
||||
|
||||
self.request_queue.selected.connect(self.args_area.set_selected_request)
|
||||
|
||||
self.supply_btn = QtWidgets.QPushButton("Supply selected")
|
||||
self.supply_btn.clicked.connect(self.args_area.supply_selected)
|
||||
layout.addWidget(self.supply_btn, 1, 0)
|
||||
|
||||
self.cancel_btn = QtWidgets.QPushButton("Cancel selected")
|
||||
self.cancel_btn.clicked.connect(self.args_area.cancel_selected)
|
||||
layout.addWidget(self.cancel_btn, 2, 0)
|
||||
|
||||
self.supply_all_btn = QtWidgets.QPushButton("Supply all")
|
||||
self.supply_all_btn.clicked.connect(self.args_area.supply_all)
|
||||
layout.addWidget(self.supply_all_btn, 3, 0)
|
||||
|
||||
self.cancel_all_btn = QtWidgets.QPushButton("Cancel all")
|
||||
self.cancel_all_btn.clicked.connect(self.args_area.cancel_all)
|
||||
layout.addWidget(self.cancel_all_btn, 4, 0)
|
||||
|
||||
self.interactive_args_sub.add_setmodel_callback(self.request_queue.setModel)
|
||||
|
||||
def supply(self, rid, values):
|
||||
asyncio.ensure_future(self._supply_task(rid, values))
|
||||
|
||||
async def _supply_task(self, rid, values):
|
||||
try:
|
||||
await self.interactive_args_rpc.supply(rid, values)
|
||||
except:
|
||||
logger.error("dashboard failed to supply interactive arguments for experiment: %d", rid, exc_info=True)
|
||||
|
||||
def cancel(self, rid):
|
||||
asyncio.ensure_future(self._cancel_task(rid))
|
||||
|
||||
async def _cancel_task(self, rid):
|
||||
try:
|
||||
await self.interactive_args_rpc.cancel(rid)
|
||||
except:
|
||||
logger.error("dashboard failed to cancel input request for experiment: %d", rid, exc_info=True)
|
|
@ -290,10 +290,11 @@ def _show_datasets(datasets):
|
|||
|
||||
def _show_interactive_args(interactive_args):
|
||||
clear_screen()
|
||||
table = PrettyTable(["RID", "Key", "Type", "Group", "Tooltip"])
|
||||
for rid, args in sorted(interactive_args.items(), key=itemgetter(0)):
|
||||
for key, procdesc, group, tooltip in args:
|
||||
table.add_row([rid, key, procdesc["ty"], group, tooltip])
|
||||
table = PrettyTable(["RID", "Title", "Key", "Type", "Group", "Tooltip"])
|
||||
for rid, input_request in sorted(interactive_args.items(), key=itemgetter(0)):
|
||||
title = input_request["title"]
|
||||
for key, procdesc, group, tooltip in input_request["arglist_desc"]:
|
||||
table.add_row([rid, title, key, procdesc["ty"], group, tooltip])
|
||||
print(table)
|
||||
|
||||
|
||||
|
@ -307,7 +308,7 @@ def _run_subscriber(host, port, subscriber):
|
|||
loop.run_until_complete(subscriber.connect(host, port))
|
||||
try:
|
||||
_, pending = loop.run_until_complete(asyncio.wait(
|
||||
[signal_handler.wait_terminate(), subscriber.receive_task],
|
||||
[loop.create_task(signal_handler.wait_terminate()), subscriber.receive_task],
|
||||
return_when=asyncio.FIRST_COMPLETED))
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
|
|
|
@ -23,7 +23,7 @@ from artiq.gui.models import ModelSubscriber
|
|||
from artiq.gui import state, log
|
||||
from artiq.dashboard import (experiments, shortcuts, explorer,
|
||||
moninj, datasets, schedule, applets_ccb,
|
||||
waveform)
|
||||
waveform, interactive_args)
|
||||
|
||||
|
||||
def get_argparser():
|
||||
|
@ -141,7 +141,7 @@ def main():
|
|||
|
||||
# create connections to master
|
||||
rpc_clients = dict()
|
||||
for target in "schedule", "experiment_db", "dataset_db", "device_db":
|
||||
for target in "schedule", "experiment_db", "dataset_db", "device_db", "interactive_arg_db":
|
||||
client = AsyncioClient()
|
||||
loop.run_until_complete(client.connect_rpc(
|
||||
args.server, args.port_control, target))
|
||||
|
@ -166,7 +166,8 @@ def main():
|
|||
for notifier_name, modelf in (("explist", explorer.Model),
|
||||
("explist_status", explorer.StatusUpdater),
|
||||
("datasets", datasets.Model),
|
||||
("schedule", schedule.Model)):
|
||||
("schedule", schedule.Model),
|
||||
("interactive_args", interactive_args.Model)):
|
||||
subscriber = ModelSubscriber(notifier_name, modelf,
|
||||
report_disconnect)
|
||||
loop.run_until_complete(subscriber.connect(
|
||||
|
@ -244,6 +245,11 @@ def main():
|
|||
loop.run_until_complete(devices_sub.connect(args.server, args.port_notify))
|
||||
atexit_register_coroutine(devices_sub.close, loop=loop)
|
||||
|
||||
d_interactive_args = interactive_args.InteractiveArgsDock(
|
||||
sub_clients["interactive_args"],
|
||||
rpc_clients["interactive_arg_db"]
|
||||
)
|
||||
|
||||
d_schedule = schedule.ScheduleDock(
|
||||
rpc_clients["schedule"], sub_clients["schedule"])
|
||||
smgr.register(d_schedule)
|
||||
|
@ -257,7 +263,7 @@ def main():
|
|||
right_docks = [
|
||||
d_explorer, d_shortcuts,
|
||||
d_ttl_dds.ttl_dock, d_ttl_dds.dds_dock, d_ttl_dds.dac_dock,
|
||||
d_datasets, d_applets, d_waveform
|
||||
d_datasets, d_applets, d_waveform, d_interactive_args
|
||||
]
|
||||
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, right_docks[0])
|
||||
for d1, d2 in zip(right_docks, right_docks[1:]):
|
||||
|
|
|
@ -167,7 +167,8 @@ def get_argparser(with_file=True):
|
|||
|
||||
|
||||
class ArgumentManager(ProcessArgumentManager):
|
||||
def get_interactive(self, interactive_arglist):
|
||||
def get_interactive(self, interactive_arglist, title):
|
||||
print(title)
|
||||
result = dict()
|
||||
for key, processor, group, tooltip in interactive_arglist:
|
||||
success = False
|
||||
|
|
|
@ -68,9 +68,11 @@ class EntryArea(QtWidgets.QTreeWidget):
|
|||
self.bottom_item.setHidden(True)
|
||||
|
||||
def setattr_argument(self, key, processor, group=None, tooltip=None):
|
||||
self.setattr_argument_procdesc(key, processor.describe(), group, tooltip)
|
||||
|
||||
def setattr_argument_procdesc(self, key, procdesc, group=None, tooltip=None):
|
||||
argument = dict()
|
||||
desc = processor.describe()
|
||||
argument["desc"] = desc
|
||||
argument["desc"] = procdesc
|
||||
argument["group"] = group
|
||||
argument["tooltip"] = tooltip
|
||||
self._arguments[key] = argument
|
||||
|
|
|
@ -214,7 +214,7 @@ class TraceArgumentManager:
|
|||
self.requested_args[key] = processor, group, tooltip
|
||||
return None
|
||||
|
||||
def get_interactive(self, interactive_arglist):
|
||||
def get_interactive(self, interactive_arglist, title):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
@ -238,7 +238,7 @@ class ProcessArgumentManager:
|
|||
raise AttributeError("Supplied argument(s) not queried in experiment: " +
|
||||
", ".join(unprocessed))
|
||||
|
||||
def get_interactive(self, interactive_arglist):
|
||||
def get_interactive(self, interactive_arglist, title):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
@ -332,7 +332,7 @@ class HasEnvironment:
|
|||
self.kernel_invariants = kernel_invariants | {key}
|
||||
|
||||
@contextmanager
|
||||
def interactive(self):
|
||||
def interactive(self, title=""):
|
||||
"""Request arguments from the user interactively.
|
||||
|
||||
This context manager returns a namespace object on which the method
|
||||
|
@ -349,7 +349,7 @@ class HasEnvironment:
|
|||
namespace.setattr_argument = setattr_argument
|
||||
yield namespace
|
||||
del namespace.setattr_argument
|
||||
argdict = self.__argument_mgr.get_interactive(interactive_arglist)
|
||||
argdict = self.__argument_mgr.get_interactive(interactive_arglist, title)
|
||||
for key, value in argdict.items():
|
||||
setattr(namespace, key, value)
|
||||
|
||||
|
|
|
@ -121,8 +121,8 @@ class InteractiveArgDB:
|
|||
self.pending = Notifier(dict())
|
||||
self.futures = dict()
|
||||
|
||||
async def get(self, rid, arglist_desc):
|
||||
self.pending[rid] = arglist_desc
|
||||
async def get(self, rid, arglist_desc, title):
|
||||
self.pending[rid] = {"title": title, "arglist_desc": arglist_desc}
|
||||
self.futures[rid] = asyncio.get_running_loop().create_future()
|
||||
try:
|
||||
value = await self.futures[rid]
|
||||
|
@ -136,7 +136,7 @@ class InteractiveArgDB:
|
|||
if rid not in self.futures:
|
||||
raise ValueError("no experiment with this RID is "
|
||||
"waiting for interactive arguments")
|
||||
if {i[0] for i in self.pending.raw_view[rid]} != set(values.keys()):
|
||||
if {i[0] for i in self.pending.raw_view[rid]["arglist_desc"]} != set(values.keys()):
|
||||
raise ValueError("supplied and requested keys do not match")
|
||||
self.futures[rid].set_result(values)
|
||||
|
||||
|
|
|
@ -218,10 +218,10 @@ def examine(device_mgr, dataset_mgr, file):
|
|||
class ArgumentManager(ProcessArgumentManager):
|
||||
_get_interactive = make_parent_action("get_interactive_arguments")
|
||||
|
||||
def get_interactive(self, interactive_arglist):
|
||||
def get_interactive(self, interactive_arglist, title):
|
||||
arglist_desc = [(k, p.describe(), g, t)
|
||||
for k, p, g, t in interactive_arglist]
|
||||
arguments = ArgumentManager._get_interactive(arglist_desc)
|
||||
arguments = ArgumentManager._get_interactive(arglist_desc, title)
|
||||
for key, processor, _, _ in interactive_arglist:
|
||||
arguments[key] = processor.process(arguments[key])
|
||||
return arguments
|
||||
|
|
Loading…
Reference in New Issue