artiq/doc/manual/writing_a_driver.rst

117 lines
5.2 KiB
ReStructuredText
Raw Normal View History

2014-10-27 20:38:02 +08:00
Writing a driver
================
2014-10-28 11:43:06 +08:00
These instructions cover writing a simple driver for a "slow" device, that uses the controller mechanism.
2014-10-27 20:38:02 +08:00
The controller
--------------
A controller is a piece of software that receives commands from a client over the network (or the ``localhost`` interface), drives a device, and returns information about the device to the client. The mechanism used is remote procedure calls (RPCs) using :class:`artiq.protocols.pc_rpc`, which makes the network layers transparent for the driver's user.
2014-10-27 20:38:02 +08:00
The controller we will develop is for a "device" that is very easy to work with: the console from which the controller is run. The operation that the driver will implement is writing a message to that console.
For using RPC, the functions that a driver provides must be the methods of a single object. We will thus define a class that provides our message-printing method: ::
class Hello:
def message(self, msg):
print("message: " + msg)
To turn it into a server, we use :class:`artiq.protocols.pc_rpc`. Import the function we will use: ::
2014-10-27 20:38:02 +08:00
from artiq.protocols.pc_rpc import simple_server_loop
2014-10-27 20:38:02 +08:00
and add a ``main`` function that is run when the program is executed: ::
def main():
2014-12-31 20:13:10 +08:00
simple_server_loop({"hello": Hello()}, "::1", 7777)
2014-10-27 20:38:02 +08:00
if __name__ == "__main__":
main()
The parameters ``::1`` and 7777 are respectively the address to bind the server to (IPv6 localhost) and the TCP port to use. Then add a line: ::
#!/usr/bin/env python3
at the beginning of the file, save it to ``hello_controller.py`` and set its execution permissions: ::
2014-10-27 20:38:02 +08:00
$ chmod 755 hello_controller.py
2014-10-27 20:38:02 +08:00
Run it as: ::
$ ./hello_controller.py
2014-10-27 20:38:02 +08:00
and verify that you can connect to the TCP port: ::
$ telnet ::1 7777
Trying ::1...
Connected to ::1.
Escape character is '^]'.
: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.
2014-12-31 20:13:10 +08:00
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: ::
2014-10-28 15:45:56 +08:00
2014-12-08 16:12:39 +08:00
$ artiq_ctlid.py ::1 7777
2014-12-31 20:13:10 +08:00
Target(s): hello
2014-10-28 15:45:56 +08:00
2014-10-27 20:38:02 +08:00
The client
----------
2014-10-28 11:43:06 +08:00
Controller clients are small command-line utilities that expose certain functionalities of the drivers. They are optional, and not used very often - typically for debugging and testing.
Create a ``hello_client.py`` file with the following contents: ::
2014-10-27 20:38:02 +08:00
#!/usr/bin/env python3
from artiq.protocols.pc_rpc import Client
2014-10-27 20:38:02 +08:00
def main():
2014-10-28 15:45:56 +08:00
remote = Client("::1", 7777, "hello")
2014-10-27 20:38:02 +08:00
try:
remote.message("Hello World!")
finally:
remote.close_rpc()
if __name__ == "__main__":
main()
Run it as before, while the controller is running. You should see the message appearing on the controller's terminal: ::
$ ./hello_controller.py
2014-10-27 20:38:02 +08:00
message: Hello World!
When using the driver in an experiment, for simple cases the ``Client`` instance can be returned by the :class:`artiq.language.db.AutoDB` mechanism and used normally as a device.
2014-10-27 20:38:02 +08:00
:warning: RPC servers operate on copies of objects provided by the client, and modifications to mutable types are not written back. For example, if the client passes a list as a parameter of an RPC method, and that method ``append()s`` an element to the list, the element is not appended to the client's list.
2014-10-27 20:38:02 +08:00
Command-line arguments
----------------------
Use the Python ``argparse`` module to make the bind address and port configurable on the controller, and the server address, port and message configurable on the client.
We suggest naming the controller parameters ``--bind`` and ``--port`` so that those parameters stay consistent across controller, and use ``-s/--server`` and ``--port`` on the client.
The controller's code would contain something similar to this: ::
def _get_args():
parser = argparse.ArgumentParser(description="Hello world controller")
parser.add_argument("--bind", default="::1",
help="hostname or IP address to bind to")
parser.add_argument("--port", default=7777, type=int,
help="TCP port to listen to")
return parser.parse_args()
def main():
args = _get_args()
simple_server_loop(Hello(), args.bind, args.port)
General guidelines
------------------
* Format your source code according to PEP8. We suggest using ``flake8`` to check for compliance.
* The device identification (e.g. serial number) to attach to must be passed as a command-line parameter to the controller.
* Controllers must be able to operate in "simulation" mode, where they behave properly even if the associated hardware is not connected. For example, they can print the data to the console instead of sending it to the device, or dump it into a file.
* Keep command line parameters consistent across clients/controllers. When adding new command line options, look for a client/controller that does a similar thing and follow its use of ``argparse``. If the original client/controller could use ``argparse`` in a better way, improve it.
* Choose a free default TCP port and add it to the default port list in this manual.