Tutorial 1: Communication#
In this tutorial chapter, we will learn basic concepts about inter-process communication on mahos.
Preparation#
Before starting this, install the library and dependencies.
Then, find the examples/comm
directory in the mahos repository.
We will use four files (conf.toml
, nodes.py
, print_clock.py
, and multipy_numbers.py
) in it for this tutorial.
You can copy this directory to somewhere in your computer if you want.
Pub-Sub#
We will learn two types of comminucation methods. First one here is Pub-Sub (Publish-Subscribe). This is asynchronous, one-to-many data distribution.
Let’s run a data-publishing node named clock by a command mahos run clock
.
And in another terminal, subscribe to and print the published message by a command mahos echo -t time clock
.
It is successful if you see something like below in the second terminal.
Subscribing to time of localhost::clock (config file: conf.toml).
2023-02-16 16:30:49
2023-02-16 16:30:50
2023-02-16 16:30:51
You can test one-to-many communication by repeating mahos echo -t time clock
in another terminal,
where you’d see the same output.
Hit Ctrl-C in the terminals to stop them.
Let’s see what was happening here.
The first command mahos run clock
was abbreviated form of mahos run -c conf.toml -H localhost clock
or mahos run -c conf.toml localhost::clock
.
localhost
is a hostname (computer name).
This will be important for multi-computer configuration,
but let’s think it is always localhost because we are on single-computer during this tutorial.
conf.toml
is the name of configuration file (conf.toml
is the default name and can be omitted in the command),
which defines what the node clock
is, like below.
1[localhost.clock]
2module = "nodes"
3class = "Clock"
4pub_endpoint = "tcp://127.0.0.1:5566"
Line 1 defines the group in form [<hostname>.<nodename>]
; we are defining node named clock on host localhost.
The module and class (Line 2 and 3) assigns Python module (any importable module) and class names that defines the node.
The pub_endpoint is the endpoint for Pub-Sub communication.
Let’s find the node implementation: Clock
class defined in module nodes (file nodes.py
).
13class ClockClient(NodeClient):
14 def __init__(self, gconf: dict, name: NodeName, context=None, prefix=None):
15 NodeClient.__init__(self, gconf, name, context=context, prefix=prefix)
16
17 self.get_time = self.add_sub(["time"])[0]
18
19
20class Clock(Node):
21 CLIENT = ClockClient
22
23 def __init__(self, gconf: dict, name: NodeName, context=None):
24 Node.__init__(self, gconf, name, context=context)
25
26 self.time_pub = self.add_pub("time")
27 self.interval = IntervalSleeper(1.0)
28
29 def main(self):
30 self.interval.sleep()
31 dt = datetime.datetime.now()
32 self.time_pub.publish(dt.strftime("%F %T"))
As we can see, Clock is a subclass of Node and a few things are implemented.
Work is done by a Publisher
initialized in Line 26, which is used to publish the formatted time in the main loop (Line 32).
The publisher is initialized with a topic (label for the messages) time.
Note that the type of the topic must be ASCII str (or bytes).
“You said main loop but I don’t see any for or while” ?
The loop is implemented elsewhere and automatically incorporated when using mahos run
command.
We always define the contents of main loop as the main()
function.
IntervalSleeper regulates the loop interval to 1.0 secs (rate of 1 Hz).
Although Publisher
can send any picklable Python object (we are sending str now),
the dedicated types can be used for serious applications (see mahos.msgs.common_msgs
).
Corresponding NodeClient ClockClient
is defined above,
and this is referenced as a class variable CLIENT
in Clock.
This is used to look up the client class from config file.
Implementation of ClockClient is even simpler; it just registers a subscriber for time.
See file print_clock.py
to see how to use the client from a custom script.
Req-Rep#
Req-Rep (Request-Reply) is the second way of the node communication. This is synchronous RPC.
Let’s run a node multiplier, and start a IPython shell by a command mahos shell multiplier
.
In the IPython shell, use cli.multiply(2, 3)
to multiply the numbers.
It is successful if you get correct answer 6, and see a log message in the terminal running the node.
cli
is a MultiplierClient (defined as below) in the IPython shell.
Obviously, MultiplierClient
registers a Requester
(Line 39) and
using in the multiply()
method (Line 42).
By calling Requester.request
, a request is sent from the client to serving node, and the response is returned.
The Multiplier
node defines how the request is handled.
A handler method handle_multiply
is registered in Line 51.
This method does the calculation, send a log message, and return the answer (Line 55-57).
35class MultiplierClient(NodeClient):
36 def __init__(self, gconf: dict, name: NodeName, context=None, prefix=None):
37 NodeClient.__init__(self, gconf, name, context=context, prefix=prefix)
38
39 self.req = self.add_req(gconf)
40
41 def multiply(self, a: int, b: int) -> int:
42 return self.req.request((a, b))
43
44
45class Multiplier(Node):
46 CLIENT = MultiplierClient
47
48 def __init__(self, gconf: dict, name: NodeName, context=None):
49 Node.__init__(self, gconf, name, context=context)
50
51 self.add_rep(self.handle_multiply)
52
53 def handle_multiply(self, req: tuple[int, int]) -> int:
54 a, b = req
55 rep = a * b
56 self.logger.info(f"{a} * {b} = {rep}")
57 return rep
See file multiply_numbers.py
to see how to use the client from a custom script.
Exercise#
The Node classes A
and B
are defined in nodes.py
.
See the definitions, run both nodes, and interact with them.
What happens by calling cli.set_data()
?
Further Reading#
zguide : The Guide for ZeroMQ library (used for node communication).