Skip to content

Commit

Permalink
feat: add services, clients and topic types
Browse files Browse the repository at this point in the history
  • Loading branch information
MrBlenny committed Dec 30, 2022
1 parent 4357c72 commit e547c52
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 22 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,19 @@ Exports ROS2 nodes (publishers, subcribers and nodes) into a [D2](https://d2lang
- [x] Simple serialisable diagram format
- [ ] Ros node descriptions pulled from the package.xml
- [ ] Topic QOS descriptions
- [ ] Topic Types
- [x] Topic Types
- [ ] Advanced theming
- [ ] Pytest helpers that ensure your ROS2 System architecture matches that of your .d2 diagram


## Examples

### Simple

![Simple example](example_output/ros-diagram-dagre-verbose.svg)


### More complex applications
![Example](example_output/ros-complex-example-dagre.svg)

## Installation
Expand Down Expand Up @@ -110,3 +119,7 @@ From the root of this repo:
Elk:
![elk](example_output/ros-diagram-elk.svg)
5. We can also export with more info using `ros_d2 export example_output/ros-diagram.d2`
![dagre](example_output/ros-diagram-dagre-verbose.svg)
2 changes: 1 addition & 1 deletion example_output/ros-complex-example-dagre.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion example_output/ros-complex-example.d2
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,4 @@
/stub_glendinning <- /vessel/glendinning/propulsion
/stub_glendinning <- /vessel/glendinning/steering
/stub_glendinning <- /vessel/glendinning/system_status
/webrtc_video_node -> /vessel/sensors/cameras/bow/image_raw
/webrtc_video_node -> /vessel/sensors/cameras/bow/image_raw
60 changes: 60 additions & 0 deletions example_output/ros-diagram-dagre-verbose.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions example_output/ros-diagram.d2
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
/adder_1/add: {
shape: class
topic: |
example_interfaces/srv/AddTwoInts
|
}
/adder_1/output: {
shape: class
topic: |
example_interfaces/msg/Bool
|
}
/adder_2/add: {
shape: class
topic: |
example_interfaces/srv/AddTwoInts
|
}
/adder_2/output: {
shape: class
topic: |
example_interfaces/msg/Bool
|
}
/adder_3/add: {
shape: class
topic: |
example_interfaces/srv/AddTwoInts
|
}
/adder_3/output: {
shape: class
topic: |
example_interfaces/msg/Bool
|
}
/input_1: {
shape: class
topic: |
example_interfaces/msg/Bool
|
}
/input_2: {
shape: class
topic: |
example_interfaces/msg/Bool
|
}
/adder_1: {
style: {
Expand All @@ -33,9 +66,12 @@
}
/adder_1 -> /adder_3/output
/adder_1 <- /adder_1/output
/adder_1 <- /adder_1/add
/adder_2 -> /input_1
/adder_2 -> /input_2
/adder_2 <- /adder_2/output
/adder_2 <- /adder_2/add
/adder_3 -> /adder_1/output
/adder_3 -> /adder_2/output
/adder_3 <- /adder_3/output
/adder_3 <- /adder_3/add
34 changes: 25 additions & 9 deletions example_output/ros-diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/ros_d2/ros_d2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
__version__ = "1.0.1"
__version__ = "1.0.2"
4 changes: 2 additions & 2 deletions src/ros_d2/ros_d2/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ros_d2.helpers.get_ros_architecture import get_ros_architecture


def export(output_file: Path):
def export(output_file: Path, verbose: bool = False):
ros_architecture = get_ros_architecture()
d2_graph = convert(ros_architecture)
d2_graph = convert(ros_architecture, verbose)
open(output_file, "w+").write(d2_graph)
40 changes: 37 additions & 3 deletions src/ros_d2/ros_d2/helpers/convert.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
from typing import List

from py_d2.D2Connection import D2Connection, Direction
from py_d2.D2Diagram import D2Diagram
from py_d2.D2Shape import D2Shape, Shape
from py_d2.D2Shape import D2Shape, D2Text, Shape
from py_d2.D2Style import D2Style

from ros_d2.helpers.get_ros_architecture import RosArchitecture
Expand All @@ -12,12 +14,24 @@
)


def convert(ros_architecture: RosArchitecture) -> str:
def concat_strings(strings: List[str]) -> str:
"""Concatenates a list of strings into a single string."""
return ", ".join(strings)


def convert(ros_architecture: RosArchitecture, verbose: bool = False) -> str:
"""Converts a list of NodeInfo objects to a d2 graph."""
diagram = D2Diagram()

for topic in ros_architecture.topics:
shape = D2Shape(name=topic.name, shape=Shape.classs)
if verbose:
shape = D2Shape(
name=topic.name,
shape=Shape.classs,
topic=D2Text(text=concat_strings(topic.types), format=""),
)
else:
shape = D2Shape(name=topic.name, shape=Shape.classs)
diagram.add_shape(shape)

for node_info in ros_architecture.nodes:
Expand All @@ -26,6 +40,7 @@ def convert(ros_architecture: RosArchitecture) -> str:
for sub in node_info.subs:
diagram.add_connection(
D2Connection(
# label=concat_strings(sub.types),
shape_1=node_info.name,
shape_2=sub.name,
direction=Direction.TO,
Expand All @@ -34,10 +49,29 @@ def convert(ros_architecture: RosArchitecture) -> str:
for pub in node_info.pubs:
diagram.add_connection(
D2Connection(
# label=concat_strings(pub.types),
shape_1=node_info.name,
shape_2=pub.name,
direction=Direction.FROM,
)
)
for client in node_info.clients:
diagram.add_connection(
D2Connection(
# label=concat_strings(pub.types),
shape_1=node_info.name,
shape_2=client.name,
direction=Direction.TO,
)
)
for service in node_info.services:
diagram.add_connection(
D2Connection(
# label=concat_strings(pub.types),
shape_1=node_info.name,
shape_2=service.name,
direction=Direction.FROM,
)
)

return str(diagram)
35 changes: 33 additions & 2 deletions src/ros_d2/ros_d2/helpers/get_ros_architecture.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
"/rosout",
]

TOPIC_TYPES_TO_IGNORE = [
"rcl_interfaces/srv/ListParameters",
"rcl_interfaces/srv/SetParameters",
"rcl_interfaces/srv/GetParameterTypes",
"rcl_interfaces/srv/GetParameters",
"rcl_interfaces/srv/SetParametersAtomically",
"rcl_interfaces/srv/DescribeParameters",
]


@dataclass
class TopicInfo:
Expand All @@ -27,6 +36,10 @@ class NodeInfo:
subs: List[TopicInfo]
# Node subscriber topics
pubs: List[TopicInfo]
# Node services
services: List[TopicInfo]
# Node service clients
clients: List[TopicInfo]


@dataclass
Expand All @@ -36,7 +49,11 @@ class RosArchitecture:


def filter_topics(topics: List[TopicInfo]) -> List[TopicInfo]:
return [t for t in topics if t.name not in TOPICS_TO_IGNORE]
return [
t
for t in topics
if t.name not in TOPICS_TO_IGNORE and t.types[0] not in TOPIC_TYPES_TO_IGNORE
]


def topic_tuple_to_info(topic_tuple: Tuple[str, List[str]]) -> TopicInfo:
Expand All @@ -57,11 +74,21 @@ def get_ros_architecture() -> RosArchitecture:
pubs_tuple = node.get_publisher_names_and_types_by_node(n.name, n.namespace)
subs = [topic_tuple_to_info(t) for t in subs_tuple]
pubs = [topic_tuple_to_info(t) for t in pubs_tuple]
services_tuple = node.get_service_names_and_types_by_node(
n.name, n.namespace
)
services = [topic_tuple_to_info(t) for t in services_tuple]
clients_tuple = node.get_client_names_and_types_by_node(n.name, n.namespace)
clients = [topic_tuple_to_info(t) for t in clients_tuple]

subs = filter_topics(subs)
pubs = filter_topics(pubs)
services = filter_topics(services)
clients = filter_topics(clients)

node_info = NodeInfo(name=node_name, subs=subs, pubs=pubs)
node_info = NodeInfo(
name=node_name, subs=subs, pubs=pubs, services=services, clients=clients
)
nodes.append(node_info)

# Get all the unique topics
Expand All @@ -76,6 +103,10 @@ def get_ros_architecture() -> RosArchitecture:
if pub.name not in topic_names:
topic_names.add(pub.name)
topics.append(pub)
for srv in node_info.services:
if srv.name not in topic_names:
topic_names.add(srv.name)
topics.append(srv)

# Order them alphabetically
topics.sort(key=lambda t: t.name)
Expand Down
10 changes: 8 additions & 2 deletions src/ros_d2/ros_d2/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@ def cli():


@cli.command("export", help="Export ROS 2 node graph to D2")
@click.option(
"--verbose",
is_flag=True,
default=False,
help="Make the diagram more verbose with topic types and more.",
)
@click.argument("output", type=str)
def export_command(output: str):
def export_command(output: str, verbose: bool):
output_file = Path(output)

# If the output_file is not a .d2 file, add the extension.
if output_file.suffix != ".d2":
output_file = output_file.with_suffix(".d2")

export(output_file)
export(output_file, verbose)


if __name__ == "__main__":
Expand Down
3 changes: 3 additions & 0 deletions src/ros_example_adder_node/launch/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def generate_launch_description():
("input_1", "adder_3/output"),
("input_2", "adder_3/output"),
("output", "adder_1/output"),
("add", "/adder_1/add"),
],
),
Node(
Expand All @@ -24,6 +25,7 @@ def generate_launch_description():
("input_1", "input_1"),
("input_2", "input_2"),
("output", "adder_2/output"),
("add", "/adder_2/add"),
],
),
Node(
Expand All @@ -34,6 +36,7 @@ def generate_launch_description():
("input_1", "adder_1/output"),
("input_2", "adder_2/output"),
("output", "adder_3/output"),
("add", "/adder_3/add"),
],
),
]
Expand Down
4 changes: 4 additions & 0 deletions tests/test_ros_d2/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def test_convert():
TopicInfo(name="topic1", types=["std_msgs/String"]),
TopicInfo(name="topic2", types=["std_msgs/String"]),
],
services=[],
clients=[],
),
NodeInfo(
name="node2",
Expand All @@ -21,6 +23,8 @@ def test_convert():
TopicInfo(name="topic2", types=["std_msgs/String"]),
],
pubs=[],
services=[],
clients=[],
),
],
topics=[
Expand Down

0 comments on commit e547c52

Please sign in to comment.