mirror of https://github.com/m-labs/artiq.git
Merge branch 'applets_pipeipc'
This commit is contained in:
commit
338e5fe3fc
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python3.5
|
#!/usr/bin/env python3.5
|
||||||
|
|
||||||
from quamash import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
from artiq.applets.simple import SimpleApplet
|
from artiq.applets.simple import SimpleApplet
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python3.5
|
#!/usr/bin/env python3.5
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import PyQt5 # make sure pyqtgraph imports Qt5
|
||||||
import pyqtgraph
|
import pyqtgraph
|
||||||
|
|
||||||
from artiq.applets.simple import SimpleApplet
|
from artiq.applets.simple import SimpleApplet
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python3.5
|
#!/usr/bin/env python3.5
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import PyQt5 # make sure pyqtgraph imports Qt5
|
||||||
import pyqtgraph
|
import pyqtgraph
|
||||||
|
|
||||||
from artiq.applets.simple import SimpleApplet
|
from artiq.applets.simple import SimpleApplet
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env python3.5
|
#!/usr/bin/env python3.5
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from quamash import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
import pyqtgraph
|
import pyqtgraph
|
||||||
|
|
||||||
from artiq.applets.simple import SimpleApplet
|
from artiq.applets.simple import SimpleApplet
|
||||||
|
|
|
@ -1,10 +1,70 @@
|
||||||
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from quamash import QEventLoop, QtWidgets, QtGui, QtCore
|
from quamash import QEventLoop, QtWidgets, QtGui, QtCore
|
||||||
|
|
||||||
from artiq.protocols.sync_struct import Subscriber
|
from artiq.protocols.sync_struct import Subscriber, process_mod
|
||||||
from artiq.protocols.pc_rpc import Client
|
from artiq.protocols import pyon
|
||||||
|
from artiq.protocols.pipe_ipc import AsyncioChildComm
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AppletIPCClient(AsyncioChildComm):
|
||||||
|
def set_close_cb(self, close_cb):
|
||||||
|
self.close_cb = close_cb
|
||||||
|
|
||||||
|
def write_pyon(self, obj):
|
||||||
|
self.write(pyon.encode(obj).encode() + b"\n")
|
||||||
|
|
||||||
|
async def read_pyon(self):
|
||||||
|
line = await self.readline()
|
||||||
|
return pyon.decode(line.decode())
|
||||||
|
|
||||||
|
async def embed(self, win_id):
|
||||||
|
# This function is only called when not subscribed to anything,
|
||||||
|
# so the only normal replies are embed_done and terminate.
|
||||||
|
self.write_pyon({"action": "embed",
|
||||||
|
"win_id": win_id})
|
||||||
|
reply = await self.read_pyon()
|
||||||
|
if reply["action"] == "terminate":
|
||||||
|
self.close_cb()
|
||||||
|
elif reply["action"] != "embed_done":
|
||||||
|
logger.error("unexpected action reply to embed request: %s",
|
||||||
|
action)
|
||||||
|
self.close_cb()
|
||||||
|
|
||||||
|
async def listen(self):
|
||||||
|
data = None
|
||||||
|
while True:
|
||||||
|
obj = await self.read_pyon()
|
||||||
|
try:
|
||||||
|
action = obj["action"]
|
||||||
|
if action == "terminate":
|
||||||
|
self.close_cb()
|
||||||
|
return
|
||||||
|
elif action == "mod":
|
||||||
|
mod = obj["mod"]
|
||||||
|
if mod["action"] == "init":
|
||||||
|
data = self.init_cb(mod["struct"])
|
||||||
|
else:
|
||||||
|
process_mod(data, mod)
|
||||||
|
self.mod_cb(mod)
|
||||||
|
else:
|
||||||
|
raise ValueError("unknown action in parent message")
|
||||||
|
except:
|
||||||
|
logger.error("error processing parent message",
|
||||||
|
exc_info=True)
|
||||||
|
self.close_cb()
|
||||||
|
|
||||||
|
def subscribe(self, datasets, init_cb, mod_cb):
|
||||||
|
self.write_pyon({"action": "subscribe",
|
||||||
|
"datasets": datasets})
|
||||||
|
self.init_cb = init_cb
|
||||||
|
self.mod_cb = mod_cb
|
||||||
|
asyncio.ensure_future(self.listen())
|
||||||
|
|
||||||
|
|
||||||
class SimpleApplet:
|
class SimpleApplet:
|
||||||
|
@ -13,27 +73,27 @@ class SimpleApplet:
|
||||||
self.main_widget_class = main_widget_class
|
self.main_widget_class = main_widget_class
|
||||||
|
|
||||||
self.argparser = argparse.ArgumentParser(description=cmd_description)
|
self.argparser = argparse.ArgumentParser(description=cmd_description)
|
||||||
|
|
||||||
self.argparser.add_argument("--update-delay", type=float,
|
self.argparser.add_argument("--update-delay", type=float,
|
||||||
default=default_update_delay,
|
default=default_update_delay,
|
||||||
help="time to wait after a mod (buffering other mods) "
|
help="time to wait after a mod (buffering other mods) "
|
||||||
"before updating (default: %(default).2f)")
|
"before updating (default: %(default).2f)")
|
||||||
group = self.argparser.add_argument_group("data server")
|
|
||||||
|
group = self.argparser.add_argument_group("standalone mode (default)")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--server-notify", default="::1",
|
"--server", default="::1",
|
||||||
help="hostname or IP to connect to for dataset notifications")
|
help="hostname or IP of the master to connect to "
|
||||||
|
"for dataset notifications "
|
||||||
|
"(ignored in embedded mode)")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--port-notify", default=3250, type=int,
|
"--port", default=3250, type=int,
|
||||||
help="TCP port to connect to for dataset notifications")
|
help="TCP port to connect to")
|
||||||
group = self.argparser.add_argument_group("GUI server")
|
|
||||||
group.add_argument(
|
self.argparser.add_argument("--embed", default=None,
|
||||||
"--server-gui", default="::1",
|
help="embed into GUI", metavar="IPC_ADDRESS")
|
||||||
help="hostname or IP to connect to for GUI control")
|
|
||||||
group.add_argument(
|
|
||||||
"--port-gui", default=6501, type=int,
|
|
||||||
help="TCP port to connect to for GUI control")
|
|
||||||
group.add_argument("--embed", default=None, type=int,
|
|
||||||
help="embed main widget into existing window")
|
|
||||||
self._arggroup_datasets = self.argparser.add_argument_group("datasets")
|
self._arggroup_datasets = self.argparser.add_argument_group("datasets")
|
||||||
|
|
||||||
self.dataset_args = set()
|
self.dataset_args = set()
|
||||||
|
|
||||||
def add_dataset(self, name, help=None, required=True):
|
def add_dataset(self, name, help=None, required=True):
|
||||||
|
@ -56,6 +116,15 @@ class SimpleApplet:
|
||||||
self.loop = QEventLoop(app)
|
self.loop = QEventLoop(app)
|
||||||
asyncio.set_event_loop(self.loop)
|
asyncio.set_event_loop(self.loop)
|
||||||
|
|
||||||
|
def ipc_init(self):
|
||||||
|
if self.args.embed is not None:
|
||||||
|
self.ipc = AppletIPCClient(self.args.embed)
|
||||||
|
self.loop.run_until_complete(self.ipc.connect())
|
||||||
|
|
||||||
|
def ipc_close(self):
|
||||||
|
if self.args.embed is not None:
|
||||||
|
self.ipc.close()
|
||||||
|
|
||||||
def create_main_widget(self):
|
def create_main_widget(self):
|
||||||
self.main_widget = self.main_widget_class(self.args)
|
self.main_widget = self.main_widget_class(self.args)
|
||||||
# Qt window embedding is ridiculously buggy, and empirical testing
|
# Qt window embedding is ridiculously buggy, and empirical testing
|
||||||
|
@ -65,15 +134,11 @@ class SimpleApplet:
|
||||||
# 3. applet sends the ID to host, host embeds the widget
|
# 3. applet sends the ID to host, host embeds the widget
|
||||||
# 4. applet shows the widget
|
# 4. applet shows the widget
|
||||||
# Doing embedding the other way around (using QWindow.setParent in the
|
# Doing embedding the other way around (using QWindow.setParent in the
|
||||||
# applet) breaks resizing; furthermore the host needs to know our
|
# applet) breaks resizing.
|
||||||
# window ID to request graceful termination by closing the window.
|
|
||||||
if self.args.embed is not None:
|
if self.args.embed is not None:
|
||||||
|
self.ipc.set_close_cb(self.main_widget.close)
|
||||||
win_id = int(self.main_widget.winId())
|
win_id = int(self.main_widget.winId())
|
||||||
remote = Client(self.args.server_gui, self.args.port_gui, "applets")
|
self.loop.run_until_complete(self.ipc.embed(win_id))
|
||||||
try:
|
|
||||||
remote.embed(self.args.embed, win_id)
|
|
||||||
finally:
|
|
||||||
remote.close_rpc()
|
|
||||||
self.main_widget.show()
|
self.main_widget.show()
|
||||||
|
|
||||||
def sub_init(self, data):
|
def sub_init(self, data):
|
||||||
|
@ -81,6 +146,10 @@ class SimpleApplet:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def filter_mod(self, mod):
|
def filter_mod(self, mod):
|
||||||
|
if self.args.embed is not None:
|
||||||
|
# the parent already filters for us
|
||||||
|
return True
|
||||||
|
|
||||||
if mod["action"] == "init":
|
if mod["action"] == "init":
|
||||||
return True
|
return True
|
||||||
if mod["path"]:
|
if mod["path"]:
|
||||||
|
@ -108,21 +177,32 @@ class SimpleApplet:
|
||||||
else:
|
else:
|
||||||
self.main_widget.data_changed(self.data, [mod])
|
self.main_widget.data_changed(self.data, [mod])
|
||||||
|
|
||||||
def create_subscriber(self):
|
def subscribe(self):
|
||||||
self.subscriber = Subscriber("datasets",
|
if self.args.embed is None:
|
||||||
self.sub_init, self.sub_mod)
|
self.subscriber = Subscriber("datasets",
|
||||||
self.loop.run_until_complete(self.subscriber.connect(
|
self.sub_init, self.sub_mod)
|
||||||
self.args.server_notify, self.args.port_notify))
|
self.loop.run_until_complete(self.subscriber.connect(
|
||||||
|
self.args.server, self.args.port))
|
||||||
|
else:
|
||||||
|
self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod)
|
||||||
|
|
||||||
|
def unsubscribe(self):
|
||||||
|
if self.args.embed is None:
|
||||||
|
self.loop.run_until_complete(self.subscriber.close())
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.args_init()
|
self.args_init()
|
||||||
self.quamash_init()
|
self.quamash_init()
|
||||||
try:
|
try:
|
||||||
self.create_main_widget()
|
self.ipc_init()
|
||||||
self.create_subscriber()
|
|
||||||
try:
|
try:
|
||||||
self.loop.run_forever()
|
self.create_main_widget()
|
||||||
|
self.subscribe()
|
||||||
|
try:
|
||||||
|
self.loop.run_forever()
|
||||||
|
finally:
|
||||||
|
self.unsubscribe()
|
||||||
finally:
|
finally:
|
||||||
self.loop.run_until_complete(self.subscriber.close())
|
self.ipc_close()
|
||||||
finally:
|
finally:
|
||||||
self.loop.close()
|
self.loop.close()
|
||||||
|
|
|
@ -14,7 +14,7 @@ from pyqtgraph import dockarea
|
||||||
|
|
||||||
from artiq import __artiq_dir__ as artiq_dir
|
from artiq import __artiq_dir__ as artiq_dir
|
||||||
from artiq.tools import *
|
from artiq.tools import *
|
||||||
from artiq.protocols.pc_rpc import AsyncioClient, Server
|
from artiq.protocols.pc_rpc import AsyncioClient
|
||||||
from artiq.gui.models import ModelSubscriber
|
from artiq.gui.models import ModelSubscriber
|
||||||
from artiq.gui import (state, experiments, shortcuts, explorer,
|
from artiq.gui import (state, experiments, shortcuts, explorer,
|
||||||
moninj, datasets, applets, schedule, log, console)
|
moninj, datasets, applets, schedule, log, console)
|
||||||
|
@ -113,9 +113,9 @@ def main():
|
||||||
|
|
||||||
d_datasets = datasets.DatasetsDock(win, dock_area, sub_clients["datasets"])
|
d_datasets = datasets.DatasetsDock(win, dock_area, sub_clients["datasets"])
|
||||||
|
|
||||||
appletmgr = applets.AppletManager(dock_area)
|
d_applets = applets.AppletsDock(dock_area, sub_clients["datasets"])
|
||||||
atexit_register_coroutine(appletmgr.stop)
|
atexit_register_coroutine(d_applets.stop)
|
||||||
smgr.register(appletmgr)
|
smgr.register(d_applets)
|
||||||
|
|
||||||
if os.name != "nt":
|
if os.name != "nt":
|
||||||
d_ttl_dds = moninj.MonInj()
|
d_ttl_dds = moninj.MonInj()
|
||||||
|
@ -135,11 +135,11 @@ def main():
|
||||||
if os.name != "nt":
|
if os.name != "nt":
|
||||||
dock_area.addDock(d_ttl_dds.dds_dock, "top")
|
dock_area.addDock(d_ttl_dds.dds_dock, "top")
|
||||||
dock_area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock)
|
dock_area.addDock(d_ttl_dds.ttl_dock, "above", d_ttl_dds.dds_dock)
|
||||||
dock_area.addDock(appletmgr.main_dock, "above", d_ttl_dds.ttl_dock)
|
dock_area.addDock(d_applets, "above", d_ttl_dds.ttl_dock)
|
||||||
dock_area.addDock(d_datasets, "above", appletmgr.main_dock)
|
dock_area.addDock(d_datasets, "above", d_applets)
|
||||||
else:
|
else:
|
||||||
dock_area.addDock(appletmgr.main_dock, "top")
|
dock_area.addDock(d_applets, "top")
|
||||||
dock_area.addDock(d_datasets, "above", appletmgr.main_dock)
|
dock_area.addDock(d_datasets, "above", d_applets)
|
||||||
dock_area.addDock(d_shortcuts, "above", d_datasets)
|
dock_area.addDock(d_shortcuts, "above", d_datasets)
|
||||||
dock_area.addDock(d_explorer, "above", d_shortcuts)
|
dock_area.addDock(d_explorer, "above", d_shortcuts)
|
||||||
dock_area.addDock(d_console, "bottom")
|
dock_area.addDock(d_console, "bottom")
|
||||||
|
@ -155,11 +155,6 @@ def main():
|
||||||
if d_log0 is not None:
|
if d_log0 is not None:
|
||||||
dock_area.addDock(d_log0, "right", d_explorer)
|
dock_area.addDock(d_log0, "right", d_explorer)
|
||||||
|
|
||||||
# start RPC server
|
|
||||||
rpc_server = Server({"applets": appletmgr.rpc})
|
|
||||||
loop.run_until_complete(rpc_server.start("::1", 6501))
|
|
||||||
atexit_register_coroutine(rpc_server.stop)
|
|
||||||
|
|
||||||
# run
|
# run
|
||||||
win.show()
|
win.show()
|
||||||
loop.run_until_complete(win.exit_request.wait())
|
loop.run_until_complete(win.exit_request.wait())
|
||||||
|
|
|
@ -7,17 +7,88 @@ from functools import partial
|
||||||
from quamash import QtCore, QtGui, QtWidgets
|
from quamash import QtCore, QtGui, QtWidgets
|
||||||
from pyqtgraph import dockarea
|
from pyqtgraph import dockarea
|
||||||
|
|
||||||
|
from artiq.protocols.pipe_ipc import AsyncioParentComm
|
||||||
|
from artiq.protocols import pyon
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AppletIPCServer(AsyncioParentComm):
|
||||||
|
def __init__(self, datasets_sub):
|
||||||
|
AsyncioParentComm.__init__(self)
|
||||||
|
self.datasets_sub = datasets_sub
|
||||||
|
self.datasets = set()
|
||||||
|
|
||||||
|
def write_pyon(self, obj):
|
||||||
|
self.write(pyon.encode(obj).encode() + b"\n")
|
||||||
|
|
||||||
|
async def read_pyon(self):
|
||||||
|
line = await self.readline()
|
||||||
|
return pyon.decode(line.decode())
|
||||||
|
|
||||||
|
def _synthesize_init(self, data):
|
||||||
|
struct = {k: v for k, v in data.items() if k in self.datasets}
|
||||||
|
return {"action": "init",
|
||||||
|
"struct": struct}
|
||||||
|
|
||||||
|
def _on_mod(self, mod):
|
||||||
|
if mod["action"] == "init":
|
||||||
|
mod = self._synthesize_init(mod["struct"])
|
||||||
|
else:
|
||||||
|
if mod["path"]:
|
||||||
|
if mod["path"][0] not in self.datasets:
|
||||||
|
return
|
||||||
|
elif mod["action"] in {"setitem", "delitem"}:
|
||||||
|
if mod["key"] not in self.datasets:
|
||||||
|
return
|
||||||
|
self.write_pyon({"action": "mod", "mod": mod})
|
||||||
|
|
||||||
|
async def serve(self, embed_cb):
|
||||||
|
self.datasets_sub.notify_cbs.append(self._on_mod)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
obj = await self.read_pyon()
|
||||||
|
try:
|
||||||
|
action = obj["action"]
|
||||||
|
if action == "embed":
|
||||||
|
embed_cb(obj["win_id"])
|
||||||
|
self.write_pyon({"action": "embed_done"})
|
||||||
|
elif action == "subscribe":
|
||||||
|
self.datasets = obj["datasets"]
|
||||||
|
if self.datasets_sub.model is not None:
|
||||||
|
mod = self._synthesize_init(
|
||||||
|
self.datasets_sub.model.backing_store)
|
||||||
|
self.write_pyon({"action": "mod", "mod": mod})
|
||||||
|
else:
|
||||||
|
raise ValueError("unknown action in applet message")
|
||||||
|
except:
|
||||||
|
logger.warning("error processing applet message",
|
||||||
|
exc_info=True)
|
||||||
|
self.write_pyon({"action": "error"})
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
logger.error("error processing data from applet, "
|
||||||
|
"server stopped", exc_info=True)
|
||||||
|
finally:
|
||||||
|
self.datasets_sub.notify_cbs.remove(self._on_mod)
|
||||||
|
|
||||||
|
def start(self, embed_cb):
|
||||||
|
self.server_task = asyncio.ensure_future(self.serve(embed_cb))
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
self.server_task.cancel()
|
||||||
|
await asyncio.wait([self.server_task])
|
||||||
|
|
||||||
|
|
||||||
class AppletDock(dockarea.Dock):
|
class AppletDock(dockarea.Dock):
|
||||||
def __init__(self, token, name, command):
|
def __init__(self, datasets_sub, uid, name, command):
|
||||||
dockarea.Dock.__init__(self, "applet" + str(token),
|
dockarea.Dock.__init__(self, "applet" + str(uid),
|
||||||
label="Applet: " + name,
|
label="Applet: " + name,
|
||||||
closable=True)
|
closable=True)
|
||||||
self.setMinimumSize(QtCore.QSize(500, 400))
|
self.setMinimumSize(QtCore.QSize(500, 400))
|
||||||
self.token = token
|
self.datasets_sub = datasets_sub
|
||||||
self.applet_name = name
|
self.applet_name = name
|
||||||
self.command = command
|
self.command = command
|
||||||
|
|
||||||
|
@ -26,41 +97,38 @@ class AppletDock(dockarea.Dock):
|
||||||
self.label.setText("Applet: " + name)
|
self.label.setText("Applet: " + name)
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
|
self.ipc = AppletIPCServer(self.datasets_sub)
|
||||||
command = self.command.format(python=sys.executable,
|
command = self.command.format(python=sys.executable,
|
||||||
embed_token=self.token)
|
ipc_address=self.ipc.get_address())
|
||||||
logger.debug("starting command %s for %s", command, self.applet_name)
|
logger.debug("starting command %s for %s", command, self.applet_name)
|
||||||
try:
|
try:
|
||||||
self.process = await asyncio.create_subprocess_exec(
|
await self.ipc.create_subprocess(*shlex.split(command))
|
||||||
*shlex.split(command))
|
|
||||||
except:
|
except:
|
||||||
logger.warning("Applet %s failed to start", self.applet_name,
|
logger.warning("Applet %s failed to start", self.applet_name,
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
|
self.ipc.start(self.embed)
|
||||||
|
|
||||||
def capture(self, win_id):
|
def embed(self, win_id):
|
||||||
logger.debug("capturing window 0x%x for %s", win_id, self.applet_name)
|
logger.debug("capturing window 0x%x for %s", win_id, self.applet_name)
|
||||||
self.captured_window = QtGui.QWindow.fromWinId(win_id)
|
embed_window = QtGui.QWindow.fromWinId(win_id)
|
||||||
self.captured_widget = QtWidgets.QWidget.createWindowContainer(
|
embed_widget = QtWidgets.QWidget.createWindowContainer(embed_window)
|
||||||
self.captured_window)
|
self.addWidget(embed_widget)
|
||||||
self.addWidget(self.captured_widget)
|
|
||||||
|
|
||||||
async def terminate(self):
|
async def terminate(self):
|
||||||
if hasattr(self, "captured_window"):
|
if hasattr(self, "ipc"):
|
||||||
self.captured_window.close()
|
await self.ipc.stop()
|
||||||
self.captured_widget.deleteLater()
|
self.ipc.write_pyon({"action": "terminate"})
|
||||||
del self.captured_window
|
|
||||||
del self.captured_widget
|
|
||||||
if hasattr(self, "process"):
|
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self.process.wait(), 2.0)
|
await asyncio.wait_for(self.ipc.process.wait(), 2.0)
|
||||||
except:
|
except:
|
||||||
logger.warning("Applet %s failed to exit, killing",
|
logger.warning("Applet %s failed to exit, killing",
|
||||||
self.applet_name)
|
self.applet_name)
|
||||||
try:
|
try:
|
||||||
self.process.kill()
|
self.ipc.process.kill()
|
||||||
except ProcessLookupError:
|
except ProcessLookupError:
|
||||||
pass
|
pass
|
||||||
await self.process.wait()
|
await self.ipc.process.wait()
|
||||||
del self.process
|
del self.ipc
|
||||||
|
|
||||||
async def restart(self):
|
async def restart(self):
|
||||||
await self.terminate()
|
await self.terminate()
|
||||||
|
@ -69,24 +137,27 @@ class AppletDock(dockarea.Dock):
|
||||||
|
|
||||||
_templates = [
|
_templates = [
|
||||||
("Big number", "{python} -m artiq.applets.big_number "
|
("Big number", "{python} -m artiq.applets.big_number "
|
||||||
"--embed {embed_token} NUMBER_DATASET"),
|
"--embed {ipc_address} NUMBER_DATASET"),
|
||||||
("Histogram", "{python} -m artiq.applets.plot_hist "
|
("Histogram", "{python} -m artiq.applets.plot_hist "
|
||||||
"--embed {embed_token} COUNTS_DATASET "
|
"--embed {ipc_address} COUNTS_DATASET "
|
||||||
"--x BIN_BOUNDARIES_DATASET"),
|
"--x BIN_BOUNDARIES_DATASET"),
|
||||||
("XY", "{python} -m artiq.applets.plot_xy "
|
("XY", "{python} -m artiq.applets.plot_xy "
|
||||||
"--embed {embed_token} Y_DATASET --x X_DATASET "
|
"--embed {ipc_address} Y_DATASET --x X_DATASET "
|
||||||
"--error ERROR_DATASET --fit FIT_DATASET"),
|
"--error ERROR_DATASET --fit FIT_DATASET"),
|
||||||
("XY + Histogram", "{python} -m artiq.applets.plot_xy_hist "
|
("XY + Histogram", "{python} -m artiq.applets.plot_xy_hist "
|
||||||
"--embed {embed_token} X_DATASET "
|
"--embed {ipc_address} X_DATASET "
|
||||||
"HIST_BIN_BOUNDARIES_DATASET "
|
"HIST_BIN_BOUNDARIES_DATASET "
|
||||||
"HISTS_COUNTS_DATASET"),
|
"HISTS_COUNTS_DATASET"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AppletsDock(dockarea.Dock):
|
class AppletsDock(dockarea.Dock):
|
||||||
def __init__(self, manager):
|
def __init__(self, dock_area, datasets_sub):
|
||||||
self.manager = manager
|
self.dock_area = dock_area
|
||||||
self.token_to_checkbox = dict()
|
self.datasets_sub = datasets_sub
|
||||||
|
self.dock_to_checkbox = dict()
|
||||||
|
self.applet_uids = set()
|
||||||
|
self.workaround_pyqtgraph_bug = False
|
||||||
|
|
||||||
dockarea.Dock.__init__(self, "Applets")
|
dockarea.Dock.__init__(self, "Applets")
|
||||||
self.setMinimumSize(QtCore.QSize(850, 450))
|
self.setMinimumSize(QtCore.QSize(850, 450))
|
||||||
|
@ -129,6 +200,18 @@ class AppletsDock(dockarea.Dock):
|
||||||
|
|
||||||
self.table.cellChanged.connect(self.cell_changed)
|
self.table.cellChanged.connect(self.cell_changed)
|
||||||
|
|
||||||
|
def create(self, uid, name, command):
|
||||||
|
dock = AppletDock(self.datasets_sub, uid, name, command)
|
||||||
|
# If a dock is floated and then dock state is restored, pyqtgraph
|
||||||
|
# leaves a "phantom" window open.
|
||||||
|
if self.workaround_pyqtgraph_bug:
|
||||||
|
self.dock_area.addDock(dock)
|
||||||
|
else:
|
||||||
|
self.dock_area.floatDock(dock)
|
||||||
|
asyncio.ensure_future(dock.start())
|
||||||
|
dock.sigClosed.connect(partial(self.on_dock_closed, dock))
|
||||||
|
return dock
|
||||||
|
|
||||||
def cell_changed(self, row, column):
|
def cell_changed(self, row, column):
|
||||||
if column == 0:
|
if column == 0:
|
||||||
item = self.table.item(row, column)
|
item = self.table.item(row, column)
|
||||||
|
@ -141,30 +224,36 @@ class AppletsDock(dockarea.Dock):
|
||||||
name = ""
|
name = ""
|
||||||
else:
|
else:
|
||||||
name = name.text()
|
name = name.text()
|
||||||
token = self.manager.create(name, command)
|
dock = self.create(item.applet_uid, name, command)
|
||||||
item.applet_token = token
|
item.applet_dock = dock
|
||||||
self.token_to_checkbox[token] = item
|
self.dock_to_checkbox[dock] = item
|
||||||
else:
|
else:
|
||||||
token = getattr(item, "applet_token", None)
|
dock = item.applet_dock
|
||||||
if token is not None:
|
if dock is not None:
|
||||||
# cell_changed is emitted at row creation
|
# This calls self.on_dock_closed
|
||||||
self.manager.delete(token)
|
dock.close()
|
||||||
elif column == 1 or column == 2:
|
elif column == 1 or column == 2:
|
||||||
new_value = self.table.item(row, column).text()
|
new_value = self.table.item(row, column).text()
|
||||||
token = getattr(self.table.item(row, 0), "applet_token", None)
|
dock = self.table.item(row, 0).applet_dock
|
||||||
if token is not None:
|
if dock is not None:
|
||||||
if column == 1:
|
if column == 1:
|
||||||
self.manager.rename(token, new_value)
|
dock.rename(new_value)
|
||||||
else:
|
else:
|
||||||
self.manager.set_command(token, new_value)
|
dock.command = new_value
|
||||||
|
|
||||||
def disable_token(self, token):
|
def on_dock_closed(self, dock):
|
||||||
checkbox_item = self.token_to_checkbox[token]
|
asyncio.ensure_future(dock.terminate())
|
||||||
checkbox_item.applet_token = None
|
checkbox_item = self.dock_to_checkbox[dock]
|
||||||
del self.token_to_checkbox[token]
|
checkbox_item.applet_dock = None
|
||||||
|
del self.dock_to_checkbox[dock]
|
||||||
checkbox_item.setCheckState(QtCore.Qt.Unchecked)
|
checkbox_item.setCheckState(QtCore.Qt.Unchecked)
|
||||||
|
|
||||||
def new(self):
|
def new(self, uid=None):
|
||||||
|
if uid is None:
|
||||||
|
uid = next(iter(set(range(len(self.applet_uids) + 1))
|
||||||
|
- self.applet_uids))
|
||||||
|
self.applet_uids.add(uid)
|
||||||
|
|
||||||
row = self.table.rowCount()
|
row = self.table.rowCount()
|
||||||
self.table.insertRow(row)
|
self.table.insertRow(row)
|
||||||
checkbox = QtWidgets.QTableWidgetItem()
|
checkbox = QtWidgets.QTableWidgetItem()
|
||||||
|
@ -172,6 +261,8 @@ class AppletsDock(dockarea.Dock):
|
||||||
QtCore.Qt.ItemIsUserCheckable |
|
QtCore.Qt.ItemIsUserCheckable |
|
||||||
QtCore.Qt.ItemIsEnabled)
|
QtCore.Qt.ItemIsEnabled)
|
||||||
checkbox.setCheckState(QtCore.Qt.Unchecked)
|
checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||||
|
checkbox.applet_uid = uid
|
||||||
|
checkbox.applet_dock = None
|
||||||
self.table.setItem(row, 0, checkbox)
|
self.table.setItem(row, 0, checkbox)
|
||||||
self.table.setItem(row, 1, QtWidgets.QTableWidgetItem())
|
self.table.setItem(row, 1, QtWidgets.QTableWidgetItem())
|
||||||
self.table.setItem(row, 2, QtWidgets.QTableWidgetItem())
|
self.table.setItem(row, 2, QtWidgets.QTableWidgetItem())
|
||||||
|
@ -185,31 +276,43 @@ class AppletsDock(dockarea.Dock):
|
||||||
selection = self.table.selectedRanges()
|
selection = self.table.selectedRanges()
|
||||||
if selection:
|
if selection:
|
||||||
row = selection[0].topRow()
|
row = selection[0].topRow()
|
||||||
token = getattr(self.table.item(row, 0), "applet_token", None)
|
dock = self.table.item(row, 0).applet_dock
|
||||||
if token is not None:
|
if dock is not None:
|
||||||
asyncio.ensure_future(self.manager.restart(token))
|
asyncio.ensure_future(dock.restart())
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
selection = self.table.selectedRanges()
|
selection = self.table.selectedRanges()
|
||||||
if selection:
|
if selection:
|
||||||
row = selection[0].topRow()
|
row = selection[0].topRow()
|
||||||
token = getattr(self.table.item(row, 0), "applet_token", None)
|
item = self.table.item(row, 0)
|
||||||
if token is not None:
|
dock = item.applet_dock
|
||||||
self.manager.delete(token)
|
if dock is not None:
|
||||||
|
# This calls self.on_dock_closed
|
||||||
|
dock.close()
|
||||||
|
self.applet_uids.remove(item.applet_uid)
|
||||||
self.table.removeRow(row)
|
self.table.removeRow(row)
|
||||||
|
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
for row in range(self.table.rowCount()):
|
||||||
|
dock = self.table.item(row, 0).applet_dock
|
||||||
|
if dock is not None:
|
||||||
|
await dock.terminate()
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
state = []
|
state = []
|
||||||
for row in range(self.table.rowCount()):
|
for row in range(self.table.rowCount()):
|
||||||
|
uid = self.table.item(row, 0).applet_uid
|
||||||
enabled = self.table.item(row, 0).checkState() == QtCore.Qt.Checked
|
enabled = self.table.item(row, 0).checkState() == QtCore.Qt.Checked
|
||||||
name = self.table.item(row, 1).text()
|
name = self.table.item(row, 1).text()
|
||||||
command = self.table.item(row, 2).text()
|
command = self.table.item(row, 2).text()
|
||||||
state.append((enabled, name, command))
|
state.append((uid, enabled, name, command))
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def restore_state(self, state):
|
def restore_state(self, state):
|
||||||
for enabled, name, command in state:
|
self.workaround_pyqtgraph_bug = True
|
||||||
row = self.new()
|
for uid, enabled, name, command in state:
|
||||||
|
row = self.new(uid)
|
||||||
item = QtWidgets.QTableWidgetItem()
|
item = QtWidgets.QTableWidgetItem()
|
||||||
item.setText(name)
|
item.setText(name)
|
||||||
self.table.setItem(row, 1, item)
|
self.table.setItem(row, 1, item)
|
||||||
|
@ -218,72 +321,4 @@ class AppletsDock(dockarea.Dock):
|
||||||
self.table.setItem(row, 2, item)
|
self.table.setItem(row, 2, item)
|
||||||
if enabled:
|
if enabled:
|
||||||
self.table.item(row, 0).setCheckState(QtCore.Qt.Checked)
|
self.table.item(row, 0).setCheckState(QtCore.Qt.Checked)
|
||||||
|
|
||||||
|
|
||||||
class AppletManagerRPC:
|
|
||||||
def __init__(self, parent):
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def embed(self, token, win_id):
|
|
||||||
self.parent.embed(token, win_id)
|
|
||||||
|
|
||||||
|
|
||||||
class AppletManager:
|
|
||||||
def __init__(self, dock_area):
|
|
||||||
self.dock_area = dock_area
|
|
||||||
self.main_dock = AppletsDock(self)
|
|
||||||
self.rpc = AppletManagerRPC(self)
|
|
||||||
self.applet_docks = dict()
|
|
||||||
self.workaround_pyqtgraph_bug = False
|
|
||||||
|
|
||||||
def embed(self, token, win_id):
|
|
||||||
if token not in self.applet_docks:
|
|
||||||
logger.warning("Ignored incorrect embed token %d for winid 0x%x",
|
|
||||||
token, win_id)
|
|
||||||
return
|
|
||||||
self.applet_docks[token].capture(win_id)
|
|
||||||
|
|
||||||
def create(self, name, command):
|
|
||||||
token = next(iter(set(range(len(self.applet_docks) + 1))
|
|
||||||
- self.applet_docks.keys()))
|
|
||||||
dock = AppletDock(token, name, command)
|
|
||||||
self.applet_docks[token] = dock
|
|
||||||
# If a dock is floated and then dock state is restored, pyqtgraph
|
|
||||||
# leaves a "phantom" window open.
|
|
||||||
if self.workaround_pyqtgraph_bug:
|
|
||||||
self.dock_area.addDock(dock)
|
|
||||||
else:
|
|
||||||
self.dock_area.floatDock(dock)
|
|
||||||
asyncio.ensure_future(dock.start())
|
|
||||||
dock.sigClosed.connect(partial(self.on_dock_closed, token))
|
|
||||||
return token
|
|
||||||
|
|
||||||
def on_dock_closed(self, token):
|
|
||||||
asyncio.ensure_future(self.applet_docks[token].terminate())
|
|
||||||
self.main_dock.disable_token(token)
|
|
||||||
del self.applet_docks[token]
|
|
||||||
|
|
||||||
def delete(self, token):
|
|
||||||
# This in turns calls on_dock_closed and main_dock.disable_token
|
|
||||||
self.applet_docks[token].close()
|
|
||||||
|
|
||||||
def rename(self, token, name):
|
|
||||||
self.applet_docks[token].rename(name)
|
|
||||||
|
|
||||||
def set_command(self, token, command):
|
|
||||||
self.applet_docks[token].command = command
|
|
||||||
|
|
||||||
async def restart(self, token):
|
|
||||||
await self.applet_docks[token].restart()
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
for dock in self.applet_docks.values():
|
|
||||||
await dock.terminate()
|
|
||||||
|
|
||||||
def save_state(self):
|
|
||||||
return self.main_dock.save_state()
|
|
||||||
|
|
||||||
def restore_state(self, state):
|
|
||||||
self.workaround_pyqtgraph_bug = True
|
|
||||||
self.main_dock.restore_state(state)
|
|
||||||
self.workaround_pyqtgraph_bug = False
|
self.workaround_pyqtgraph_bug = False
|
||||||
|
|
|
@ -35,6 +35,7 @@ _encode_map = {
|
||||||
bytes: "bytes",
|
bytes: "bytes",
|
||||||
tuple: "tuple",
|
tuple: "tuple",
|
||||||
list: "list",
|
list: "list",
|
||||||
|
set: "set",
|
||||||
dict: "dict",
|
dict: "dict",
|
||||||
wrapping_int: "number",
|
wrapping_int: "number",
|
||||||
Fraction: "fraction",
|
Fraction: "fraction",
|
||||||
|
@ -98,6 +99,12 @@ class _Encoder:
|
||||||
r += "]"
|
r += "]"
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
def encode_set(self, x):
|
||||||
|
r = "{"
|
||||||
|
r += ", ".join([self.encode(item) for item in x])
|
||||||
|
r += "}"
|
||||||
|
return r
|
||||||
|
|
||||||
def encode_dict(self, x):
|
def encode_dict(self, x):
|
||||||
r = "{"
|
r = "{"
|
||||||
if not self.pretty or len(x) < 2:
|
if not self.pretty or len(x) < 2:
|
||||||
|
@ -149,9 +156,7 @@ class _Encoder:
|
||||||
|
|
||||||
def encode(x, pretty=False):
|
def encode(x, pretty=False):
|
||||||
"""Serializes a Python object and returns the corresponding string in
|
"""Serializes a Python object and returns the corresponding string in
|
||||||
Python syntax.
|
Python syntax."""
|
||||||
|
|
||||||
"""
|
|
||||||
return _Encoder(pretty).encode(x)
|
return _Encoder(pretty).encode(x)
|
||||||
|
|
||||||
|
|
||||||
|
@ -181,9 +186,7 @@ _eval_dict = {
|
||||||
|
|
||||||
def decode(s):
|
def decode(s):
|
||||||
"""Parses a string in the Python syntax, reconstructs the corresponding
|
"""Parses a string in the Python syntax, reconstructs the corresponding
|
||||||
object, and returns it.
|
object, and returns it."""
|
||||||
|
|
||||||
"""
|
|
||||||
return eval(s, _eval_dict, {})
|
return eval(s, _eval_dict, {})
|
||||||
|
|
||||||
|
|
||||||
|
@ -202,23 +205,3 @@ def load_file(filename):
|
||||||
"""Parses the specified file and returns the decoded Python object."""
|
"""Parses the specified file and returns the decoded Python object."""
|
||||||
with open(filename, "r") as f:
|
with open(filename, "r") as f:
|
||||||
return decode(f.read())
|
return decode(f.read())
|
||||||
|
|
||||||
|
|
||||||
class FlatFileDB:
|
|
||||||
def __init__(self, filename):
|
|
||||||
self.filename = filename
|
|
||||||
self.data = pyon.load_file(self.filename)
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
pyon.store_file(self.filename, self.data)
|
|
||||||
|
|
||||||
def get(self, key):
|
|
||||||
return self.data[key]
|
|
||||||
|
|
||||||
def set(self, key, value):
|
|
||||||
self.data[key] = value
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def delete(self, key):
|
|
||||||
del self.data[key]
|
|
||||||
self.save()
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ from artiq.protocols import pyon
|
||||||
_pyon_test_object = {
|
_pyon_test_object = {
|
||||||
(1, 2): [(3, 4.2), (2, )],
|
(1, 2): [(3, 4.2), (2, )],
|
||||||
Fraction(3, 4): np.linspace(5, 10, 1),
|
Fraction(3, 4): np.linspace(5, 10, 1),
|
||||||
|
{"testing", "sets"},
|
||||||
"a": np.int8(9), "b": np.int16(-98), "c": np.int32(42), "d": np.int64(-5),
|
"a": np.int8(9), "b": np.int16(-98), "c": np.int32(42), "d": np.int64(-5),
|
||||||
"e": np.uint8(8), "f": np.uint16(5), "g": np.uint32(4), "h": np.uint64(9),
|
"e": np.uint8(8), "f": np.uint16(5), "g": np.uint32(4), "h": np.uint64(9),
|
||||||
"x": np.float16(9.0), "y": np.float32(9.0), "z": np.float64(9.0),
|
"x": np.float16(9.0), "y": np.float32(9.0), "z": np.float64(9.0),
|
||||||
|
|
|
@ -2,7 +2,7 @@ from time import sleep
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from artiq import *
|
from artiq.experiment import *
|
||||||
|
|
||||||
|
|
||||||
class Histograms(EnvExperiment):
|
class Histograms(EnvExperiment):
|
||||||
|
|
Loading…
Reference in New Issue