mirror of https://github.com/m-labs/artiq.git
pc_rpc: support for method listing, frontend: artiq_rpctool, +fixes by SB
This commit is contained in:
parent
6d11da3887
commit
b396f5dd43
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
from artiq.protocols.pc_rpc import Client
|
|
||||||
|
|
||||||
|
|
||||||
def get_argparser():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="ARTIQ controller identification tool")
|
|
||||||
parser.add_argument("server",
|
|
||||||
help="hostname or IP of the controller to connect to")
|
|
||||||
parser.add_argument("port", type=int,
|
|
||||||
help="TCP port to use to connect to the controller")
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = get_argparser().parse_args()
|
|
||||||
remote = Client(args.server, args.port, None)
|
|
||||||
try:
|
|
||||||
target_names, id_parameters = remote.get_rpc_id()
|
|
||||||
finally:
|
|
||||||
remote.close_rpc()
|
|
||||||
print("Target(s): " + ", ".join(target_names))
|
|
||||||
if id_parameters is not None:
|
|
||||||
print("Parameters: " + id_parameters)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import textwrap
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from artiq.protocols.pc_rpc import Client
|
||||||
|
|
||||||
|
|
||||||
|
def get_argparser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="ARTIQ RPC tool")
|
||||||
|
parser.add_argument("server",
|
||||||
|
help="hostname or IP of the controller to connect to")
|
||||||
|
parser.add_argument("port", type=int,
|
||||||
|
help="TCP port to use to connect to the controller")
|
||||||
|
subparsers = parser.add_subparsers(dest="action")
|
||||||
|
subparsers.required = True
|
||||||
|
subparsers.add_parser("list-targets", help="list existing targets")
|
||||||
|
parser_list_methods = subparsers.add_parser("list-methods",
|
||||||
|
help="list target's methods")
|
||||||
|
parser_list_methods.add_argument("-t", "--target", help="target name")
|
||||||
|
parser_call = subparsers.add_parser("call", help="call a target's method")
|
||||||
|
parser_call.add_argument("-t", "--target", help="target name")
|
||||||
|
parser_call.add_argument("method", help="method name")
|
||||||
|
parser_call.add_argument("args", nargs=argparse.REMAINDER,
|
||||||
|
help="arguments")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def list_targets(target_names, id_parameters):
|
||||||
|
print("Target(s): " + ", ".join(target_names))
|
||||||
|
if id_parameters is not None:
|
||||||
|
print("Parameters: " + id_parameters)
|
||||||
|
|
||||||
|
|
||||||
|
def list_methods(remote):
|
||||||
|
methods = remote.get_rpc_method_list()
|
||||||
|
for name, (argspec, docstring) in sorted(methods.items()):
|
||||||
|
args = ""
|
||||||
|
for arg in argspec["args"]:
|
||||||
|
args += arg
|
||||||
|
if argspec["defaults"] is not None:
|
||||||
|
kword_index = len(argspec["defaults"]) - len(argspec["args"])\
|
||||||
|
+ argspec["args"].index(arg)
|
||||||
|
if kword_index >= 0:
|
||||||
|
if argspec["defaults"][kword_index] == Ellipsis:
|
||||||
|
args += "=..."
|
||||||
|
else:
|
||||||
|
args += "={}".format(argspec["defaults"][kword_index])
|
||||||
|
if argspec["args"].index(arg) < len(argspec["args"]) - 1:
|
||||||
|
args += ", "
|
||||||
|
if argspec["varargs"] is not None:
|
||||||
|
args += ", *{}".format(argspec["varargs"])
|
||||||
|
elif len(argspec["kwonlyargs"]) > 0:
|
||||||
|
args += ", *"
|
||||||
|
for kwonlyarg in argspec["kwonlyargs"]:
|
||||||
|
args += ", {}".format(kwonlyarg)
|
||||||
|
if kwonlyarg in argspec["kwonlydefaults"]:
|
||||||
|
if argspec["kwonlydefaults"][kwonlyarg] == Ellipsis:
|
||||||
|
args += "=..."
|
||||||
|
else:
|
||||||
|
args += "={}".format(argspec["kwonlydefaults"][kwonlyarg])
|
||||||
|
if argspec["varkw"] is not None:
|
||||||
|
args += ", **{}".format(argspec["varkw"])
|
||||||
|
print("{}({})".format(name, args))
|
||||||
|
if docstring is not None:
|
||||||
|
print(textwrap.indent(docstring, " "))
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def call_method(remote, method_name, args):
|
||||||
|
method = getattr(remote, method_name)
|
||||||
|
if args != []:
|
||||||
|
args = eval(" ".join(args))
|
||||||
|
try:
|
||||||
|
iter(args)
|
||||||
|
except TypeError:
|
||||||
|
# not iterable
|
||||||
|
ret = method(args)
|
||||||
|
else:
|
||||||
|
# iterable
|
||||||
|
ret = method(*args)
|
||||||
|
else:
|
||||||
|
ret = method()
|
||||||
|
if ret is not None:
|
||||||
|
print("{}".format(ret))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_argparser().parse_args()
|
||||||
|
|
||||||
|
remote = Client(args.server, args.port, None)
|
||||||
|
|
||||||
|
targets, id_parameters = remote.get_rpc_id()
|
||||||
|
|
||||||
|
if args.action != "list-targets":
|
||||||
|
# If no target specified and remote has only one, then use this one.
|
||||||
|
# Exit otherwise.
|
||||||
|
if len(targets) > 1 and args.target is None:
|
||||||
|
print("Remote server has several targets, please supply one with "
|
||||||
|
"-t")
|
||||||
|
sys.exit(1)
|
||||||
|
elif args.target is None:
|
||||||
|
args.target = targets[0]
|
||||||
|
remote.select_rpc_target(args.target)
|
||||||
|
|
||||||
|
if args.action == "list-targets":
|
||||||
|
list_targets(targets, id_parameters)
|
||||||
|
elif args.action == "list-methods":
|
||||||
|
list_methods(remote)
|
||||||
|
elif args.action == "call":
|
||||||
|
call_method(remote, args.method, args.args)
|
||||||
|
else:
|
||||||
|
print("Unrecognized action: {}".format(args.action))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -18,6 +18,7 @@ import traceback
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import inspect
|
||||||
|
|
||||||
from artiq.protocols import pyon
|
from artiq.protocols import pyon
|
||||||
from artiq.protocols.asyncio_server import AsyncioServer as _AsyncioServer
|
from artiq.protocols.asyncio_server import AsyncioServer as _AsyncioServer
|
||||||
|
@ -127,9 +128,8 @@ class Client:
|
||||||
buf += more.decode()
|
buf += more.decode()
|
||||||
return pyon.decode(buf)
|
return pyon.decode(buf)
|
||||||
|
|
||||||
def __do_rpc(self, name, args, kwargs):
|
def __do_action(self, action):
|
||||||
obj = {"action": "call", "name": name, "args": args, "kwargs": kwargs}
|
self.__send(action)
|
||||||
self.__send(obj)
|
|
||||||
|
|
||||||
obj = self.__recv()
|
obj = self.__recv()
|
||||||
if obj["status"] == "ok":
|
if obj["status"] == "ok":
|
||||||
|
@ -139,6 +139,14 @@ class Client:
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
def __do_rpc(self, name, args, kwargs):
|
||||||
|
obj = {"action": "call", "name": name, "args": args, "kwargs": kwargs}
|
||||||
|
return self.__do_action(obj)
|
||||||
|
|
||||||
|
def get_rpc_method_list(self):
|
||||||
|
obj = {"action": "get_rpc_method_list"}
|
||||||
|
return self.__do_action(obj)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
def proxy(*args, **kwargs):
|
def proxy(*args, **kwargs):
|
||||||
return self.__do_rpc(name, args, kwargs)
|
return self.__do_rpc(name, args, kwargs)
|
||||||
|
@ -397,9 +405,24 @@ class Server(_AsyncioServer):
|
||||||
break
|
break
|
||||||
obj = pyon.decode(line.decode())
|
obj = pyon.decode(line.decode())
|
||||||
try:
|
try:
|
||||||
|
if obj["action"] == "get_rpc_method_list":
|
||||||
|
members = inspect.getmembers(target, inspect.ismethod)
|
||||||
|
methods = {}
|
||||||
|
for name, method in members:
|
||||||
|
if name.startswith("_"):
|
||||||
|
continue
|
||||||
|
method = getattr(target, name)
|
||||||
|
argspec = inspect.getfullargspec(method)
|
||||||
|
methods[name] = (dict(argspec.__dict__),
|
||||||
|
inspect.getdoc(method))
|
||||||
|
obj = {"status": "ok", "ret": methods}
|
||||||
|
elif obj["action"] == "call":
|
||||||
method = getattr(target, obj["name"])
|
method = getattr(target, obj["name"])
|
||||||
ret = method(*obj["args"], **obj["kwargs"])
|
ret = method(*obj["args"], **obj["kwargs"])
|
||||||
obj = {"status": "ok", "ret": ret}
|
obj = {"status": "ok", "ret": ret}
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown action: {}"
|
||||||
|
.format(obj["action"]))
|
||||||
except Exception:
|
except Exception:
|
||||||
obj = {"status": "failed",
|
obj = {"status": "failed",
|
||||||
"message": traceback.format_exc()}
|
"message": traceback.format_exc()}
|
||||||
|
|
|
@ -11,7 +11,7 @@ build:
|
||||||
string: dev
|
string: dev
|
||||||
entry_points:
|
entry_points:
|
||||||
- artiq_client = artiq.frontend.artiq_client:main
|
- artiq_client = artiq.frontend.artiq_client:main
|
||||||
- artiq_ctlid = artiq.frontend.artiq_ctlid:main
|
- artiq_rpctool = artiq.frontend.artiq_rpctool:main
|
||||||
- artiq_gui = artiq.frontend.artiq_gui:main # [not win]
|
- artiq_gui = artiq.frontend.artiq_gui:main # [not win]
|
||||||
- artiq_master = artiq.frontend.artiq_master:main
|
- artiq_master = artiq.frontend.artiq_master:main
|
||||||
- artiq_run = artiq.frontend.artiq_run:main
|
- artiq_run = artiq.frontend.artiq_run:main
|
||||||
|
|
|
@ -8,9 +8,72 @@ Local running tool
|
||||||
:ref: artiq.frontend.artiq_run.get_argparser
|
:ref: artiq.frontend.artiq_run.get_argparser
|
||||||
:prog: artiq_run
|
:prog: artiq_run
|
||||||
|
|
||||||
Controller identification tool
|
Remote Procedure Call tool
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
.. argparse::
|
.. argparse::
|
||||||
:ref: artiq.frontend.artiq_ctlid.get_argparser
|
:ref: artiq.frontend.artiq_rpctool.get_argparser
|
||||||
:prog: artiq_ctlid
|
:prog: artiq_rpctool
|
||||||
|
|
||||||
|
This tool is the preferred way of handling simple ARTIQ controllers.
|
||||||
|
Instead of writing a client for very simple cases you can just use this tool
|
||||||
|
in order to call remote functions of an ARTIQ controller.
|
||||||
|
|
||||||
|
* Listing existing targets
|
||||||
|
|
||||||
|
The ``list-targets`` sub-command will print to standard output the
|
||||||
|
target list of the remote server::
|
||||||
|
|
||||||
|
$ artiq_rpctool.py hostname port list-targets
|
||||||
|
|
||||||
|
* Listing callable functions
|
||||||
|
|
||||||
|
The ``list-methods`` sub-command will print to standard output a sorted
|
||||||
|
list of the functions you can call on the remote server's target.
|
||||||
|
|
||||||
|
The list will contain function names, signatures (arguments) and
|
||||||
|
docstrings.
|
||||||
|
|
||||||
|
If the server has only one target, you can do::
|
||||||
|
|
||||||
|
$ artiq_rpctool.py hostname port list-methods
|
||||||
|
|
||||||
|
Otherwise you need to specify the target, using the ``-t target``
|
||||||
|
option::
|
||||||
|
|
||||||
|
$ artiq_rpctool.py hostname port list-methods -t target_name
|
||||||
|
|
||||||
|
* Remotely calling a function
|
||||||
|
|
||||||
|
The ``call`` sub-command will call a function on the specified remote
|
||||||
|
server's target, passing the specified arguments.
|
||||||
|
Like with the previous sub-command, you only need to provide the target
|
||||||
|
name (with ``-t target``) if the server hosts several targets.
|
||||||
|
|
||||||
|
The following example will call the ``set_attenuation`` method of the
|
||||||
|
Lda controller with the argument ``5``::
|
||||||
|
|
||||||
|
$ artiq_rpctool.py ::1 3253 call -t lda set_attenuation 5
|
||||||
|
|
||||||
|
In general, to call a function named ``f`` with N arguments named
|
||||||
|
respectively ``x1, x2, ..., xN``.
|
||||||
|
|
||||||
|
You must pass them as a Python iterable object::
|
||||||
|
|
||||||
|
$ artiq_rpctool.py hostname port call -t target f '(x1, x2, ..., xN)'
|
||||||
|
|
||||||
|
You can use Python syntax to compute arguments as they will be passed
|
||||||
|
to the ``eval()`` primitive::
|
||||||
|
|
||||||
|
$ artiq_rpctool.py hostname port call -t target f '(x*3+5 for x in range(8))'
|
||||||
|
$ artiq_rpctool.py hostname port call -t target f 'range(5)'
|
||||||
|
|
||||||
|
If you only need one argument, you don't need to pass an iterable, a
|
||||||
|
single value is accepted.
|
||||||
|
|
||||||
|
If the called function has a return value, it will get printed to
|
||||||
|
the standard output if the value is not None like in the standard
|
||||||
|
python interactive console::
|
||||||
|
|
||||||
|
$ artiq_rpctool.py ::1 3253 call get_attenuation
|
||||||
|
5.0 dB
|
||||||
|
|
|
@ -51,9 +51,9 @@ and verify that you can connect to the TCP port: ::
|
||||||
|
|
||||||
:tip: Use the key combination Ctrl-AltGr-9 to get the ``telnet>`` prompt, and enter ``close`` to quit Telnet. Quit the controller with Ctrl-C.
|
:tip: Use the key combination Ctrl-AltGr-9 to get the ``telnet>`` prompt, and enter ``close`` to quit Telnet. Quit the controller with Ctrl-C.
|
||||||
|
|
||||||
Also verify that a target (service) named "hello" (as passed in the first argument to ``simple_server_loop``) exists using the ``artiq_ctlid.py`` program from the ARTIQ front-end tools: ::
|
Also verify that a target (service) named "hello" (as passed in the first argument to ``simple_server_loop``) exists using the ``artiq_rpctool.py`` program from the ARTIQ front-end tools: ::
|
||||||
|
|
||||||
$ artiq_ctlid.py ::1 3249
|
$ artiq_rpctool.py ::1 3249 list-targets
|
||||||
Target(s): hello
|
Target(s): hello
|
||||||
|
|
||||||
The client
|
The client
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -10,7 +10,7 @@ requirements = [
|
||||||
]
|
]
|
||||||
scripts = [
|
scripts = [
|
||||||
"artiq_client=artiq.frontend.artiq_client:main",
|
"artiq_client=artiq.frontend.artiq_client:main",
|
||||||
"artiq_ctlid=artiq.frontend.artiq_ctlid:main",
|
"artiq_rpctool=artiq.frontend.artiq_rpctool:main",
|
||||||
"artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main",
|
"artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main",
|
||||||
"artiq_master=artiq.frontend.artiq_master:main",
|
"artiq_master=artiq.frontend.artiq_master:main",
|
||||||
"artiq_run=artiq.frontend.artiq_run:main",
|
"artiq_run=artiq.frontend.artiq_run:main",
|
||||||
|
|
Loading…
Reference in New Issue