Skip to content

Commit

Permalink
Add Python examples
Browse files Browse the repository at this point in the history
  • Loading branch information
JEnoch committed Feb 6, 2024
1 parent d5d6a95 commit ebd39c1
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 0 deletions.
5 changes: 5 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Examples of Zenoh applications communicating with ROS 2 Nodes

This directory contains some examples of applications using the Zenoh APIs only (not ROS 2), and that can communicate with ROS 2 Nodes using CycloneDDS as RMW, via the `zenoh-bridge-ros2dds`.

> :warning: The code of those examples are made to work with the version 0.10.x of the `zenoh-plugin-ros2dds` or `zenoh-bridge-ros2dds`. Soon both will be adapted for compatibility with the incoming `rmw_zenoh`, implying some code change in those examples.
30 changes: 30 additions & 0 deletions examples/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Examples of Zenoh Python applications communicating with ROS 2 Nodes


## Messages Publication: [talker.py](talker.py)

This code mimics the ROS 2 [Topics "talker" demo](https://github.com/ros2/demos/blob/rolling/demo_nodes_cpp/src/topics/talker.cpp). It's compatible with the ROS 2 [Topics "listener" demo](https://github.com/ros2/demos/blob/rolling/demo_nodes_cpp/src/topics/listener.cpp) running those commands:
- `ros2 run demo_nodes_cpp listener`
- `zenho-bridge-ros2dds`
- `python ./talker.py`

## Messages Subscription: [listener.py](listener.py)

This code mimics the ROS 2 [Topics "listener" demo](https://github.com/ros2/demos/blob/rolling/demo_nodes_cpp/src/topics/listener.cpp). It's compatible with the ROS 2 [Topics "talker" demo](https://github.com/ros2/demos/blob/rolling/demo_nodes_cpp/src/topics/talker.cpp) running those commands:
- `ros2 run demo_nodes_cpp talker`
- `zenho-bridge-ros2dds`
- `python ./listener.py`

## Services Client: [add_two_ints_client.py](add_two_ints_client.py)

This code mimics the ROS 2 [Services "add_two_ints_client" demo](https://github.com/ros2/demos/blob/rolling/demo_nodes_cpp/src/services/add_two_ints_client.cpp). It's compatible with the ROS 2 [Services "add_two_ints_server" demo](https://github.com/ros2/demos/blob/rolling/demo_nodes_cpp/src/services/add_two_ints_server.cpp) running those commands:
- `ros2 run demo_nodes_cpp add_two_ints_server`
- `zenho-bridge-ros2dds`
- `python ./add_two_ints_client.py`

## Actions Client: [fibonnacci_action_client.py](fibonnacci_action_client.py)

This code mimics the ROS 2 [Actions "fibonnacci_action_client" demo](https://github.com/ros2/demos/blob/rolling/action_tutorials/action_tutorials_cpp/src/fibonacci_action_client.cpp). It's compatible with the ROS 2 [Actions "fibonnacci_action_server" demo](https://github.com/ros2/demos/blob/rolling/action_tutorials/action_tutorials_cpp/src/fibonacci_action_server.cpp) running those commands:
- `ros2 run action_tutorials_cpp fibonacci_action_server`
- `zenho-bridge-ros2dds`
- `python ./fibonnacci_action_client.py`
62 changes: 62 additions & 0 deletions examples/python/add_two_ints_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#
# Copyright (c) 2024 ZettaScale Technology
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# ZettaScale Zenoh Team, <[email protected]>
#
import time
import argparse
import zenoh

# pycdr2 is the serializer of data in CDR format (required by ROS2)
import pycdr2
from pycdr2 import IdlStruct
from dataclasses import dataclass

# Equivalent to AddTwoInts.Request class, but serializable by pycdr2
@dataclass
class AddTwoInts_Request(IdlStruct, typename="AddTwoInts_Request"):
a: pycdr2.types.int64
b: pycdr2.types.int64

# Equivalent to AddTwoInts.Response class, but serializable by pycdr2
@dataclass
class AddTwoInts_Response(IdlStruct, typename="AddTwoInts_Request"):
sum: pycdr2.types.int64

def main():
parser = argparse.ArgumentParser(
prog='add_two_ints_client',
description='Zenoh/ROS2 add_two_ints_client example')
parser.add_argument('--config', '-c', dest='config',
metavar='FILE',
type=str,
help='A configuration file.')
args = parser.parse_args()

# Create Zenoh Config from file if provoded, or a default one otherwise
conf = zenoh.Config.from_file(args.config) if args.config is not None else zenoh.Config()
# Open Zenoh Session
session = zenoh.open(conf)

req = AddTwoInts_Request(a=2, b=3)
# Send the query with the serialized request
replies = session.get('add_two_ints', zenoh.Queue(), value=req.serialize())
# Zenoh could get several replies for a request (e.g. from several "Service Servers" using the same name)
for reply in replies.receiver:
# Deserialize the response
rep = AddTwoInts_Response.deserialize(reply.ok.payload)
print('Result of add_two_ints: %d' % rep.sum)

session.close()


if __name__ == '__main__':
main()
112 changes: 112 additions & 0 deletions examples/python/fibonnacci_action_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#
# Copyright (c) 2024 ZettaScale Technology
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# ZettaScale Zenoh Team, <[email protected]>
#
import time
import argparse
import zenoh

# pycdr2 is the serializer of data in CDR format (required by ROS2)
import pycdr2
from pycdr2 import IdlStruct
from dataclasses import dataclass


@dataclass
class Time(IdlStruct, typename="Time"):
sec: pycdr2.types.uint32
nsec: pycdr2.types.uint32

# Equivalent to Fibonnaci.Goal.Request class, but serializable by pycdr2
@dataclass
class Fibonacci_SendGoal_Request(IdlStruct, typename="Fibonacci_SendGoal_Request"):
goal_id: pycdr2.types.array[pycdr2.types.uint8, 16]
order: pycdr2.types.int32

# Equivalent to Fibonnaci.Goal.Response class, but serializable by pycdr2
@dataclass
class Fibonacci_SendGoal_Response(IdlStruct, typename="Fibonacci_SendGoal_Response"):
accepted: bool
stamp: Time

# Equivalent to Fibonnaci.Goal.Request class, but serializable by pycdr2
@dataclass
class Fibonacci_GetResult_Request(IdlStruct, typename="Fibonacci_GetResult_Request"):
goal_id: pycdr2.types.array[pycdr2.types.uint8, 16]

# Equivalent to Fibonnaci.Goal.Response class, but serializable by pycdr2
@dataclass
class Fibonacci_GetResult_Response(IdlStruct, typename="Fibonacci_GetResult_Response"):
status: pycdr2.types.int8
sequence: pycdr2.types.sequence[pycdr2.types.int32]

@dataclass
class Fibonacci_Feedback(IdlStruct, typename="Fibonacci_Feedback"):
goal_id: pycdr2.types.array[pycdr2.types.uint8, 16]
partial_sequence: pycdr2.types.sequence[pycdr2.types.int32]




def feedback_callback(sample: zenoh.Sample):
# Deserialize the message
feedback = Fibonacci_Feedback.deserialize(sample.payload)
print('Received feedback: {0}'.format(feedback.partial_sequence))


def main():
parser = argparse.ArgumentParser(
prog='fibonacci_action_client',
description='Zenoh/ROS2 fibonacci_action_client example')
parser.add_argument('--config', '-c', dest='config',
metavar='FILE',
type=str,
help='A configuration file.')
args = parser.parse_args()

# Create Zenoh Config from file if provoded, or a default one otherwise
conf = zenoh.Config.from_file(args.config) if args.config is not None else zenoh.Config()
# Open Zenoh Session
session = zenoh.open(conf)

# Declare a subscriber for feedbacks
pub = session.declare_subscriber('fibonacci/_action/feedback', feedback_callback)

goal_id = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
req = Fibonacci_SendGoal_Request(goal_id, order=10)
# Send the query with the serialized request
replies = session.get('fibonacci/_action/send_goal', zenoh.Queue(), value=req.serialize())
# Zenoh could get several replies for a request (e.g. from several "Service Servers" using the same name)
for reply in replies.receiver:
# Deserialize the response
rep = Fibonacci_SendGoal_Response.deserialize(reply.ok.payload)
if not rep.accepted:
print('Goal rejected :(')
return

print('Goal accepted :)')

req = Fibonacci_GetResult_Request(goal_id)
# Send the query with the serialized request
replies = session.get('fibonacci/_action/get_result', zenoh.Queue(), value=req.serialize())
# Zenoh could get several replies for a request (e.g. from several "Service Servers" using the same name)
for reply in replies.receiver:
# Deserialize the response
rep = Fibonacci_GetResult_Response.deserialize(reply.ok.payload)
print('Result: {0}'.format(rep.sequence))


session.close()


if __name__ == '__main__':
main()
61 changes: 61 additions & 0 deletions examples/python/listener.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#
# Copyright (c) 2024 ZettaScale Technology
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# ZettaScale Zenoh Team, <[email protected]>
#
import time
import argparse
import zenoh

# pycdr2 is the serializer of data in CDR format (required by ROS2)
from pycdr2 import IdlStruct
from dataclasses import dataclass

# Equivalent to std_msgs.msg.String class, but serializable by pycdr2
@dataclass
class String(IdlStruct, typename="String"):
data: str

def chatter_callback(sample: zenoh.Sample):
# Deserialize the message
msg = String.deserialize(sample.payload)
print('I heard: [%s]' % msg.data)

def main():
parser = argparse.ArgumentParser(
prog='listener',
description='Zenoh/ROS2 listener example')
parser.add_argument('--config', '-c', dest='config',
metavar='FILE',
type=str,
help='A configuration file.')
args = parser.parse_args()

# Create Zenoh Config from file if provoded, or a default one otherwise
conf = zenoh.Config.from_file(args.config) if args.config is not None else zenoh.Config()
# Open Zenoh Session
session = zenoh.open(conf)

# Declare a subscriber
pub = session.declare_subscriber('chatter', chatter_callback)

try:
while True:
time.sleep(1)
except (KeyboardInterrupt):
pass
finally:
pub.undeclare()
session.close()


if __name__ == '__main__':
main()
63 changes: 63 additions & 0 deletions examples/python/talker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#
# Copyright (c) 2024 ZettaScale Technology
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# ZettaScale Zenoh Team, <[email protected]>
#
import time
import argparse
import zenoh

# pycdr2 is the serializer of data in CDR format (required by ROS2)
from pycdr2 import IdlStruct
from dataclasses import dataclass

# Equivalent to std_msgs.msg.String class, but serializable by pycdr2
@dataclass
class String(IdlStruct, typename="String"):
data: str

def main():
parser = argparse.ArgumentParser(
prog='talker',
description='Zenoh/ROS2 talker example')
parser.add_argument('--config', '-c', dest='config',
metavar='FILE',
type=str,
help='A configuration file.')
args = parser.parse_args()

# Create Zenoh Config from file if provoded, or a default one otherwise
conf = zenoh.Config.from_file(args.config) if args.config is not None else zenoh.Config()
# Open Zenoh Session
session = zenoh.open(conf)

# Declare a publisher (optional but allows Zenoh to perform some optimizations)
pub = session.declare_publisher('chatter')

try:
i = 0
while True:
i += 1
msg = String(data='Hello World: {0}'.format(i))
print('Publishing: "{0}"'.format(msg.data))
# Publish the serialized message
pub.put(msg.serialize())
time.sleep(1)

except (KeyboardInterrupt):
pass
finally:
pub.undeclare()
session.close()


if __name__ == '__main__':
main()

0 comments on commit ebd39c1

Please sign in to comment.