Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Zenoh examples #80

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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](src/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`
- `zenoh-bridge-ros2dds`
- `python ./talker.py`

## Messages Subscription: [listener.py](src/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`
- `zenoh-bridge-ros2dds`
- `python ./listener.py`

## Services Client: [add_two_ints_client.py](src/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`
- `zenoh-bridge-ros2dds`
- `python ./add_two_ints_client.py`

## Actions Client: [fibonnacci_action_client.py](src/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`
- `zenoh-bridge-ros2dds`
- `python ./fibonnacci_action_client.py`
62 changes: 62 additions & 0 deletions examples/python/src/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, <zenoh@zettascale.tech>
#
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_Response"):
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()
113 changes: 113 additions & 0 deletions examples/python/src/fibonnacci_action_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#
# 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, <zenoh@zettascale.tech>
#
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('Next number in sequence received: {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
print('Sending goal')
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 by server, waiting for result')

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/src/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, <zenoh@zettascale.tech>
#
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/src/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, <zenoh@zettascale.tech>
#
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()
17 changes: 17 additions & 0 deletions examples/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "rust_examples"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
[dependencies]
async-std = { version = "1.12.0" }
futures = { version = "0.3.28" }
zenoh = { version = "0.10.1-rc" }
clap = { version = "4.4.11", features = ["derive"] }
env_logger = { version = "0.10.0" }
serde = {version = "1" }
serde_derive = {version = "1"}
cdr = {version = "0.2.4"}
log = { version = "0.4.21"}
Loading