From e7fff464839da212e27b04d352bd52c5b6d08655 Mon Sep 17 00:00:00 2001 From: robo Date: Sat, 22 Apr 2023 22:57:45 +0300 Subject: [PATCH 01/26] ros2diagnostics cli init --- ros2diagnostics_cli/package.xml | 18 ++++++++++++ .../resource/ros2diagnostics_cli | 0 .../ros2diagnostics_cli/__init__.py | 0 .../ros2diagnostics_cli/command/__init__.py | 0 .../ros2diagnostics_cli/command/hello.py | 22 ++++++++++++++ .../ros2diagnostics_cli/verb/__init__.py | 0 .../ros2diagnostics_cli/verb/world.py | 8 +++++ ros2diagnostics_cli/setup.cfg | 4 +++ ros2diagnostics_cli/setup.py | 29 +++++++++++++++++++ ros2diagnostics_cli/test/test_copyright.py | 25 ++++++++++++++++ ros2diagnostics_cli/test/test_flake8.py | 25 ++++++++++++++++ ros2diagnostics_cli/test/test_pep257.py | 23 +++++++++++++++ 12 files changed, 154 insertions(+) create mode 100644 ros2diagnostics_cli/package.xml create mode 100644 ros2diagnostics_cli/resource/ros2diagnostics_cli create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/__init__.py create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/command/__init__.py create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/command/hello.py create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/verb/__init__.py create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py create mode 100644 ros2diagnostics_cli/setup.cfg create mode 100644 ros2diagnostics_cli/setup.py create mode 100644 ros2diagnostics_cli/test/test_copyright.py create mode 100644 ros2diagnostics_cli/test/test_flake8.py create mode 100644 ros2diagnostics_cli/test/test_pep257.py diff --git a/ros2diagnostics_cli/package.xml b/ros2diagnostics_cli/package.xml new file mode 100644 index 000000000..8d7b05094 --- /dev/null +++ b/ros2diagnostics_cli/package.xml @@ -0,0 +1,18 @@ + + + + ros2diagnostics_cli + 0.0.0 + TODO: Package description + user + TODO: License declaration + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + ros2cli + + ament_python + + diff --git a/ros2diagnostics_cli/resource/ros2diagnostics_cli b/ros2diagnostics_cli/resource/ros2diagnostics_cli new file mode 100644 index 000000000..e69de29bb diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/command/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/command/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/command/hello.py b/ros2diagnostics_cli/ros2diagnostics_cli/command/hello.py new file mode 100644 index 000000000..60914a03c --- /dev/null +++ b/ros2diagnostics_cli/ros2diagnostics_cli/command/hello.py @@ -0,0 +1,22 @@ +from ros2cli.command import add_subparsers +from ros2cli.command import CommandExtension +from ros2cli.verb import get_verb_extensions + + +class HelloCommand(CommandExtension): + """The 'hello' command extension.""" + + def add_arguments(self, parser, cli_name): + self._subparser = parser + verb_extensions = get_verb_extensions('ros2diagnostics_cli.verb') + add_subparsers( + parser, cli_name, '_verb', verb_extensions, required=False) + + def main(self, *, parser, args): + if not hasattr(args, '_verb'): + self._subparser.print_help() + return 0 + + extension = getattr(args, '_verb') + + return extension.main(args=args) \ No newline at end of file diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py new file mode 100644 index 000000000..d517b469a --- /dev/null +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py @@ -0,0 +1,8 @@ +from ros2cli.verb import VerbExtension + + +class WorldVerb(VerbExtension): + """Prints Hello World on the terminal.""" + + def main(self, *, args): + print("Hello, ROS 2 World!") diff --git a/ros2diagnostics_cli/setup.cfg b/ros2diagnostics_cli/setup.cfg new file mode 100644 index 000000000..3f0786bae --- /dev/null +++ b/ros2diagnostics_cli/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/ros2diagnostics_cli +[install] +install_scripts=$base/lib/ros2diagnostics_cli diff --git a/ros2diagnostics_cli/setup.py b/ros2diagnostics_cli/setup.py new file mode 100644 index 000000000..d4584971f --- /dev/null +++ b/ros2diagnostics_cli/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup + +package_name = 'ros2diagnostics_cli' + +setup( + name=package_name, + version='0.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='user', + maintainer_email='robo2020@gmail.com', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'ros2cli.command': [ + 'hello = ros2diagnostics_cli.command.hello:HelloCommand', + ], + 'ros2diagnostics_cli.verb': [ + 'world = ros2diagnostics_cli.verb.world:WorldVerb', + ] + }, +) diff --git a/ros2diagnostics_cli/test/test_copyright.py b/ros2diagnostics_cli/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/ros2diagnostics_cli/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/ros2diagnostics_cli/test/test_flake8.py b/ros2diagnostics_cli/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/ros2diagnostics_cli/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/ros2diagnostics_cli/test/test_pep257.py b/ros2diagnostics_cli/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/ros2diagnostics_cli/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' From 3391feebcdcab696222483494692172883a8f020 Mon Sep 17 00:00:00 2001 From: robo Date: Sat, 22 Apr 2023 23:27:30 +0300 Subject: [PATCH 02/26] rename hello to diagnostics --- .../ros2diagnostics_cli/api/__init__.py | 5 +++++ .../command/diagnostics.py | 22 +++++++++++++++++++ .../ros2diagnostics_cli/command/hello.py | 22 ------------------- .../ros2diagnostics_cli/verb/world.py | 12 +++++++++- ros2diagnostics_cli/setup.py | 2 +- 5 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py delete mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/command/hello.py diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py new file mode 100644 index 000000000..032e5801e --- /dev/null +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -0,0 +1,5 @@ +def get_hello_world(): + return 'Hello, ROS 2 World!' + +def get_hello_world_leet(): + return 'He110, R0S 2 W04ld!' \ No newline at end of file diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py b/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py new file mode 100644 index 000000000..52d6c84b0 --- /dev/null +++ b/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py @@ -0,0 +1,22 @@ +from ros2cli.command import add_subparsers_on_demand +from ros2cli.command import CommandExtension + + +class DiagCommand(CommandExtension): + def __init__(self): + super(DiagCommand, self).__init__() + self._subparser = None + + def add_arguments(self, parser, cli_name): + self._subparser = parser + add_subparsers_on_demand( + parser, cli_name, '_verb', "ros2diagnostics_cli.verb", required=False) + + def main(self, *, parser, args): + if not hasattr(args, '_verb'): + self._subparser.print_help() + return 0 + + extension = getattr(args, '_verb') + + return extension.main(args=args) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/command/hello.py b/ros2diagnostics_cli/ros2diagnostics_cli/command/hello.py deleted file mode 100644 index 60914a03c..000000000 --- a/ros2diagnostics_cli/ros2diagnostics_cli/command/hello.py +++ /dev/null @@ -1,22 +0,0 @@ -from ros2cli.command import add_subparsers -from ros2cli.command import CommandExtension -from ros2cli.verb import get_verb_extensions - - -class HelloCommand(CommandExtension): - """The 'hello' command extension.""" - - def add_arguments(self, parser, cli_name): - self._subparser = parser - verb_extensions = get_verb_extensions('ros2diagnostics_cli.verb') - add_subparsers( - parser, cli_name, '_verb', verb_extensions, required=False) - - def main(self, *, parser, args): - if not hasattr(args, '_verb'): - self._subparser.print_help() - return 0 - - extension = getattr(args, '_verb') - - return extension.main(args=args) \ No newline at end of file diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py index d517b469a..4eade5c9c 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py @@ -1,8 +1,18 @@ from ros2cli.verb import VerbExtension +from ros2diagnostics_cli.api import get_hello_world, get_hello_world_leet class WorldVerb(VerbExtension): """Prints Hello World on the terminal.""" + def add_arguments(self, parser, cli_name): + parser.add_argument( + "--leet", + "-l", + action="store_true", + help="Display the message in 'l33t' form.", + ) + def main(self, *, args): - print("Hello, ROS 2 World!") + message = get_hello_world() if not args.leet else get_hello_world_leet() + print(message) diff --git a/ros2diagnostics_cli/setup.py b/ros2diagnostics_cli/setup.py index d4584971f..3ca56a4f7 100644 --- a/ros2diagnostics_cli/setup.py +++ b/ros2diagnostics_cli/setup.py @@ -20,7 +20,7 @@ tests_require=['pytest'], entry_points={ 'ros2cli.command': [ - 'hello = ros2diagnostics_cli.command.hello:HelloCommand', + 'diagnostics = ros2diagnostics_cli.command.diagnostics:DiagCommand', ], 'ros2diagnostics_cli.verb': [ 'world = ros2diagnostics_cli.verb.world:WorldVerb', From 6e1223416efa3d864b091a53775dad5d6615e3a6 Mon Sep 17 00:00:00 2001 From: robo Date: Tue, 25 Apr 2023 15:00:54 +0300 Subject: [PATCH 03/26] add option with string csv --output --- .../ros2diagnostics_cli/api/__init__.py | 18 ++++++++++++++++-- .../ros2diagnostics_cli/verb/csv.py | 12 ++++++++++++ ros2diagnostics_cli/setup.py | 6 ++++-- 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index 032e5801e..d758aefc2 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -1,5 +1,19 @@ +import rclpy +from diagnostic_msgs.msg import DiagnosticArray + +TOPIC_DIAGNOSTICS = "/diagnostics" + + +def diagnostic_handler(msg: DiagnosticArray) -> None: + print(msg) + + def get_hello_world(): - return 'Hello, ROS 2 World!' + rclpy.init() + node = rclpy.create_node("ros2diagnostics_cli_filter") + node.create_subscription(DiagnosticArray, TOPIC_DIAGNOSTICS, diagnostic_handler, 10) + rclpy.spin_once(node) + def get_hello_world_leet(): - return 'He110, R0S 2 W04ld!' \ No newline at end of file + return "He110, R0S 2 W04ld!" diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py new file mode 100644 index 000000000..88be28507 --- /dev/null +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -0,0 +1,12 @@ +import click +from ros2cli.verb import VerbExtension +from ros2diagnostics_cli.api import get_hello_world, get_hello_world_leet + + +class CSVVerb(VerbExtension): + def add_arguments(self, parser, cli_name): + parser.add_argument("--output", "-o", type=str, help="export file to file") + + def main(self, *, args): + if args.output: + print(args.output) diff --git a/ros2diagnostics_cli/setup.py b/ros2diagnostics_cli/setup.py index 3ca56a4f7..5c6680f99 100644 --- a/ros2diagnostics_cli/setup.py +++ b/ros2diagnostics_cli/setup.py @@ -1,17 +1,18 @@ from setuptools import setup +from setuptools import find_packages package_name = 'ros2diagnostics_cli' setup( name=package_name, version='0.0.0', - packages=[package_name], + packages=find_packages(exclude=['test']), data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), ], - install_requires=['setuptools'], + install_requires=['ros2cli'], zip_safe=True, maintainer='user', maintainer_email='robo2020@gmail.com', @@ -24,6 +25,7 @@ ], 'ros2diagnostics_cli.verb': [ 'world = ros2diagnostics_cli.verb.world:WorldVerb', + 'csv = ros2diagnostics_cli.verb.csv:CSVVerb' ] }, ) From 8a112d898f24b9880444c0587983579a34a69905 Mon Sep 17 00:00:00 2001 From: robo Date: Tue, 25 Apr 2023 15:02:40 +0300 Subject: [PATCH 04/26] arg parse examples --- ros2diagnostics_cli/scripts/README.md | 100 ++++++++++++++++++ .../scripts/arg_parse_demo1.py | 6 ++ .../scripts/arg_parse_demo2.py | 6 ++ .../scripts/arg_parse_demo3.py | 16 +++ .../scripts/arg_parse_demo4.py | 23 ++++ .../scripts/arg_parse_demo4a.py | 17 +++ .../scripts/arg_parse_demo5.py | 12 +++ .../scripts/arg_parse_demo6.py | 25 +++++ .../scripts/arg_parse_demo7.py | 30 ++++++ 9 files changed, 235 insertions(+) create mode 100644 ros2diagnostics_cli/scripts/README.md create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo1.py create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo2.py create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo3.py create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo4.py create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo4a.py create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo5.py create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo6.py create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo7.py diff --git a/ros2diagnostics_cli/scripts/README.md b/ros2diagnostics_cli/scripts/README.md new file mode 100644 index 000000000..a71a90e1c --- /dev/null +++ b/ros2diagnostics_cli/scripts/README.md @@ -0,0 +1,100 @@ +Type: diagnostic_msgs/msg/DiagnosticArray + +``` +std_msgs/Header header # for timestamp + builtin_interfaces/Time stamp + int32 sec + uint32 nanosec + string frame_id +DiagnosticStatus[] status # an array of components being reported on + byte OK=0 + byte WARN=1 + byte ERROR=2 + byte STALE=3 + byte level + string name + string message + string hardware_id + KeyValue[] values + string key + string value + +``` + +```bash +# status info +--- +header: + stamp: + sec: 1682396036 + nanosec: 978628351 + frame_id: '' +status: +- level: "\0" + name: 'diagnostic_simple: DemoTask' + message: running + hardware_id: '' + values: [] +--- + +# status error +--- +header: + stamp: + sec: 1682396095 + nanosec: 590431434 + frame_id: '' +status: +- level: "\x02" + name: 'diagnostic_simple: DemoTask' + message: running + hardware_id: '' + values: [] +--- + +# status warning +--- +header: + stamp: + sec: 1682396159 + nanosec: 892125876 + frame_id: '' +status: +- level: "\x01" + name: 'diagnostic_simple: DemoTask' + message: running + hardware_id: '' + values: [] +--- + +``` + +## Add other data to Task +```python +def run(self, stat: DiagnosticStatusWrapper): + stat.summary(DiagnosticStatus.WARN, "running") + stat.add("key1", "val1") + stat.add("key2", "val2") + return stat +``` + +```bash +--- +header: + stamp: + sec: 1682396583 + nanosec: 587749252 + frame_id: '' +status: +- level: "\x01" + name: 'diagnostic_simple: DemoTask' + message: running + hardware_id: '' + values: + - key: key1 + value: val1 + - key: key2 + value: val2 +--- + +``` \ No newline at end of file diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo1.py b/ros2diagnostics_cli/scripts/arg_parse_demo1.py new file mode 100644 index 000000000..d178a2193 --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo1.py @@ -0,0 +1,6 @@ +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +parser.add_argument("echo", help="echo given string") +args: Namespace = parser.parse_args() +print(args.echo) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo2.py b/ros2diagnostics_cli/scripts/arg_parse_demo2.py new file mode 100644 index 000000000..00da0bfb5 --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo2.py @@ -0,0 +1,6 @@ +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +parser.add_argument("square", help="square given number", type=int) +args: Namespace = parser.parse_args() +print(args.square**2) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo3.py b/ros2diagnostics_cli/scripts/arg_parse_demo3.py new file mode 100644 index 000000000..17c02dd14 --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo3.py @@ -0,0 +1,16 @@ +""" Add positional argument +position argument: +- required: True/False +- action: boolean +""" +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +parser.add_argument("square", help="square given number", type=int) +parser.add_argument("-v", "--verbose", action="store_true", required=True, help="verbose opt") +args: Namespace = parser.parse_args() + +if args.verbose: + print(f"verbose mode {args.square**2}") +else: + print(args.square**2) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo4.py b/ros2diagnostics_cli/scripts/arg_parse_demo4.py new file mode 100644 index 000000000..36dbb1c1a --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo4.py @@ -0,0 +1,23 @@ +""" +- chose option +""" +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +parser.add_argument( + "-v", + "--verbose", + required=True, + help="verbose opt", + type=int, + choices=[1, 2, 3], +) + +args: Namespace = parser.parse_args() + +if args.verbose == 1: + print("mode 1") +elif args.verbose == 2: + print("mode 2") +elif args.verbose == 3: + print("mode 3") diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo4a.py b/ros2diagnostics_cli/scripts/arg_parse_demo4a.py new file mode 100644 index 000000000..5cba7ac6f --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo4a.py @@ -0,0 +1,17 @@ +""" +- chose option +""" +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +parser.add_argument( + "-o", + "--output", + help="output path", + type=str +) + +args: Namespace = parser.parse_args() + +if args.output: + print(args.output) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo5.py b/ros2diagnostics_cli/scripts/arg_parse_demo5.py new file mode 100644 index 000000000..e750147c7 --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo5.py @@ -0,0 +1,12 @@ +""" +- chose option +""" +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +parser.add_argument("square", help="square given", type=int, default=0, nargs="?") + +args: Namespace = parser.parse_args() +result: int = args.square**2 +print(result) + diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo6.py b/ros2diagnostics_cli/scripts/arg_parse_demo6.py new file mode 100644 index 000000000..5eb6ef1bc --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo6.py @@ -0,0 +1,25 @@ +""" +- option count +arg_parse_demo6 -v +arg_parse_demo6 -vv +arg_parse_demo6 --verbose --verbose +""" +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +parser.add_argument("square", help="square given", type=int, default=0, nargs="?") +parser.add_argument("-v", "--verbose", action="count", help="verbose, use -vv for more") +args: Namespace = parser.parse_args() +result: int = args.square**2 + +if args.verbose == 1: + print("verbose") +elif args.verbose == 2: + print("more verbose") +elif args.verbose > 2: + print("no extra verbode option max -vv") +else: + print("no verbose") + + + diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo7.py b/ros2diagnostics_cli/scripts/arg_parse_demo7.py new file mode 100644 index 000000000..94f568e47 --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo7.py @@ -0,0 +1,30 @@ +""" +- select argument + + +arg_parse_demo7 -v +arg_parse_demo7 -vv +arg_parse_demo7 -v -s +usage: arg_parse_demo7.py [-h] [-v | -s] [square] +""" +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +group = parser.add_mutually_exclusive_group() +parser.add_argument("square", help="square given", type=int, default=0, nargs="?") +group.add_argument("-v", "--verbose", action="count", help="verbose, use -vv for more") +group.add_argument("-s", "--silence", action="store_true", help="no verbose") +args: Namespace = parser.parse_args() +result: int = args.square**2 + +match args.verbose: + case 1: + print("verbose") + case 2: + print("more verbose") + case _: + print("") + + + + From 1aed29a0411c1150b0c6c09d4566d4a58c3619a4 Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 26 Apr 2023 08:55:19 +0300 Subject: [PATCH 05/26] add csv verb --- ros2diagnostics_cli/package.xml | 1 + .../ros2diagnostics_cli/api/__init__.py | 171 +++++++++++++++++- .../ros2diagnostics_cli/verb/csv.py | 50 ++++- .../ros2diagnostics_cli/verb/list.py | 11 ++ .../ros2diagnostics_cli/verb/show.py | 31 ++++ .../ros2diagnostics_cli/verb/world.py | 18 -- .../scripts/arg_parse_demo6str.py | 15 ++ .../scripts/simple_diag_reader.py | 34 ++++ .../scripts/simple_diagnostics.py | 50 +++++ .../scripts/yaml_demos/demo1.py | 12 ++ .../scripts/yaml_demos/example.yaml | 14 ++ ros2diagnostics_cli/setup.py | 4 +- ros2diagnostics_cli/test/test_csv.py | 8 + .../test/test_diagnostic_parse.py | 24 +++ 14 files changed, 410 insertions(+), 33 deletions(-) create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py create mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py delete mode 100644 ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py create mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo6str.py create mode 100644 ros2diagnostics_cli/scripts/simple_diag_reader.py create mode 100644 ros2diagnostics_cli/scripts/simple_diagnostics.py create mode 100644 ros2diagnostics_cli/scripts/yaml_demos/demo1.py create mode 100644 ros2diagnostics_cli/scripts/yaml_demos/example.yaml create mode 100644 ros2diagnostics_cli/test/test_csv.py create mode 100644 ros2diagnostics_cli/test/test_diagnostic_parse.py diff --git a/ros2diagnostics_cli/package.xml b/ros2diagnostics_cli/package.xml index 8d7b05094..90c8e0757 100644 --- a/ros2diagnostics_cli/package.xml +++ b/ros2diagnostics_cli/package.xml @@ -12,6 +12,7 @@ ament_pep257 python3-pytest ros2cli + python3-pyyaml ament_python diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index d758aefc2..48ef101b6 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -1,19 +1,170 @@ +"""_summary_ + +std_msgs/Header header # for timestamp +builtin_interfaces/Time stamp + int32 sec + uint32 nanosec +string frame_id +DiagnosticStatus[] status # an array of components being reported on +byte OK=0 +byte WARN=1 +byte ERROR=2 +byte STALE=3 +byte level +string name +string message +string hardware_id +KeyValue[] values + string key + string value +""" +from typing import Dict, List, Tuple, TextIO +import yaml import rclpy -from diagnostic_msgs.msg import DiagnosticArray +from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue +from rclpy.qos import qos_profile_system_default +from argparse import ArgumentParser +import pathlib +from enum import IntEnum TOPIC_DIAGNOSTICS = "/diagnostics" +COLOR_DEFAULT = "\033[39m" +GREEN = "\033[92m" +RED = "\033[91m" +YELLOW = "\033[93m" + + +def open_file_for_output(csv_file) -> TextIO: + base_dir = pathlib.Path(csv_file).parents[0] + folder_exists = pathlib.Path(base_dir).is_dir() + if not folder_exists: + raise Exception(f"Folder {csv_file} not exists") + f = open(csv_file, "w", encoding="utf-8") + return f + +class ParserModeEnum(IntEnum): + List = 1 + CSV = 2 + Show = 3 +class DiagnosticsParser: + def __init__(self, mode: ParserModeEnum, verbose=False, levels=None, run_once=False) -> None: + self.__status_render_handler = self.render + self.__mode = mode + self.__run_once = run_once + self.__verbose = verbose + self.__levels_info = ",".join(levels) if levels is not None else "" + self.__levels = DiagnosticsParser.map_level_from_name(levels) + + def set_render(self, handler): + self.__status_render_handler = handler + + def run(self): + self.register_and_parse_diagnostics_topic() + + def __filter_level(self, level): + if not self.__levels: + return False + return level not in self.__levels + + @staticmethod + def map_level_from_name(levels: List[str]) -> List[bytes]: + b_levels = [] + if levels is None: + return b_levels + + for level in levels: + match level: + case "info": + b_levels.append(b"\x00") + case "warn": + b_levels.append(b"\x01") + case "error": + b_levels.append(b"\x02") + return b_levels + + + + @staticmethod + def convert_level_to_str(level) -> Tuple(str, str): + match level: + case b"\x00": + return ("OK", GREEN + "OK" + COLOR_DEFAULT) + case b"\x01": + return ("WARN", YELLOW + "WARN" + COLOR_DEFAULT) + case b"\x02": + return ("ERROR", RED + "ERROR" + COLOR_DEFAULT) + case b"\x03": + return ("STALE", RED + "STALE" + COLOR_DEFAULT) + case _: + return ("UNDEFINED", "UNDEFINED") + + def render(self, status: DiagnosticStatus, time_sec, verbose): + _, level_name = DiagnosticsParser.convert_level_to_str(status.level) + item = f"{status.name}: {level_name}, {status.message}" + print(item) + if verbose: + kv: KeyValue + for kv in status.values: + print(f"- {kv.key}={kv.value}") + + def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: + counter: int = 0 + status: DiagnosticStatus + print(f"--- time: {msg.header.stamp.sec} ---") + for status in msg.status: + if self.__filter_level(status.level): + continue + self.__status_render_handler(status, msg.header.stamp.sec, self.__verbose) + counter += 1 + + if not counter: + print(f"No diagnostic for levels: {self.__levels_info}") + + def register_and_parse_diagnostics_topic(self): + match self.__mode: + case ParserModeEnum.List: + handler = diagnostic_list_handler + case _ : + handler = self.diagnostics_status_handler + + rclpy.init() + node = rclpy.create_node("ros2diagnostics_cli_filter") + node.create_subscription( + DiagnosticArray, + TOPIC_DIAGNOSTICS, + handler, + qos_profile=qos_profile_system_default, + ) + try: + if self.__run_once: + rclpy.spin_once(node) + else: + rclpy.spin(node) + finally: + node.destroy_node() + rclpy.try_shutdown() -def diagnostic_handler(msg: DiagnosticArray) -> None: - print(msg) +def diagnostic_list_handler(msg: DiagnosticArray) -> None: + """Group diagnostics Task by node + print group data as yaml to stdout -def get_hello_world(): - rclpy.init() - node = rclpy.create_node("ros2diagnostics_cli_filter") - node.create_subscription(DiagnosticArray, TOPIC_DIAGNOSTICS, diagnostic_handler, 10) - rclpy.spin_once(node) + Args: + msg (DiagnosticArray): _description_ + """ + status: DiagnosticStatus + data: Dict[str, List[str]] = {} + print(f"--- time: {msg.header.stamp.sec} ---") + for status in msg.status: + node, name = status.name.split(":") + name = name.strip() + if node in data: + data[node].append(name) + else: + data[node] = [name] + print(yaml.dump(data)) -def get_hello_world_leet(): - return "He110, R0S 2 W04ld!" +def add_common_arguments(parser: ArgumentParser): + parser.add_argument("-1", "--once", action="store_true", help="run only once") diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index 88be28507..453763fff 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -1,12 +1,54 @@ -import click +from typing import TextIO from ros2cli.verb import VerbExtension -from ros2diagnostics_cli.api import get_hello_world, get_hello_world_leet +from ros2diagnostics_cli.api import ( + DiagnosticsParser, + open_file_for_output, + add_common_arguments, + ParserModeEnum, +) +from diagnostic_msgs.msg import DiagnosticStatus, KeyValue class CSVVerb(VerbExtension): + """export diagnostics message to csv file""" + def __init__(self): + super().__init__() + self.csv: TextIO = None + def add_arguments(self, parser, cli_name): - parser.add_argument("--output", "-o", type=str, help="export file to file") + add_common_arguments(parser) + parser.add_argument("--output", "-o", type=str, help="export file full path") + + def render(self, status: DiagnosticStatus, time_sec, verbose=False): + level_name, _ = DiagnosticsParser.convert_level_to_str(status.level) + line = [ + str(time_sec), + level_name, + status.name, + status.message, + status.hardware_id, + ] + if verbose: + kv: KeyValue + for kv in status.values: + line.append(kv.value) + + s_line = ",".join(line) + "\n" + print(s_line) + self.csv.write(s_line) def main(self, *, args): if args.output: - print(args.output) + try: + self.csv = open_file_for_output(args.output) + except Exception as error: + print(str(error)) + return + + try: + handler = DiagnosticsParser(mode=ParserModeEnum.CSV, run_once=args.once) + handler.set_render(self.render) + handler.run() + finally: + if self.csv: + self.csv.close() diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py new file mode 100644 index 000000000..7eb98f0fe --- /dev/null +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py @@ -0,0 +1,11 @@ +from ros2cli.verb import VerbExtension +from ros2diagnostics_cli.api import DiagnosticsParser, ParserModeEnum + +class ListVerb(VerbExtension): + """List all diagnostic status items group by node name""" + + + + def main(self, *, args): + diagnostic_parser = DiagnosticsParser(ParserModeEnum.List) + diagnostic_parser.run() diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py new file mode 100644 index 000000000..d0a7d2a7f --- /dev/null +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py @@ -0,0 +1,31 @@ +from ros2cli.verb import VerbExtension +from ros2diagnostics_cli.api import DiagnosticsParser, add_common_arguments, ParserModeEnum + + +class ShowVerb(VerbExtension): + """Show diagnostics status item info""" + def add_arguments(self, parser, cli_name): + add_common_arguments(parser) + parser.add_argument( + "--verbose", + "-v", + action="store_true", + help="Display more info.", + ) + + parser.add_argument( + "-l", + "--levels", + action="append", + type=str, + choices=["info", "warn", "error"], + help="levels to filter, can be multiple times", + ) + + def main(self, *, args): + diagnostic_parser = DiagnosticsParser( + mode = ParserModeEnum.Show, + verbose=args.verbose, + levels=args.levels, + run_once=args.once) + diagnostic_parser.run() diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py deleted file mode 100644 index 4eade5c9c..000000000 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/world.py +++ /dev/null @@ -1,18 +0,0 @@ -from ros2cli.verb import VerbExtension -from ros2diagnostics_cli.api import get_hello_world, get_hello_world_leet - - -class WorldVerb(VerbExtension): - """Prints Hello World on the terminal.""" - - def add_arguments(self, parser, cli_name): - parser.add_argument( - "--leet", - "-l", - action="store_true", - help="Display the message in 'l33t' form.", - ) - - def main(self, *, args): - message = get_hello_world() if not args.leet else get_hello_world_leet() - print(message) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo6str.py b/ros2diagnostics_cli/scripts/arg_parse_demo6str.py new file mode 100644 index 000000000..70df3144c --- /dev/null +++ b/ros2diagnostics_cli/scripts/arg_parse_demo6str.py @@ -0,0 +1,15 @@ +""" +- option count +arg_parse_demo6 -v +arg_parse_demo6 -vv +arg_parse_demo6 --verbose --verbose +""" +from argparse import ArgumentParser, Namespace + +parser = ArgumentParser() +parser.add_argument( + "-l", "--levels", action="append", type=str, choices=["info", "warn", "error"], help="levels to filter" +) +args: Namespace = parser.parse_args() + +print(args.levels) diff --git a/ros2diagnostics_cli/scripts/simple_diag_reader.py b/ros2diagnostics_cli/scripts/simple_diag_reader.py new file mode 100644 index 000000000..7bddf93d6 --- /dev/null +++ b/ros2diagnostics_cli/scripts/simple_diag_reader.py @@ -0,0 +1,34 @@ +import rclpy +from rclpy.node import Node +from rclpy.qos import qos_profile_system_default +from diagnostic_msgs.msg import DiagnosticArray +from rosidl_runtime_py import message_to_csv + +class MyNode(Node): + def __init__(self): + node_name="simple_diag_reader" + super().__init__(node_name) + self.create_subscription(DiagnosticArray, "/diagnostics", self.__handler, qos_profile=qos_profile_system_default) + self.get_logger().info("Hello diagnostics parser") + + def __handler(self, msg: DiagnosticArray): + for status in msg.status: + to_print = message_to_csv( + status) + + print(to_print) + + +def main(): + rclpy.init() + node = MyNode() + try: + rclpy.spin(node) + except KeyboardInterrupt: + print("User exit") + finally: + node.destroy_node() + rclpy.try_shutdown() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2diagnostics_cli/scripts/simple_diagnostics.py b/ros2diagnostics_cli/scripts/simple_diagnostics.py new file mode 100644 index 000000000..751a4ec92 --- /dev/null +++ b/ros2diagnostics_cli/scripts/simple_diagnostics.py @@ -0,0 +1,50 @@ +import rclpy +from rclpy.node import Node +from diagnostic_updater import (DiagnosticTask, + Updater, + DiagnosticStatusWrapper) +from diagnostic_msgs.msg import DiagnosticStatus + + +class DemoStatus(DiagnosticTask): + def __init__(self, name): + super().__init__(name) + + def run(self, stat: DiagnosticStatusWrapper): + stat.summary(DiagnosticStatus.WARN, "running") + stat.add("key1", "val1") + stat.add("key2", "val2") + return stat + +class Demo1Status(DiagnosticTask): + def __init__(self, name): + super().__init__(name) + + def run(self, stat: DiagnosticStatusWrapper): + stat.summary(DiagnosticStatus.ERROR, "bad") + return stat + +class MyNode(Node): + def __init__(self): + node_name = "diagnostic_simple" + super().__init__(node_name) + self.diag_update = Updater(self) + self.diag_update.add(DemoStatus("DemoTask")) + self.diag_update.add(Demo1Status("DemoTask2")) + self.get_logger().info("Hello ROS2") + + +def main(): + rclpy.init() + node = MyNode() + try: + rclpy.spin(node) + except KeyboardInterrupt: + print("User exit") + finally: + node.destroy_node() + rclpy.try_shutdown() + + +if __name__ == "__main__": + main() diff --git a/ros2diagnostics_cli/scripts/yaml_demos/demo1.py b/ros2diagnostics_cli/scripts/yaml_demos/demo1.py new file mode 100644 index 000000000..3aaf854e1 --- /dev/null +++ b/ros2diagnostics_cli/scripts/yaml_demos/demo1.py @@ -0,0 +1,12 @@ +import yaml +from pathlib import Path + +base_dir = Path(__file__).parents[0] + +file_path = Path.joinpath(base_dir, "example.yaml") +with open(file_path, "r", encoding="utf-8") as f: + data = yaml.safe_load(f) + +print(data) + +print(yaml.dump(data)) diff --git a/ros2diagnostics_cli/scripts/yaml_demos/example.yaml b/ros2diagnostics_cli/scripts/yaml_demos/example.yaml new file mode 100644 index 000000000..65ca45cc8 --- /dev/null +++ b/ros2diagnostics_cli/scripts/yaml_demos/example.yaml @@ -0,0 +1,14 @@ +key: value +mylist: + - item1 + - item2 + - item3 + +my_dict: + key1: value1 + key2: value2 + +multi-line-str: | + hello world + this is a + multi line string \ No newline at end of file diff --git a/ros2diagnostics_cli/setup.py b/ros2diagnostics_cli/setup.py index 5c6680f99..5a9551c00 100644 --- a/ros2diagnostics_cli/setup.py +++ b/ros2diagnostics_cli/setup.py @@ -25,7 +25,9 @@ ], 'ros2diagnostics_cli.verb': [ 'world = ros2diagnostics_cli.verb.world:WorldVerb', - 'csv = ros2diagnostics_cli.verb.csv:CSVVerb' + 'csv = ros2diagnostics_cli.verb.csv:CSVVerb', + 'list = ros2diagnostics_cli.verb.list:ListVerb', + 'show = ros2diagnostics_cli.verb.show:ShowVerb' ] }, ) diff --git a/ros2diagnostics_cli/test/test_csv.py b/ros2diagnostics_cli/test/test_csv.py new file mode 100644 index 000000000..29cb08c4b --- /dev/null +++ b/ros2diagnostics_cli/test/test_csv.py @@ -0,0 +1,8 @@ +from ros2diagnostics_cli.api import open_file_for_output + + +def test_file_create(): + f = open_file_for_output("/tmp/1/a.csv") + assert f is not None + +test_file_create() \ No newline at end of file diff --git a/ros2diagnostics_cli/test/test_diagnostic_parse.py b/ros2diagnostics_cli/test/test_diagnostic_parse.py new file mode 100644 index 000000000..b4f1812ad --- /dev/null +++ b/ros2diagnostics_cli/test/test_diagnostic_parse.py @@ -0,0 +1,24 @@ +import unittest +import rclpy +from rclpy.executors import SingleThreadedExecutor + +TEST_NODE = "cli_diag_parse_test_node" +TEST_NAMESPACE = "cli_diag_parse" + + +class TestROS2DiagnosticParse(unittest.TestCase): + def setUp(self): + self.context = rclpy.context.Context() + rclpy.init(context=self.context) + self.node = rclpy.create_node( + TEST_NODE, namespace=TEST_NAMESPACE, context=self.context + ) + self.executor = SingleThreadedExecutor(context=self.context) + self.executor.add_node(self.node) + + def tearDown(self): + self.node.destroy_node() + rclpy.shutdown(context=self.context) + + def test_diagnostic_pub(self): + assert True From e2e917effa79b1507912afa7add4f5ad6ffb8bba Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 26 Apr 2023 20:22:51 +0300 Subject: [PATCH 06/26] version 0.0.1 --- ros2diagnostics_cli/README.md | 146 ++++++++++++++++++ .../ros2diagnostics_cli/api/__init__.py | 25 ++- .../ros2diagnostics_cli/verb/csv.py | 25 ++- .../ros2diagnostics_cli/verb/show.py | 14 +- ros2diagnostics_cli/scripts/reg_demo.py | 5 + ros2diagnostics_cli/setup.py | 2 +- 6 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 ros2diagnostics_cli/README.md create mode 100644 ros2diagnostics_cli/scripts/reg_demo.py diff --git a/ros2diagnostics_cli/README.md b/ros2diagnostics_cli/README.md new file mode 100644 index 000000000..fa411f2d3 --- /dev/null +++ b/ros2diagnostics_cli/README.md @@ -0,0 +1,146 @@ +# ROS2 diagnostic cli + +ROS2 cli to analysis and monitor `/diagnostics` topic +It's alternative to `diagnostic_analysis` project that not ported yet to ROS2. + +The project add `diagnostics` command to ROS2 cli with there verbs. + +- list +- show +- csv + +### list +Monitor the current diagnostics_status message from `/diagnostics` topic group by Node name and print `diagnostics status` name + +```bash +ros2 diagnostics list +# Result +--- time: 1682528234 --- +diagnostic_simple: +- DemoTask +- DemoTask2 +``` + +### show +Monitor `/diagnostics` topic and print the diagnostics_status data can filter by level and node/status name + +```bash +ros2 diagnostics show -h +usage: ros2 diagnostics show [-h] [-1] [-f FILTER] [--verbose] [-l {info,warn,error}] + +Show diagnostics status item info + +options: + -h, --help show this help message and exit + -1, --once run only once + -f FILTER, --filter FILTER + filter diagnostic status name + --verbose, -v Display more info. + -l {info,warn,error}, --levels {info,warn,error} + levels to filter, can be multiple times +``` + +#### demo + +```bash title="show all diagnostics status" +ros2 diagnostics show +# +--- time: 1682528494 --- +diagnostic_simple: DemoTask: WARN, running +diagnostic_simple: DemoTask2: ERROR, bad +--- time: 1682528495 --- +diagnostic_simple: DemoTask: WARN, running +diagnostic_simple: DemoTask2: ERROR, bad +``` + +```bash title="filter by level" +ros2 diagnostics show -l error +--- time: 1682528568 --- +diagnostic_simple: DemoTask2: ERROR, bad +--- time: 1682528569 --- +diagnostic_simple: DemoTask2: ERROR, bad +--- time: 1682528570 --- +``` + +```bash title="filter by name" +ros2 diagnostics show -f Task2 +# +--- time: 1682528688 --- +diagnostic_simple: DemoTask2: ERROR, bad +--- time: 1682528689 --- +diagnostic_simple: DemoTask2: ERROR, bad +``` + +```bash title="verbose usage" +ros2 diagnostics show -l warn -v +# +--- time: 1682528760 --- +diagnostic_simple: DemoTask: WARN, running +- key1=val1 +- key2=val2 +--- time: 1682528761 --- +diagnostic_simple: DemoTask: WARN, running +- key1=val1 +- key2=val2 + +``` + +### csv +Export `/diagnostics` topic to csv file + +**CSV headers**: +- time (sec) +- level +- node name +- diagnostics status name +- message +- hardware id +- values from keyvalue field (only on verbose) + + +```bash +ros2 diagnostics csv --help +usage: ros2 diagnostics csv [-h] [-1] [-f FILTER] [-l {info,warn,error}] [--output OUTPUT] [--verbose] + +export /diagnostics message to csv file + +options: + -h, --help show this help message and exit + -1, --once run only once + -f FILTER, --filter FILTER + filter diagnostic status name + -l {info,warn,error}, --levels {info,warn,error} + levels to filter, can be multiple times + --output OUTPUT, -o OUTPUT + export file full path + --verbose, -v export DiagnosticStatus values filed +``` + +#### Demos + +```bash title="simple csv file" +ros2 diagnostics csv -o /tmp/1.csv +--- time: 1682529183 --- +1682529183,WARN,diagnostic_simple,DemoTask,running, +``` + +```bash title="show csv file" +cat /tmp/1.csv + +1682529183,WARN,diagnostic_simple,DemoTask,running, +1682529183,ERROR,diagnostic_simple,DemoTask2,bad, +``` + +```bash title="filter by level" + ros2 diagnostics csv -o /tmp/1.csv -l error +``` + +```bash title="filter by name with regex" +ros2 diagnostics csv -o /tmp/1.csv -f Task$ -v +``` + +## Todo +- More tests +- Add unit test +- DEB package and install tests +- Ideas \ No newline at end of file diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index 48ef101b6..e943d096c 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -25,6 +25,7 @@ from rclpy.qos import qos_profile_system_default from argparse import ArgumentParser import pathlib +import re from enum import IntEnum TOPIC_DIAGNOSTICS = "/diagnostics" @@ -48,7 +49,8 @@ class ParserModeEnum(IntEnum): CSV = 2 Show = 3 class DiagnosticsParser: - def __init__(self, mode: ParserModeEnum, verbose=False, levels=None, run_once=False) -> None: + def __init__(self, mode: ParserModeEnum, verbose=False, levels=None, run_once=False, name_filter=None) -> None: + self.__name_filter = name_filter self.__status_render_handler = self.render self.__mode = mode self.__run_once = run_once @@ -86,7 +88,7 @@ def map_level_from_name(levels: List[str]) -> List[bytes]: @staticmethod - def convert_level_to_str(level) -> Tuple(str, str): + def convert_level_to_str(level) -> Tuple[str, str]: match level: case b"\x00": return ("OK", GREEN + "OK" + COLOR_DEFAULT) @@ -109,10 +111,20 @@ def render(self, status: DiagnosticStatus, time_sec, verbose): print(f"- {kv.key}={kv.value}") def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: + """Run handler for each DiagnosticStatus in array + filter status by level, name, node name + + Args: + msg (DiagnosticArray): _description_ + """ counter: int = 0 status: DiagnosticStatus print(f"--- time: {msg.header.stamp.sec} ---") for status in msg.status: + if self.__name_filter: + result = re.search(self.__name_filter, status.name) + if not result: + continue if self.__filter_level(status.level): continue self.__status_render_handler(status, msg.header.stamp.sec, self.__verbose) @@ -168,3 +180,12 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: def add_common_arguments(parser: ArgumentParser): parser.add_argument("-1", "--once", action="store_true", help="run only once") + parser.add_argument("-f", "--filter", type=str, help="filter diagnostic status name") + parser.add_argument( + "-l", + "--levels", + action="append", + type=str, + choices=["info", "warn", "error"], + help="levels to filter, can be multiple times", + ) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index 453763fff..9f4855a03 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -10,21 +10,32 @@ class CSVVerb(VerbExtension): - """export diagnostics message to csv file""" + """export /diagnostics message to csv file""" + def __init__(self): super().__init__() self.csv: TextIO = None def add_arguments(self, parser, cli_name): add_common_arguments(parser) - parser.add_argument("--output", "-o", type=str, help="export file full path") + parser.add_argument("--output", "-o", type=str, required=True, help="export file full path") + + parser.add_argument( + "--verbose", + "-v", + action="store_true", + help="export DiagnosticStatus values filed", + ) def render(self, status: DiagnosticStatus, time_sec, verbose=False): level_name, _ = DiagnosticsParser.convert_level_to_str(status.level) + node_name, name = status.name.split(":") + name = name.strip() line = [ str(time_sec), level_name, - status.name, + node_name, + name, status.message, status.hardware_id, ] @@ -46,7 +57,13 @@ def main(self, *, args): return try: - handler = DiagnosticsParser(mode=ParserModeEnum.CSV, run_once=args.once) + handler = DiagnosticsParser( + mode=ParserModeEnum.CSV, + verbose=args.verbose, + run_once=args.once, + name_filter=args.filter, + levels=args.levels + ) handler.set_render(self.render) handler.run() finally: diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py index d0a7d2a7f..49fa21305 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py @@ -13,19 +13,13 @@ def add_arguments(self, parser, cli_name): help="Display more info.", ) - parser.add_argument( - "-l", - "--levels", - action="append", - type=str, - choices=["info", "warn", "error"], - help="levels to filter, can be multiple times", - ) + def main(self, *, args): diagnostic_parser = DiagnosticsParser( mode = ParserModeEnum.Show, verbose=args.verbose, - levels=args.levels, - run_once=args.once) + levels=args.levels, + run_once=args.once, + name_filter=args.filter) diagnostic_parser.run() diff --git a/ros2diagnostics_cli/scripts/reg_demo.py b/ros2diagnostics_cli/scripts/reg_demo.py new file mode 100644 index 000000000..dbb6e81c0 --- /dev/null +++ b/ros2diagnostics_cli/scripts/reg_demo.py @@ -0,0 +1,5 @@ +import re + +txt = "node_name: DemoTask2" +x = re.search("name", txt) +print(x) \ No newline at end of file diff --git a/ros2diagnostics_cli/setup.py b/ros2diagnostics_cli/setup.py index 5a9551c00..925bd51e1 100644 --- a/ros2diagnostics_cli/setup.py +++ b/ros2diagnostics_cli/setup.py @@ -5,7 +5,7 @@ setup( name=package_name, - version='0.0.0', + version='0.0.1', packages=find_packages(exclude=['test']), data_files=[ ('share/ament_index/resource_index/packages', From 9a22aec51728aad839795203029d3f068fd1615c Mon Sep 17 00:00:00 2001 From: robobe <3914144+robobe@users.noreply.github.com> Date: Wed, 26 Apr 2023 22:29:40 +0300 Subject: [PATCH 07/26] Update ros2diagnostics_cli/package.xml Co-authored-by: Christian Henkel <6976069+ct2034@users.noreply.github.com> --- ros2diagnostics_cli/package.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ros2diagnostics_cli/package.xml b/ros2diagnostics_cli/package.xml index 90c8e0757..387b54ce7 100644 --- a/ros2diagnostics_cli/package.xml +++ b/ros2diagnostics_cli/package.xml @@ -12,7 +12,7 @@ ament_pep257 python3-pytest ros2cli - python3-pyyaml + python3-yaml ament_python From f637fadf69c70f1b1d15f0a07d05de037cf8b5bd Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 26 Apr 2023 22:50:08 +0300 Subject: [PATCH 08/26] clean test and scripts file --- ros2diagnostics_cli/scripts/README.md | 100 ------------------ .../scripts/arg_parse_demo1.py | 6 -- .../scripts/arg_parse_demo2.py | 6 -- .../scripts/arg_parse_demo3.py | 16 --- .../scripts/arg_parse_demo4.py | 23 ---- .../scripts/arg_parse_demo4a.py | 17 --- .../scripts/arg_parse_demo5.py | 12 --- .../scripts/arg_parse_demo6.py | 25 ----- .../scripts/arg_parse_demo6str.py | 15 --- .../scripts/arg_parse_demo7.py | 30 ------ ros2diagnostics_cli/scripts/reg_demo.py | 5 - .../scripts/simple_diag_reader.py | 34 ------ .../scripts/simple_diagnostics.py | 50 --------- .../scripts/yaml_demos/demo1.py | 12 --- .../scripts/yaml_demos/example.yaml | 14 --- ros2diagnostics_cli/test/test_csv.py | 8 -- .../test/test_diagnostic_parse.py | 24 ----- 17 files changed, 397 deletions(-) delete mode 100644 ros2diagnostics_cli/scripts/README.md delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo1.py delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo2.py delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo3.py delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo4.py delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo4a.py delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo5.py delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo6.py delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo6str.py delete mode 100644 ros2diagnostics_cli/scripts/arg_parse_demo7.py delete mode 100644 ros2diagnostics_cli/scripts/reg_demo.py delete mode 100644 ros2diagnostics_cli/scripts/simple_diag_reader.py delete mode 100644 ros2diagnostics_cli/scripts/simple_diagnostics.py delete mode 100644 ros2diagnostics_cli/scripts/yaml_demos/demo1.py delete mode 100644 ros2diagnostics_cli/scripts/yaml_demos/example.yaml delete mode 100644 ros2diagnostics_cli/test/test_csv.py delete mode 100644 ros2diagnostics_cli/test/test_diagnostic_parse.py diff --git a/ros2diagnostics_cli/scripts/README.md b/ros2diagnostics_cli/scripts/README.md deleted file mode 100644 index a71a90e1c..000000000 --- a/ros2diagnostics_cli/scripts/README.md +++ /dev/null @@ -1,100 +0,0 @@ -Type: diagnostic_msgs/msg/DiagnosticArray - -``` -std_msgs/Header header # for timestamp - builtin_interfaces/Time stamp - int32 sec - uint32 nanosec - string frame_id -DiagnosticStatus[] status # an array of components being reported on - byte OK=0 - byte WARN=1 - byte ERROR=2 - byte STALE=3 - byte level - string name - string message - string hardware_id - KeyValue[] values - string key - string value - -``` - -```bash -# status info ---- -header: - stamp: - sec: 1682396036 - nanosec: 978628351 - frame_id: '' -status: -- level: "\0" - name: 'diagnostic_simple: DemoTask' - message: running - hardware_id: '' - values: [] ---- - -# status error ---- -header: - stamp: - sec: 1682396095 - nanosec: 590431434 - frame_id: '' -status: -- level: "\x02" - name: 'diagnostic_simple: DemoTask' - message: running - hardware_id: '' - values: [] ---- - -# status warning ---- -header: - stamp: - sec: 1682396159 - nanosec: 892125876 - frame_id: '' -status: -- level: "\x01" - name: 'diagnostic_simple: DemoTask' - message: running - hardware_id: '' - values: [] ---- - -``` - -## Add other data to Task -```python -def run(self, stat: DiagnosticStatusWrapper): - stat.summary(DiagnosticStatus.WARN, "running") - stat.add("key1", "val1") - stat.add("key2", "val2") - return stat -``` - -```bash ---- -header: - stamp: - sec: 1682396583 - nanosec: 587749252 - frame_id: '' -status: -- level: "\x01" - name: 'diagnostic_simple: DemoTask' - message: running - hardware_id: '' - values: - - key: key1 - value: val1 - - key: key2 - value: val2 ---- - -``` \ No newline at end of file diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo1.py b/ros2diagnostics_cli/scripts/arg_parse_demo1.py deleted file mode 100644 index d178a2193..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo1.py +++ /dev/null @@ -1,6 +0,0 @@ -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -parser.add_argument("echo", help="echo given string") -args: Namespace = parser.parse_args() -print(args.echo) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo2.py b/ros2diagnostics_cli/scripts/arg_parse_demo2.py deleted file mode 100644 index 00da0bfb5..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo2.py +++ /dev/null @@ -1,6 +0,0 @@ -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -parser.add_argument("square", help="square given number", type=int) -args: Namespace = parser.parse_args() -print(args.square**2) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo3.py b/ros2diagnostics_cli/scripts/arg_parse_demo3.py deleted file mode 100644 index 17c02dd14..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo3.py +++ /dev/null @@ -1,16 +0,0 @@ -""" Add positional argument -position argument: -- required: True/False -- action: boolean -""" -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -parser.add_argument("square", help="square given number", type=int) -parser.add_argument("-v", "--verbose", action="store_true", required=True, help="verbose opt") -args: Namespace = parser.parse_args() - -if args.verbose: - print(f"verbose mode {args.square**2}") -else: - print(args.square**2) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo4.py b/ros2diagnostics_cli/scripts/arg_parse_demo4.py deleted file mode 100644 index 36dbb1c1a..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo4.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -- chose option -""" -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -parser.add_argument( - "-v", - "--verbose", - required=True, - help="verbose opt", - type=int, - choices=[1, 2, 3], -) - -args: Namespace = parser.parse_args() - -if args.verbose == 1: - print("mode 1") -elif args.verbose == 2: - print("mode 2") -elif args.verbose == 3: - print("mode 3") diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo4a.py b/ros2diagnostics_cli/scripts/arg_parse_demo4a.py deleted file mode 100644 index 5cba7ac6f..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo4a.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -- chose option -""" -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -parser.add_argument( - "-o", - "--output", - help="output path", - type=str -) - -args: Namespace = parser.parse_args() - -if args.output: - print(args.output) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo5.py b/ros2diagnostics_cli/scripts/arg_parse_demo5.py deleted file mode 100644 index e750147c7..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo5.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -- chose option -""" -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -parser.add_argument("square", help="square given", type=int, default=0, nargs="?") - -args: Namespace = parser.parse_args() -result: int = args.square**2 -print(result) - diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo6.py b/ros2diagnostics_cli/scripts/arg_parse_demo6.py deleted file mode 100644 index 5eb6ef1bc..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo6.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -- option count -arg_parse_demo6 -v -arg_parse_demo6 -vv -arg_parse_demo6 --verbose --verbose -""" -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -parser.add_argument("square", help="square given", type=int, default=0, nargs="?") -parser.add_argument("-v", "--verbose", action="count", help="verbose, use -vv for more") -args: Namespace = parser.parse_args() -result: int = args.square**2 - -if args.verbose == 1: - print("verbose") -elif args.verbose == 2: - print("more verbose") -elif args.verbose > 2: - print("no extra verbode option max -vv") -else: - print("no verbose") - - - diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo6str.py b/ros2diagnostics_cli/scripts/arg_parse_demo6str.py deleted file mode 100644 index 70df3144c..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo6str.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -- option count -arg_parse_demo6 -v -arg_parse_demo6 -vv -arg_parse_demo6 --verbose --verbose -""" -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -parser.add_argument( - "-l", "--levels", action="append", type=str, choices=["info", "warn", "error"], help="levels to filter" -) -args: Namespace = parser.parse_args() - -print(args.levels) diff --git a/ros2diagnostics_cli/scripts/arg_parse_demo7.py b/ros2diagnostics_cli/scripts/arg_parse_demo7.py deleted file mode 100644 index 94f568e47..000000000 --- a/ros2diagnostics_cli/scripts/arg_parse_demo7.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -- select argument - - -arg_parse_demo7 -v -arg_parse_demo7 -vv -arg_parse_demo7 -v -s -usage: arg_parse_demo7.py [-h] [-v | -s] [square] -""" -from argparse import ArgumentParser, Namespace - -parser = ArgumentParser() -group = parser.add_mutually_exclusive_group() -parser.add_argument("square", help="square given", type=int, default=0, nargs="?") -group.add_argument("-v", "--verbose", action="count", help="verbose, use -vv for more") -group.add_argument("-s", "--silence", action="store_true", help="no verbose") -args: Namespace = parser.parse_args() -result: int = args.square**2 - -match args.verbose: - case 1: - print("verbose") - case 2: - print("more verbose") - case _: - print("") - - - - diff --git a/ros2diagnostics_cli/scripts/reg_demo.py b/ros2diagnostics_cli/scripts/reg_demo.py deleted file mode 100644 index dbb6e81c0..000000000 --- a/ros2diagnostics_cli/scripts/reg_demo.py +++ /dev/null @@ -1,5 +0,0 @@ -import re - -txt = "node_name: DemoTask2" -x = re.search("name", txt) -print(x) \ No newline at end of file diff --git a/ros2diagnostics_cli/scripts/simple_diag_reader.py b/ros2diagnostics_cli/scripts/simple_diag_reader.py deleted file mode 100644 index 7bddf93d6..000000000 --- a/ros2diagnostics_cli/scripts/simple_diag_reader.py +++ /dev/null @@ -1,34 +0,0 @@ -import rclpy -from rclpy.node import Node -from rclpy.qos import qos_profile_system_default -from diagnostic_msgs.msg import DiagnosticArray -from rosidl_runtime_py import message_to_csv - -class MyNode(Node): - def __init__(self): - node_name="simple_diag_reader" - super().__init__(node_name) - self.create_subscription(DiagnosticArray, "/diagnostics", self.__handler, qos_profile=qos_profile_system_default) - self.get_logger().info("Hello diagnostics parser") - - def __handler(self, msg: DiagnosticArray): - for status in msg.status: - to_print = message_to_csv( - status) - - print(to_print) - - -def main(): - rclpy.init() - node = MyNode() - try: - rclpy.spin(node) - except KeyboardInterrupt: - print("User exit") - finally: - node.destroy_node() - rclpy.try_shutdown() - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/ros2diagnostics_cli/scripts/simple_diagnostics.py b/ros2diagnostics_cli/scripts/simple_diagnostics.py deleted file mode 100644 index 751a4ec92..000000000 --- a/ros2diagnostics_cli/scripts/simple_diagnostics.py +++ /dev/null @@ -1,50 +0,0 @@ -import rclpy -from rclpy.node import Node -from diagnostic_updater import (DiagnosticTask, - Updater, - DiagnosticStatusWrapper) -from diagnostic_msgs.msg import DiagnosticStatus - - -class DemoStatus(DiagnosticTask): - def __init__(self, name): - super().__init__(name) - - def run(self, stat: DiagnosticStatusWrapper): - stat.summary(DiagnosticStatus.WARN, "running") - stat.add("key1", "val1") - stat.add("key2", "val2") - return stat - -class Demo1Status(DiagnosticTask): - def __init__(self, name): - super().__init__(name) - - def run(self, stat: DiagnosticStatusWrapper): - stat.summary(DiagnosticStatus.ERROR, "bad") - return stat - -class MyNode(Node): - def __init__(self): - node_name = "diagnostic_simple" - super().__init__(node_name) - self.diag_update = Updater(self) - self.diag_update.add(DemoStatus("DemoTask")) - self.diag_update.add(Demo1Status("DemoTask2")) - self.get_logger().info("Hello ROS2") - - -def main(): - rclpy.init() - node = MyNode() - try: - rclpy.spin(node) - except KeyboardInterrupt: - print("User exit") - finally: - node.destroy_node() - rclpy.try_shutdown() - - -if __name__ == "__main__": - main() diff --git a/ros2diagnostics_cli/scripts/yaml_demos/demo1.py b/ros2diagnostics_cli/scripts/yaml_demos/demo1.py deleted file mode 100644 index 3aaf854e1..000000000 --- a/ros2diagnostics_cli/scripts/yaml_demos/demo1.py +++ /dev/null @@ -1,12 +0,0 @@ -import yaml -from pathlib import Path - -base_dir = Path(__file__).parents[0] - -file_path = Path.joinpath(base_dir, "example.yaml") -with open(file_path, "r", encoding="utf-8") as f: - data = yaml.safe_load(f) - -print(data) - -print(yaml.dump(data)) diff --git a/ros2diagnostics_cli/scripts/yaml_demos/example.yaml b/ros2diagnostics_cli/scripts/yaml_demos/example.yaml deleted file mode 100644 index 65ca45cc8..000000000 --- a/ros2diagnostics_cli/scripts/yaml_demos/example.yaml +++ /dev/null @@ -1,14 +0,0 @@ -key: value -mylist: - - item1 - - item2 - - item3 - -my_dict: - key1: value1 - key2: value2 - -multi-line-str: | - hello world - this is a - multi line string \ No newline at end of file diff --git a/ros2diagnostics_cli/test/test_csv.py b/ros2diagnostics_cli/test/test_csv.py deleted file mode 100644 index 29cb08c4b..000000000 --- a/ros2diagnostics_cli/test/test_csv.py +++ /dev/null @@ -1,8 +0,0 @@ -from ros2diagnostics_cli.api import open_file_for_output - - -def test_file_create(): - f = open_file_for_output("/tmp/1/a.csv") - assert f is not None - -test_file_create() \ No newline at end of file diff --git a/ros2diagnostics_cli/test/test_diagnostic_parse.py b/ros2diagnostics_cli/test/test_diagnostic_parse.py deleted file mode 100644 index b4f1812ad..000000000 --- a/ros2diagnostics_cli/test/test_diagnostic_parse.py +++ /dev/null @@ -1,24 +0,0 @@ -import unittest -import rclpy -from rclpy.executors import SingleThreadedExecutor - -TEST_NODE = "cli_diag_parse_test_node" -TEST_NAMESPACE = "cli_diag_parse" - - -class TestROS2DiagnosticParse(unittest.TestCase): - def setUp(self): - self.context = rclpy.context.Context() - rclpy.init(context=self.context) - self.node = rclpy.create_node( - TEST_NODE, namespace=TEST_NAMESPACE, context=self.context - ) - self.executor = SingleThreadedExecutor(context=self.context) - self.executor.add_node(self.node) - - def tearDown(self): - self.node.destroy_node() - rclpy.shutdown(context=self.context) - - def test_diagnostic_pub(self): - assert True From c13ce4ebd519aa4f39fff9ce609f8b65056edbfd Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 26 Apr 2023 22:52:44 +0300 Subject: [PATCH 09/26] add copyright --- .../ros2diagnostics_cli/api/__init__.py | 29 +++++++++++++++++++ .../command/diagnostics.py | 29 +++++++++++++++++++ .../ros2diagnostics_cli/verb/csv.py | 29 +++++++++++++++++++ .../ros2diagnostics_cli/verb/list.py | 29 +++++++++++++++++++ .../ros2diagnostics_cli/verb/show.py | 29 +++++++++++++++++++ 5 files changed, 145 insertions(+) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index e943d096c..255b17c66 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -1,3 +1,32 @@ +# Copyright 2023 robobe +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the robobe nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + """_summary_ std_msgs/Header header # for timestamp diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py b/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py index 52d6c84b0..577f02aea 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py @@ -1,3 +1,32 @@ +# Copyright 2023 robobe +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the robobe nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + from ros2cli.command import add_subparsers_on_demand from ros2cli.command import CommandExtension diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index 9f4855a03..db4483476 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -1,3 +1,32 @@ +# Copyright 2023 robobe +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the robobe nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + from typing import TextIO from ros2cli.verb import VerbExtension from ros2diagnostics_cli.api import ( diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py index 7eb98f0fe..5fac53280 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py @@ -1,3 +1,32 @@ +# Copyright 2023 robobe +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the robobe nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + from ros2cli.verb import VerbExtension from ros2diagnostics_cli.api import DiagnosticsParser, ParserModeEnum diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py index 49fa21305..5278bf7bb 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py @@ -1,3 +1,32 @@ +# Copyright 2023 robobe +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the robobe nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + from ros2cli.verb import VerbExtension from ros2diagnostics_cli.api import DiagnosticsParser, add_common_arguments, ParserModeEnum From c4d5f29681226521234bb44bdb691a289ade470b Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 26 Apr 2023 23:02:41 +0300 Subject: [PATCH 10/26] fix: support python 3.8 remove match --- .../ros2diagnostics_cli/api/__init__.py | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index 255b17c66..b13665c0c 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -73,12 +73,22 @@ def open_file_for_output(csv_file) -> TextIO: f = open(csv_file, "w", encoding="utf-8") return f + class ParserModeEnum(IntEnum): List = 1 CSV = 2 Show = 3 + + class DiagnosticsParser: - def __init__(self, mode: ParserModeEnum, verbose=False, levels=None, run_once=False, name_filter=None) -> None: + def __init__( + self, + mode: ParserModeEnum, + verbose=False, + levels=None, + run_once=False, + name_filter=None, + ) -> None: self.__name_filter = name_filter self.__status_render_handler = self.render self.__mode = mode @@ -104,18 +114,16 @@ def map_level_from_name(levels: List[str]) -> List[bytes]: if levels is None: return b_levels + map_levels = { + "info": lambda: b_levels.append(b"\x00"), + "warn": lambda: b_levels.append(b"\x01"), + "error": lambda: b_levels.append(b"\x02"), + } + for level in levels: - match level: - case "info": - b_levels.append(b"\x00") - case "warn": - b_levels.append(b"\x01") - case "error": - b_levels.append(b"\x02") + map_levels[level]() return b_levels - - @staticmethod def convert_level_to_str(level) -> Tuple[str, str]: match level: @@ -166,9 +174,9 @@ def register_and_parse_diagnostics_topic(self): match self.__mode: case ParserModeEnum.List: handler = diagnostic_list_handler - case _ : + case _: handler = self.diagnostics_status_handler - + rclpy.init() node = rclpy.create_node("ros2diagnostics_cli_filter") node.create_subscription( @@ -207,14 +215,17 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: print(yaml.dump(data)) + def add_common_arguments(parser: ArgumentParser): parser.add_argument("-1", "--once", action="store_true", help="run only once") - parser.add_argument("-f", "--filter", type=str, help="filter diagnostic status name") parser.add_argument( - "-l", - "--levels", - action="append", - type=str, - choices=["info", "warn", "error"], - help="levels to filter, can be multiple times", - ) + "-f", "--filter", type=str, help="filter diagnostic status name" + ) + parser.add_argument( + "-l", + "--levels", + action="append", + type=str, + choices=["info", "warn", "error"], + help="levels to filter, can be multiple times", + ) From 1c3ca747dcfae62412d77b43ce80f48bcf3a6db1 Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 26 Apr 2023 23:19:05 +0300 Subject: [PATCH 11/26] format and run linters --- ros2diagnostics_cli/package.xml | 2 +- .../ros2diagnostics_cli/verb/csv.py | 6 +++-- .../ros2diagnostics_cli/verb/list.py | 3 +-- .../ros2diagnostics_cli/verb/show.py | 14 +++++++---- ros2diagnostics_cli/test/test_copyright.py | 2 +- ros2diagnostics_cli/test/test_pep257.py | 23 ------------------- 6 files changed, 16 insertions(+), 34 deletions(-) delete mode 100644 ros2diagnostics_cli/test/test_pep257.py diff --git a/ros2diagnostics_cli/package.xml b/ros2diagnostics_cli/package.xml index 387b54ce7..4b1d75b4e 100644 --- a/ros2diagnostics_cli/package.xml +++ b/ros2diagnostics_cli/package.xml @@ -9,7 +9,7 @@ ament_copyright ament_flake8 - ament_pep257 + python3-pytest ros2cli python3-yaml diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index db4483476..8454993ae 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -47,7 +47,9 @@ def __init__(self): def add_arguments(self, parser, cli_name): add_common_arguments(parser) - parser.add_argument("--output", "-o", type=str, required=True, help="export file full path") + parser.add_argument( + "--output", "-o", type=str, required=True, help="export file full path" + ) parser.add_argument( "--verbose", @@ -91,7 +93,7 @@ def main(self, *, args): verbose=args.verbose, run_once=args.once, name_filter=args.filter, - levels=args.levels + levels=args.levels, ) handler.set_render(self.render) handler.run() diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py index 5fac53280..8b9f7541a 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py @@ -30,11 +30,10 @@ from ros2cli.verb import VerbExtension from ros2diagnostics_cli.api import DiagnosticsParser, ParserModeEnum + class ListVerb(VerbExtension): """List all diagnostic status items group by node name""" - - def main(self, *, args): diagnostic_parser = DiagnosticsParser(ParserModeEnum.List) diagnostic_parser.run() diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py index 5278bf7bb..c58aee398 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py @@ -28,11 +28,16 @@ from ros2cli.verb import VerbExtension -from ros2diagnostics_cli.api import DiagnosticsParser, add_common_arguments, ParserModeEnum +from ros2diagnostics_cli.api import ( + DiagnosticsParser, + add_common_arguments, + ParserModeEnum, +) class ShowVerb(VerbExtension): """Show diagnostics status item info""" + def add_arguments(self, parser, cli_name): add_common_arguments(parser) parser.add_argument( @@ -42,13 +47,12 @@ def add_arguments(self, parser, cli_name): help="Display more info.", ) - - def main(self, *, args): diagnostic_parser = DiagnosticsParser( - mode = ParserModeEnum.Show, + mode=ParserModeEnum.Show, verbose=args.verbose, levels=args.levels, run_once=args.once, - name_filter=args.filter) + name_filter=args.filter, + ) diagnostic_parser.run() diff --git a/ros2diagnostics_cli/test/test_copyright.py b/ros2diagnostics_cli/test/test_copyright.py index 97a39196e..395f7f316 100644 --- a/ros2diagnostics_cli/test/test_copyright.py +++ b/ros2diagnostics_cli/test/test_copyright.py @@ -17,7 +17,7 @@ # Remove the `skip` decorator once the source file(s) have a copyright header -@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +# @pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') @pytest.mark.copyright @pytest.mark.linter def test_copyright(): diff --git a/ros2diagnostics_cli/test/test_pep257.py b/ros2diagnostics_cli/test/test_pep257.py deleted file mode 100644 index b234a3840..000000000 --- a/ros2diagnostics_cli/test/test_pep257.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2015 Open Source Robotics Foundation, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ament_pep257.main import main -import pytest - - -@pytest.mark.linter -@pytest.mark.pep257 -def test_pep257(): - rc = main(argv=['.', 'test']) - assert rc == 0, 'Found code style errors / warnings' From e03df55e1e1b8e99a307a52b61c6d1aa30b10bed Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 26 Apr 2023 23:44:54 +0300 Subject: [PATCH 12/26] fix: suport python 3.8 remove math case remove some of pylint warnings --- .../ros2diagnostics_cli/api/__init__.py | 55 +++++++++---------- .../ros2diagnostics_cli/verb/csv.py | 3 +- .../ros2diagnostics_cli/verb/list.py | 2 +- .../ros2diagnostics_cli/verb/show.py | 2 +- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index b13665c0c..392e50af8 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -47,15 +47,16 @@ string key string value """ -from typing import Dict, List, Tuple, TextIO -import yaml -import rclpy -from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue -from rclpy.qos import qos_profile_system_default -from argparse import ArgumentParser import pathlib import re +from argparse import ArgumentParser from enum import IntEnum +from typing import Dict, List, TextIO, Tuple + +import rclpy +import yaml +from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue +from rclpy.qos import qos_profile_system_default TOPIC_DIAGNOSTICS = "/diagnostics" @@ -64,6 +65,16 @@ RED = "\033[91m" YELLOW = "\033[93m" +level_to_str_mapping = { + b"\x00": lambda: ("OK", GREEN + "OK" + COLOR_DEFAULT), + b"\x01": lambda: ("WARN", YELLOW + "WARN" + COLOR_DEFAULT), + b"\x02": lambda: ("ERROR", RED + "ERROR" + COLOR_DEFAULT), + b"\x03": lambda: ("STALE", RED + "STALE" + COLOR_DEFAULT) +} + +def convert_level_to_str(level) -> Tuple[str, str]: + return level_to_str_mapping[level]() + def open_file_for_output(csv_file) -> TextIO: base_dir = pathlib.Path(csv_file).parents[0] @@ -75,9 +86,9 @@ def open_file_for_output(csv_file) -> TextIO: class ParserModeEnum(IntEnum): - List = 1 + LIST = 1 CSV = 2 - Show = 3 + SHOW = 3 class DiagnosticsParser: @@ -90,7 +101,7 @@ def __init__( name_filter=None, ) -> None: self.__name_filter = name_filter - self.__status_render_handler = self.render + self.__status_render_handler = DiagnosticsParser.render self.__mode = mode self.__run_once = run_once self.__verbose = verbose @@ -125,21 +136,8 @@ def map_level_from_name(levels: List[str]) -> List[bytes]: return b_levels @staticmethod - def convert_level_to_str(level) -> Tuple[str, str]: - match level: - case b"\x00": - return ("OK", GREEN + "OK" + COLOR_DEFAULT) - case b"\x01": - return ("WARN", YELLOW + "WARN" + COLOR_DEFAULT) - case b"\x02": - return ("ERROR", RED + "ERROR" + COLOR_DEFAULT) - case b"\x03": - return ("STALE", RED + "STALE" + COLOR_DEFAULT) - case _: - return ("UNDEFINED", "UNDEFINED") - - def render(self, status: DiagnosticStatus, time_sec, verbose): - _, level_name = DiagnosticsParser.convert_level_to_str(status.level) + def render(status: DiagnosticStatus, time_sec, verbose): + _, level_name = convert_level_to_str(status.level) item = f"{status.name}: {level_name}, {status.message}" print(item) if verbose: @@ -171,11 +169,10 @@ def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: print(f"No diagnostic for levels: {self.__levels_info}") def register_and_parse_diagnostics_topic(self): - match self.__mode: - case ParserModeEnum.List: - handler = diagnostic_list_handler - case _: - handler = self.diagnostics_status_handler + if self.__mode == ParserModeEnum.LIST: + handler = diagnostic_list_handler + else: + handler = self.diagnostics_status_handler rclpy.init() node = rclpy.create_node("ros2diagnostics_cli_filter") diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index 8454993ae..2c02e021a 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -34,6 +34,7 @@ open_file_for_output, add_common_arguments, ParserModeEnum, + convert_level_to_str ) from diagnostic_msgs.msg import DiagnosticStatus, KeyValue @@ -59,7 +60,7 @@ def add_arguments(self, parser, cli_name): ) def render(self, status: DiagnosticStatus, time_sec, verbose=False): - level_name, _ = DiagnosticsParser.convert_level_to_str(status.level) + level_name, _ = convert_level_to_str(status.level) node_name, name = status.name.split(":") name = name.strip() line = [ diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py index 8b9f7541a..9169208ff 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py @@ -35,5 +35,5 @@ class ListVerb(VerbExtension): """List all diagnostic status items group by node name""" def main(self, *, args): - diagnostic_parser = DiagnosticsParser(ParserModeEnum.List) + diagnostic_parser = DiagnosticsParser(ParserModeEnum.LIST) diagnostic_parser.run() diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py index c58aee398..61eb409ac 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py @@ -49,7 +49,7 @@ def add_arguments(self, parser, cli_name): def main(self, *, args): diagnostic_parser = DiagnosticsParser( - mode=ParserModeEnum.Show, + mode=ParserModeEnum.SHOW, verbose=args.verbose, levels=args.levels, run_once=args.once, From 6c2b60c0d1e68e7ca6fa5d8a6cb92ccc607f5379 Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 26 Apr 2023 23:47:31 +0300 Subject: [PATCH 13/26] fix flake8 errors --- ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index 392e50af8..776e963eb 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -69,9 +69,10 @@ b"\x00": lambda: ("OK", GREEN + "OK" + COLOR_DEFAULT), b"\x01": lambda: ("WARN", YELLOW + "WARN" + COLOR_DEFAULT), b"\x02": lambda: ("ERROR", RED + "ERROR" + COLOR_DEFAULT), - b"\x03": lambda: ("STALE", RED + "STALE" + COLOR_DEFAULT) + b"\x03": lambda: ("STALE", RED + "STALE" + COLOR_DEFAULT), } + def convert_level_to_str(level) -> Tuple[str, str]: return level_to_str_mapping[level]() From f662b6cf9b9a871a6b99231929c43049a89fe6ab Mon Sep 17 00:00:00 2001 From: robo Date: Tue, 2 May 2023 20:25:18 +0300 Subject: [PATCH 14/26] fix: handle name without clone task name separator --- ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py | 6 +++++- ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index 776e963eb..d067b3a70 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -204,7 +204,11 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: data: Dict[str, List[str]] = {} print(f"--- time: {msg.header.stamp.sec} ---") for status in msg.status: - node, name = status.name.split(":") + if ":" in name: + node, name = status.name.split(":") + else: + node = "Aggr" + name = status.name name = name.strip() if node in data: data[node].append(name) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index 2c02e021a..308d6b57b 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -61,12 +61,16 @@ def add_arguments(self, parser, cli_name): def render(self, status: DiagnosticStatus, time_sec, verbose=False): level_name, _ = convert_level_to_str(status.level) - node_name, name = status.name.split(":") + if ":" in name: + node, name = status.name.split(":") + else: + node = "Aggr" + name = status.name name = name.strip() line = [ str(time_sec), level_name, - node_name, + node, name, status.message, status.hardware_id, From 35aa3a4840a29f2c4c36c8c6d4d74254f702b64c Mon Sep 17 00:00:00 2001 From: robo Date: Wed, 3 May 2023 05:53:53 +0300 Subject: [PATCH 15/26] fix: extract node name todo: add regex for extract node name from diagnostic name --- ros2diagnostics_cli/README.md | 8 +++++++- ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py | 4 ++-- ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ros2diagnostics_cli/README.md b/ros2diagnostics_cli/README.md index fa411f2d3..db9355a53 100644 --- a/ros2diagnostics_cli/README.md +++ b/ros2diagnostics_cli/README.md @@ -143,4 +143,10 @@ ros2 diagnostics csv -o /tmp/1.csv -f Task$ -v - More tests - Add unit test - DEB package and install tests -- Ideas \ No newline at end of file +- Ideas + + +## Tests +``` +ros2 launch diagnostic_aggregator example.launch.py +``` diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index d067b3a70..4b490338f 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -204,10 +204,10 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: data: Dict[str, List[str]] = {} print(f"--- time: {msg.header.stamp.sec} ---") for status in msg.status: - if ":" in name: + if ":" in status.name: node, name = status.name.split(":") else: - node = "Aggr" + node = "---" name = status.name name = name.strip() if node in data: diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index 308d6b57b..e3f5b1ada 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -61,10 +61,10 @@ def add_arguments(self, parser, cli_name): def render(self, status: DiagnosticStatus, time_sec, verbose=False): level_name, _ = convert_level_to_str(status.level) - if ":" in name: + if ":" in status.name: node, name = status.name.split(":") else: - node = "Aggr" + node = "---" name = status.name name = name.strip() line = [ From 85360cd058791a8ab02c50cdf004bd1cddec9be8 Mon Sep 17 00:00:00 2001 From: robo Date: Thu, 4 May 2023 21:21:02 +0300 Subject: [PATCH 16/26] set version and license --- ros2diagnostics_cli/package.xml | 13 +++++++------ ros2diagnostics_cli/setup.py | 14 +++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ros2diagnostics_cli/package.xml b/ros2diagnostics_cli/package.xml index 4b1d75b4e..c17591dce 100644 --- a/ros2diagnostics_cli/package.xml +++ b/ros2diagnostics_cli/package.xml @@ -1,11 +1,12 @@ - - + + ros2diagnostics_cli - 0.0.0 - TODO: Package description - user - TODO: License declaration + 3.1.2 + diagnostic command for ROS2 command line, parse and show /diagnostics topic + + Christian Henkel + BSD-3-Clause ament_copyright ament_flake8 diff --git a/ros2diagnostics_cli/setup.py b/ros2diagnostics_cli/setup.py index 925bd51e1..0e5fda977 100644 --- a/ros2diagnostics_cli/setup.py +++ b/ros2diagnostics_cli/setup.py @@ -1,23 +1,23 @@ from setuptools import setup from setuptools import find_packages -package_name = 'ros2diagnostics_cli' +PACKAGE_NAME = 'ros2diagnostics_cli' setup( - name=package_name, - version='0.0.1', + name=PACKAGE_NAME, + version='3.1.2', packages=find_packages(exclude=['test']), data_files=[ ('share/ament_index/resource_index/packages', - ['resource/' + package_name]), - ('share/' + package_name, ['package.xml']), + ['resource/' + PACKAGE_NAME]), + ('share/' + PACKAGE_NAME, ['package.xml']), ], install_requires=['ros2cli'], zip_safe=True, maintainer='user', maintainer_email='robo2020@gmail.com', - description='TODO: Package description', - license='TODO: License declaration', + description='diagnostic command for ROS2 command line, parse and show /diagnostics topic', + license='BSD-3-Clause', tests_require=['pytest'], entry_points={ 'ros2cli.command': [ From 6f1b85f3cb1a1149aad36ecc093271d7d68dcfef Mon Sep 17 00:00:00 2001 From: robo Date: Fri, 28 Apr 2023 08:55:21 +0300 Subject: [PATCH 17/26] init tutorials --- diagnostics_tutorial/README.md | 28 ++++++++++ .../diagnostics_tutorial/__init__.py | 0 .../composite_task_demo.py | 53 +++++++++++++++++++ .../freq_diagnostics_demo.py | 38 +++++++++++++ .../status_wrapper_demo.py | 45 ++++++++++++++++ .../diagnostics_tutorial/task_demo.py | 40 ++++++++++++++ diagnostics_tutorial/package.xml | 18 +++++++ .../resource/diagnostics_tutorial | 0 diagnostics_tutorial/setup.cfg | 4 ++ diagnostics_tutorial/setup.py | 29 ++++++++++ diagnostics_tutorial/test/test_copyright.py | 25 +++++++++ diagnostics_tutorial/test/test_flake8.py | 25 +++++++++ diagnostics_tutorial/test/test_pep257.py | 23 ++++++++ .../ros2diagnostics_cli/api/__init__.py | 20 ++++--- 14 files changed, 342 insertions(+), 6 deletions(-) create mode 100644 diagnostics_tutorial/README.md create mode 100644 diagnostics_tutorial/diagnostics_tutorial/__init__.py create mode 100644 diagnostics_tutorial/diagnostics_tutorial/composite_task_demo.py create mode 100644 diagnostics_tutorial/diagnostics_tutorial/freq_diagnostics_demo.py create mode 100644 diagnostics_tutorial/diagnostics_tutorial/status_wrapper_demo.py create mode 100644 diagnostics_tutorial/diagnostics_tutorial/task_demo.py create mode 100644 diagnostics_tutorial/package.xml create mode 100644 diagnostics_tutorial/resource/diagnostics_tutorial create mode 100644 diagnostics_tutorial/setup.cfg create mode 100644 diagnostics_tutorial/setup.py create mode 100644 diagnostics_tutorial/test/test_copyright.py create mode 100644 diagnostics_tutorial/test/test_flake8.py create mode 100644 diagnostics_tutorial/test/test_pep257.py diff --git a/diagnostics_tutorial/README.md b/diagnostics_tutorial/README.md new file mode 100644 index 000000000..a397f6706 --- /dev/null +++ b/diagnostics_tutorial/README.md @@ -0,0 +1,28 @@ +# Diagnostics mini tutorial +base on [code from here](http://docs.ros.org/en/melodic/api/diagnostic_updater/html/example_8cpp_source.html) + +## DiagnosticTask +[API]() + + +[code example](diagnostics_tutorial/task_demo.py) + +## DiagnosticStatusWrapper +[API](https://docs.ros.org/en/humble/p/diagnostic_updater/generated/classdiagnostic__updater_1_1DiagnosticStatusWrapper.html) +Wrapper for the diagnostic_msgs::msg::DiagnosticStatus message that makes it easier to update and handle + +[code example](diagnostics_tutorial/status_wrapper_demo.py) + +## CompositeDiagnosticTask +[API](https://docs.ros.org/en/humble/p/diagnostic_updater/generated/classdiagnostic__updater_1_1CompositeDiagnosticTask.html) +The CompositeDiagnosticTask allows multiple DiagnosticTask instances to be combined into a single task that produces a single DiagnosticStatusWrapped + +[code example](diagnostics_tutorial/composite_task_demo.py) + +## HeaderlessTopicDiagnostic +[API](https://docs.ros.org/en/humble/p/diagnostic_updater/generated/classdiagnostic__updater_1_1HeaderlessTopicDiagnostic.html) +A class to facilitate making diagnostics for a topic using a [FrequencyStatus](#frequencystatus). + +## FrequencyStatus +[API](https://docs.ros.org/en/humble/p/diagnostic_updater/generated/classdiagnostic__updater_1_1FrequencyStatus.html#classdiagnostic__updater_1_1FrequencyStatus) +A diagnostic task that monitors the frequency of an event. \ No newline at end of file diff --git a/diagnostics_tutorial/diagnostics_tutorial/__init__.py b/diagnostics_tutorial/diagnostics_tutorial/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/diagnostics_tutorial/diagnostics_tutorial/composite_task_demo.py b/diagnostics_tutorial/diagnostics_tutorial/composite_task_demo.py new file mode 100644 index 000000000..9b76c64fe --- /dev/null +++ b/diagnostics_tutorial/diagnostics_tutorial/composite_task_demo.py @@ -0,0 +1,53 @@ +import rclpy +from diagnostic_msgs.msg import DiagnosticStatus +from diagnostic_updater import ( + CompositeDiagnosticTask, + Updater, + FunctionDiagnosticTask, + DiagnosticStatusWrapper, +) +from rclpy.node import Node + + +def check_lower_bound(state: DiagnosticStatusWrapper): + state.summary(DiagnosticStatus.OK, "lower ok") + return state + + +def check_upper_bound(state: DiagnosticStatusWrapper): + state.summary(DiagnosticStatus.OK, "upper ok") + return state + + +class MyNode(Node): + def __init__(self): + node_name = "minimal" + super().__init__(node_name) + self.diagnostic_updater = Updater(self) + lower = FunctionDiagnosticTask("lower check", check_lower_bound) + upper = FunctionDiagnosticTask("upper check", check_upper_bound) + comp_task = CompositeDiagnosticTask("range demo") + comp_task.addTask(lower) + comp_task.addTask(upper) + self.diagnostic_updater.add(comp_task) + self.create_timer(1.0, self.__timer_handler) + self.get_logger().info("Hello ROS2") + + def __timer_handler(self): + self.diagnostic_updater.update() + + +def main(): + rclpy.init() + node = MyNode() + try: + rclpy.spin(node) + except KeyboardInterrupt: + print("User exit") + finally: + node.destroy_node() + rclpy.try_shutdown() + + +if __name__ == "__main__": + main() diff --git a/diagnostics_tutorial/diagnostics_tutorial/freq_diagnostics_demo.py b/diagnostics_tutorial/diagnostics_tutorial/freq_diagnostics_demo.py new file mode 100644 index 000000000..74305cffb --- /dev/null +++ b/diagnostics_tutorial/diagnostics_tutorial/freq_diagnostics_demo.py @@ -0,0 +1,38 @@ +import rclpy +from rclpy.node import Node +from diagnostic_updater import HeaderlessTopicDiagnostic, FrequencyStatusParam, Updater + + +class MyNode(Node): + def __init__(self): + node_name = "frequency_diagnostics" + super().__init__(node_name) + + self.diagnostic_updater = Updater(self) + freq_bound = {"min": 0.5, "max": 2} + self.pub_freq = HeaderlessTopicDiagnostic( + "topic1", self.diagnostic_updater, FrequencyStatusParam(freq_bound) + ) + + self.create_timer(3.0, self.__timer_handler) + self.get_logger().info("Hello ROS2") + + def __timer_handler(self): + self.pub_freq.tick() + self.diagnostic_updater.update() + + +def main(args=None): + rclpy.init(args=args) + node = MyNode() + try: + rclpy.spin(node) + except KeyboardInterrupt: + print("User exit") + finally: + node.destroy_node() + rclpy.try_shutdown() + + +if __name__ == "__main__": + main() diff --git a/diagnostics_tutorial/diagnostics_tutorial/status_wrapper_demo.py b/diagnostics_tutorial/diagnostics_tutorial/status_wrapper_demo.py new file mode 100644 index 000000000..740c4e912 --- /dev/null +++ b/diagnostics_tutorial/diagnostics_tutorial/status_wrapper_demo.py @@ -0,0 +1,45 @@ +import rclpy +from diagnostic_msgs.msg import DiagnosticStatus +from diagnostic_updater import DiagnosticStatusWrapper, Updater +from rclpy.node import Node + + +class DummyClass: + def produce_diagnostics(self, stat: DiagnosticStatusWrapper) -> DiagnosticStatusWrapper: + stat.summary(DiagnosticStatus.WARN, "demo class status") + stat.add("dummy data", "2000") + return stat + +def dummy_diagnostics(stat: DiagnosticStatusWrapper) -> DiagnosticStatusWrapper: + stat.summary(DiagnosticStatus.OK, "dummy_diagnostics function") + return stat + +class MyNode(Node): + def __init__(self): + node_name = "minimal" + super().__init__(node_name) + self.diagnostic_updater = Updater(self) + dc = DummyClass() + self.diagnostic_updater.add("method updater", dc.produce_diagnostics) + self.diagnostic_updater.add("function update", dummy_diagnostics) + self.create_timer(1.0, self.__timer_handler) + self.get_logger().info("Hello ROS2") + + def __timer_handler(self): + self.diagnostic_updater.update() + + +def main(): + rclpy.init() + node = MyNode() + try: + rclpy.spin(node) + except KeyboardInterrupt: + print("User exit") + finally: + node.destroy_node() + rclpy.try_shutdown() + + +if __name__ == "__main__": + main() diff --git a/diagnostics_tutorial/diagnostics_tutorial/task_demo.py b/diagnostics_tutorial/diagnostics_tutorial/task_demo.py new file mode 100644 index 000000000..8f1ba0aa9 --- /dev/null +++ b/diagnostics_tutorial/diagnostics_tutorial/task_demo.py @@ -0,0 +1,40 @@ +import rclpy +from rclpy.node import Node +from diagnostic_updater import DiagnosticTask, DiagnosticStatusWrapper, Updater +from diagnostic_msgs.msg import DiagnosticStatus + + +class DummyTask(DiagnosticTask): + def run(self, stat: DiagnosticStatusWrapper): + stat.summary(DiagnosticStatus.WARN, "DummyTask demo") + return stat + + +class MyNode(Node): + def __init__(self): + node_name = "minimal" + super().__init__(node_name) + self.diagnostic_updater = Updater(self) + self.diagnostic_updater.setHardwareID("demo hw") + self.diagnostic_updater.add(DummyTask("dummy_task")) + self.create_timer(1.0, self.__timer_handler) + self.get_logger().info("Hello ROS2") + + def __timer_handler(self): + self.diagnostic_updater.update() + + +def main(args=None): + rclpy.init(args=args) + node = MyNode() + try: + rclpy.spin(node) + except KeyboardInterrupt: + print("User exit") + finally: + node.destroy_node() + rclpy.try_shutdown() + + +if __name__ == "__main__": + main() diff --git a/diagnostics_tutorial/package.xml b/diagnostics_tutorial/package.xml new file mode 100644 index 000000000..d375e65a4 --- /dev/null +++ b/diagnostics_tutorial/package.xml @@ -0,0 +1,18 @@ + + + + diagnostics_tutorial + 0.0.0 + TODO: Package description + user + TODO: License declaration + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/diagnostics_tutorial/resource/diagnostics_tutorial b/diagnostics_tutorial/resource/diagnostics_tutorial new file mode 100644 index 000000000..e69de29bb diff --git a/diagnostics_tutorial/setup.cfg b/diagnostics_tutorial/setup.cfg new file mode 100644 index 000000000..ad477c415 --- /dev/null +++ b/diagnostics_tutorial/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/diagnostics_tutorial +[install] +install_scripts=$base/lib/diagnostics_tutorial diff --git a/diagnostics_tutorial/setup.py b/diagnostics_tutorial/setup.py new file mode 100644 index 000000000..6f7c820e9 --- /dev/null +++ b/diagnostics_tutorial/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup + +package_name = "diagnostics_tutorial" + +setup( + name=package_name, + version="0.0.0", + packages=[package_name], + data_files=[ + ("share/ament_index/resource_index/packages", ["resource/" + package_name]), + ("share/" + package_name, ["package.xml"]), + ], + install_requires=["setuptools"], + zip_safe=True, + maintainer="user", + maintainer_email="robo2020@gmail.com", + description="TODO: Package description", + license="TODO: License declaration", + tests_require=["pytest"], + entry_points={ + "console_scripts": [ + "wrapper_demo=diagnostics_tutorial.status_wrapper_demo:main", + "comp=diagnostics_tutorial.composite_task_demo:main", + "freq=diagnostics_tutorial.freq_diagnostics_demo:main", + "task=diagnostics_tutorial.task_demo:main" + + ], + }, +) diff --git a/diagnostics_tutorial/test/test_copyright.py b/diagnostics_tutorial/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/diagnostics_tutorial/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/diagnostics_tutorial/test/test_flake8.py b/diagnostics_tutorial/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/diagnostics_tutorial/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/diagnostics_tutorial/test/test_pep257.py b/diagnostics_tutorial/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/diagnostics_tutorial/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index 4b490338f..1308ee9dd 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -73,15 +73,17 @@ } -def convert_level_to_str(level) -> Tuple[str, str]: +def convert_level_to_str(level: bytes) -> Tuple[str, str]: return level_to_str_mapping[level]() -def open_file_for_output(csv_file) -> TextIO: +def open_file_for_output(csv_file: str) -> TextIO: base_dir = pathlib.Path(csv_file).parents[0] folder_exists = pathlib.Path(base_dir).is_dir() if not folder_exists: - raise Exception(f"Folder {csv_file} not exists") + raise Exception( + f"Folder {csv_file} not exists" + ) # pylint: disable=[broad-exception-raised] f = open(csv_file, "w", encoding="utf-8") return f @@ -113,7 +115,7 @@ def set_render(self, handler): self.__status_render_handler = handler def run(self): - self.register_and_parse_diagnostics_topic() + self.register_diagnostics_topic() def __filter_level(self, level): if not self.__levels: @@ -169,7 +171,11 @@ def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: if not counter: print(f"No diagnostic for levels: {self.__levels_info}") - def register_and_parse_diagnostics_topic(self): + def register_diagnostics_topic(self): + """ + create ros node and + subscribe to /diagnostic topic + """ if self.__mode == ParserModeEnum.LIST: handler = diagnostic_list_handler else: @@ -198,7 +204,8 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: print group data as yaml to stdout Args: - msg (DiagnosticArray): _description_ + msg (DiagnosticArray): /diagnostics topic message + http://docs.ros.org/en/noetic/api/diagnostic_msgs/html/msg/DiagnosticArray.html """ status: DiagnosticStatus data: Dict[str, List[str]] = {} @@ -219,6 +226,7 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: def add_common_arguments(parser: ArgumentParser): + """common arguments for csv and show verbs""" parser.add_argument("-1", "--once", action="store_true", help="run only once") parser.add_argument( "-f", "--filter", type=str, help="filter diagnostic status name" From afe944e8baa24e9b248d17397fbe9b6d4e2a93fd Mon Sep 17 00:00:00 2001 From: robo Date: Thu, 4 May 2023 21:27:29 +0300 Subject: [PATCH 18/26] remove diagnostic_tutorial from PR --- diagnostics_tutorial/README.md | 28 ---------- .../diagnostics_tutorial/__init__.py | 0 .../composite_task_demo.py | 53 ------------------- .../freq_diagnostics_demo.py | 38 ------------- .../status_wrapper_demo.py | 45 ---------------- .../diagnostics_tutorial/task_demo.py | 40 -------------- diagnostics_tutorial/package.xml | 18 ------- .../resource/diagnostics_tutorial | 0 diagnostics_tutorial/setup.cfg | 4 -- diagnostics_tutorial/setup.py | 29 ---------- diagnostics_tutorial/test/test_copyright.py | 25 --------- diagnostics_tutorial/test/test_flake8.py | 25 --------- diagnostics_tutorial/test/test_pep257.py | 23 -------- 13 files changed, 328 deletions(-) delete mode 100644 diagnostics_tutorial/README.md delete mode 100644 diagnostics_tutorial/diagnostics_tutorial/__init__.py delete mode 100644 diagnostics_tutorial/diagnostics_tutorial/composite_task_demo.py delete mode 100644 diagnostics_tutorial/diagnostics_tutorial/freq_diagnostics_demo.py delete mode 100644 diagnostics_tutorial/diagnostics_tutorial/status_wrapper_demo.py delete mode 100644 diagnostics_tutorial/diagnostics_tutorial/task_demo.py delete mode 100644 diagnostics_tutorial/package.xml delete mode 100644 diagnostics_tutorial/resource/diagnostics_tutorial delete mode 100644 diagnostics_tutorial/setup.cfg delete mode 100644 diagnostics_tutorial/setup.py delete mode 100644 diagnostics_tutorial/test/test_copyright.py delete mode 100644 diagnostics_tutorial/test/test_flake8.py delete mode 100644 diagnostics_tutorial/test/test_pep257.py diff --git a/diagnostics_tutorial/README.md b/diagnostics_tutorial/README.md deleted file mode 100644 index a397f6706..000000000 --- a/diagnostics_tutorial/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Diagnostics mini tutorial -base on [code from here](http://docs.ros.org/en/melodic/api/diagnostic_updater/html/example_8cpp_source.html) - -## DiagnosticTask -[API]() - - -[code example](diagnostics_tutorial/task_demo.py) - -## DiagnosticStatusWrapper -[API](https://docs.ros.org/en/humble/p/diagnostic_updater/generated/classdiagnostic__updater_1_1DiagnosticStatusWrapper.html) -Wrapper for the diagnostic_msgs::msg::DiagnosticStatus message that makes it easier to update and handle - -[code example](diagnostics_tutorial/status_wrapper_demo.py) - -## CompositeDiagnosticTask -[API](https://docs.ros.org/en/humble/p/diagnostic_updater/generated/classdiagnostic__updater_1_1CompositeDiagnosticTask.html) -The CompositeDiagnosticTask allows multiple DiagnosticTask instances to be combined into a single task that produces a single DiagnosticStatusWrapped - -[code example](diagnostics_tutorial/composite_task_demo.py) - -## HeaderlessTopicDiagnostic -[API](https://docs.ros.org/en/humble/p/diagnostic_updater/generated/classdiagnostic__updater_1_1HeaderlessTopicDiagnostic.html) -A class to facilitate making diagnostics for a topic using a [FrequencyStatus](#frequencystatus). - -## FrequencyStatus -[API](https://docs.ros.org/en/humble/p/diagnostic_updater/generated/classdiagnostic__updater_1_1FrequencyStatus.html#classdiagnostic__updater_1_1FrequencyStatus) -A diagnostic task that monitors the frequency of an event. \ No newline at end of file diff --git a/diagnostics_tutorial/diagnostics_tutorial/__init__.py b/diagnostics_tutorial/diagnostics_tutorial/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/diagnostics_tutorial/diagnostics_tutorial/composite_task_demo.py b/diagnostics_tutorial/diagnostics_tutorial/composite_task_demo.py deleted file mode 100644 index 9b76c64fe..000000000 --- a/diagnostics_tutorial/diagnostics_tutorial/composite_task_demo.py +++ /dev/null @@ -1,53 +0,0 @@ -import rclpy -from diagnostic_msgs.msg import DiagnosticStatus -from diagnostic_updater import ( - CompositeDiagnosticTask, - Updater, - FunctionDiagnosticTask, - DiagnosticStatusWrapper, -) -from rclpy.node import Node - - -def check_lower_bound(state: DiagnosticStatusWrapper): - state.summary(DiagnosticStatus.OK, "lower ok") - return state - - -def check_upper_bound(state: DiagnosticStatusWrapper): - state.summary(DiagnosticStatus.OK, "upper ok") - return state - - -class MyNode(Node): - def __init__(self): - node_name = "minimal" - super().__init__(node_name) - self.diagnostic_updater = Updater(self) - lower = FunctionDiagnosticTask("lower check", check_lower_bound) - upper = FunctionDiagnosticTask("upper check", check_upper_bound) - comp_task = CompositeDiagnosticTask("range demo") - comp_task.addTask(lower) - comp_task.addTask(upper) - self.diagnostic_updater.add(comp_task) - self.create_timer(1.0, self.__timer_handler) - self.get_logger().info("Hello ROS2") - - def __timer_handler(self): - self.diagnostic_updater.update() - - -def main(): - rclpy.init() - node = MyNode() - try: - rclpy.spin(node) - except KeyboardInterrupt: - print("User exit") - finally: - node.destroy_node() - rclpy.try_shutdown() - - -if __name__ == "__main__": - main() diff --git a/diagnostics_tutorial/diagnostics_tutorial/freq_diagnostics_demo.py b/diagnostics_tutorial/diagnostics_tutorial/freq_diagnostics_demo.py deleted file mode 100644 index 74305cffb..000000000 --- a/diagnostics_tutorial/diagnostics_tutorial/freq_diagnostics_demo.py +++ /dev/null @@ -1,38 +0,0 @@ -import rclpy -from rclpy.node import Node -from diagnostic_updater import HeaderlessTopicDiagnostic, FrequencyStatusParam, Updater - - -class MyNode(Node): - def __init__(self): - node_name = "frequency_diagnostics" - super().__init__(node_name) - - self.diagnostic_updater = Updater(self) - freq_bound = {"min": 0.5, "max": 2} - self.pub_freq = HeaderlessTopicDiagnostic( - "topic1", self.diagnostic_updater, FrequencyStatusParam(freq_bound) - ) - - self.create_timer(3.0, self.__timer_handler) - self.get_logger().info("Hello ROS2") - - def __timer_handler(self): - self.pub_freq.tick() - self.diagnostic_updater.update() - - -def main(args=None): - rclpy.init(args=args) - node = MyNode() - try: - rclpy.spin(node) - except KeyboardInterrupt: - print("User exit") - finally: - node.destroy_node() - rclpy.try_shutdown() - - -if __name__ == "__main__": - main() diff --git a/diagnostics_tutorial/diagnostics_tutorial/status_wrapper_demo.py b/diagnostics_tutorial/diagnostics_tutorial/status_wrapper_demo.py deleted file mode 100644 index 740c4e912..000000000 --- a/diagnostics_tutorial/diagnostics_tutorial/status_wrapper_demo.py +++ /dev/null @@ -1,45 +0,0 @@ -import rclpy -from diagnostic_msgs.msg import DiagnosticStatus -from diagnostic_updater import DiagnosticStatusWrapper, Updater -from rclpy.node import Node - - -class DummyClass: - def produce_diagnostics(self, stat: DiagnosticStatusWrapper) -> DiagnosticStatusWrapper: - stat.summary(DiagnosticStatus.WARN, "demo class status") - stat.add("dummy data", "2000") - return stat - -def dummy_diagnostics(stat: DiagnosticStatusWrapper) -> DiagnosticStatusWrapper: - stat.summary(DiagnosticStatus.OK, "dummy_diagnostics function") - return stat - -class MyNode(Node): - def __init__(self): - node_name = "minimal" - super().__init__(node_name) - self.diagnostic_updater = Updater(self) - dc = DummyClass() - self.diagnostic_updater.add("method updater", dc.produce_diagnostics) - self.diagnostic_updater.add("function update", dummy_diagnostics) - self.create_timer(1.0, self.__timer_handler) - self.get_logger().info("Hello ROS2") - - def __timer_handler(self): - self.diagnostic_updater.update() - - -def main(): - rclpy.init() - node = MyNode() - try: - rclpy.spin(node) - except KeyboardInterrupt: - print("User exit") - finally: - node.destroy_node() - rclpy.try_shutdown() - - -if __name__ == "__main__": - main() diff --git a/diagnostics_tutorial/diagnostics_tutorial/task_demo.py b/diagnostics_tutorial/diagnostics_tutorial/task_demo.py deleted file mode 100644 index 8f1ba0aa9..000000000 --- a/diagnostics_tutorial/diagnostics_tutorial/task_demo.py +++ /dev/null @@ -1,40 +0,0 @@ -import rclpy -from rclpy.node import Node -from diagnostic_updater import DiagnosticTask, DiagnosticStatusWrapper, Updater -from diagnostic_msgs.msg import DiagnosticStatus - - -class DummyTask(DiagnosticTask): - def run(self, stat: DiagnosticStatusWrapper): - stat.summary(DiagnosticStatus.WARN, "DummyTask demo") - return stat - - -class MyNode(Node): - def __init__(self): - node_name = "minimal" - super().__init__(node_name) - self.diagnostic_updater = Updater(self) - self.diagnostic_updater.setHardwareID("demo hw") - self.diagnostic_updater.add(DummyTask("dummy_task")) - self.create_timer(1.0, self.__timer_handler) - self.get_logger().info("Hello ROS2") - - def __timer_handler(self): - self.diagnostic_updater.update() - - -def main(args=None): - rclpy.init(args=args) - node = MyNode() - try: - rclpy.spin(node) - except KeyboardInterrupt: - print("User exit") - finally: - node.destroy_node() - rclpy.try_shutdown() - - -if __name__ == "__main__": - main() diff --git a/diagnostics_tutorial/package.xml b/diagnostics_tutorial/package.xml deleted file mode 100644 index d375e65a4..000000000 --- a/diagnostics_tutorial/package.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - diagnostics_tutorial - 0.0.0 - TODO: Package description - user - TODO: License declaration - - ament_copyright - ament_flake8 - ament_pep257 - python3-pytest - - - ament_python - - diff --git a/diagnostics_tutorial/resource/diagnostics_tutorial b/diagnostics_tutorial/resource/diagnostics_tutorial deleted file mode 100644 index e69de29bb..000000000 diff --git a/diagnostics_tutorial/setup.cfg b/diagnostics_tutorial/setup.cfg deleted file mode 100644 index ad477c415..000000000 --- a/diagnostics_tutorial/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[develop] -script_dir=$base/lib/diagnostics_tutorial -[install] -install_scripts=$base/lib/diagnostics_tutorial diff --git a/diagnostics_tutorial/setup.py b/diagnostics_tutorial/setup.py deleted file mode 100644 index 6f7c820e9..000000000 --- a/diagnostics_tutorial/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -from setuptools import setup - -package_name = "diagnostics_tutorial" - -setup( - name=package_name, - version="0.0.0", - packages=[package_name], - data_files=[ - ("share/ament_index/resource_index/packages", ["resource/" + package_name]), - ("share/" + package_name, ["package.xml"]), - ], - install_requires=["setuptools"], - zip_safe=True, - maintainer="user", - maintainer_email="robo2020@gmail.com", - description="TODO: Package description", - license="TODO: License declaration", - tests_require=["pytest"], - entry_points={ - "console_scripts": [ - "wrapper_demo=diagnostics_tutorial.status_wrapper_demo:main", - "comp=diagnostics_tutorial.composite_task_demo:main", - "freq=diagnostics_tutorial.freq_diagnostics_demo:main", - "task=diagnostics_tutorial.task_demo:main" - - ], - }, -) diff --git a/diagnostics_tutorial/test/test_copyright.py b/diagnostics_tutorial/test/test_copyright.py deleted file mode 100644 index 97a39196e..000000000 --- a/diagnostics_tutorial/test/test_copyright.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2015 Open Source Robotics Foundation, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ament_copyright.main import main -import pytest - - -# Remove the `skip` decorator once the source file(s) have a copyright header -@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') -@pytest.mark.copyright -@pytest.mark.linter -def test_copyright(): - rc = main(argv=['.', 'test']) - assert rc == 0, 'Found errors' diff --git a/diagnostics_tutorial/test/test_flake8.py b/diagnostics_tutorial/test/test_flake8.py deleted file mode 100644 index 27ee1078f..000000000 --- a/diagnostics_tutorial/test/test_flake8.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2017 Open Source Robotics Foundation, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ament_flake8.main import main_with_errors -import pytest - - -@pytest.mark.flake8 -@pytest.mark.linter -def test_flake8(): - rc, errors = main_with_errors(argv=[]) - assert rc == 0, \ - 'Found %d code style errors / warnings:\n' % len(errors) + \ - '\n'.join(errors) diff --git a/diagnostics_tutorial/test/test_pep257.py b/diagnostics_tutorial/test/test_pep257.py deleted file mode 100644 index b234a3840..000000000 --- a/diagnostics_tutorial/test/test_pep257.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2015 Open Source Robotics Foundation, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ament_pep257.main import main -import pytest - - -@pytest.mark.linter -@pytest.mark.pep257 -def test_pep257(): - rc = main(argv=['.', 'test']) - assert rc == 0, 'Found code style errors / warnings' From 249b2db53dde4a85bea2323c116dff3ecbd2609c Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Wed, 17 May 2023 12:09:35 +0200 Subject: [PATCH 19/26] gh tests for the new package Signed-off-by: Christian Henkel --- .github/workflows/lint.yaml | 1 + .github/workflows/test.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 010a90157..d4575de9e 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -36,4 +36,5 @@ jobs: diagnostic_aggregator diagnostic_common_diagnostics diagnostic_updater + ros2diagnostics_cli self_test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2fe97d371..ca9d8d089 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -19,6 +19,7 @@ jobs: diagnostic_aggregator, diagnostic_common_diagnostics, diagnostic_updater, + ros2diagnostics_cli, self_test, ] distro: [humble, iron, rolling] From 3d43229a9bf635600c26df194160d5045c878d67 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Wed, 17 May 2023 13:48:21 +0200 Subject: [PATCH 20/26] fixing flake8 errors Signed-off-by: Christian Henkel --- .../ros2diagnostics_cli/api/__init__.py | 115 +++++++----------- .../command/diagnostics.py | 9 +- .../ros2diagnostics_cli/verb/csv.py | 36 +++--- .../ros2diagnostics_cli/verb/list.py | 2 +- .../ros2diagnostics_cli/verb/show.py | 12 +- ros2diagnostics_cli/setup.py | 2 +- 6 files changed, 82 insertions(+), 94 deletions(-) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index 1308ee9dd..ef74762c1 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -26,50 +26,31 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. - -"""_summary_ - -std_msgs/Header header # for timestamp -builtin_interfaces/Time stamp - int32 sec - uint32 nanosec -string frame_id -DiagnosticStatus[] status # an array of components being reported on -byte OK=0 -byte WARN=1 -byte ERROR=2 -byte STALE=3 -byte level -string name -string message -string hardware_id -KeyValue[] values - string key - string value -""" -import pathlib -import re from argparse import ArgumentParser from enum import IntEnum +import pathlib +import re from typing import Dict, List, TextIO, Tuple -import rclpy -import yaml from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue + +import rclpy from rclpy.qos import qos_profile_system_default -TOPIC_DIAGNOSTICS = "/diagnostics" +import yaml + +TOPIC_DIAGNOSTICS = '/diagnostics' -COLOR_DEFAULT = "\033[39m" -GREEN = "\033[92m" -RED = "\033[91m" -YELLOW = "\033[93m" +COLOR_DEFAULT = '\033[39m' +GREEN = '\033[92m' +RED = '\033[91m' +YELLOW = '\033[93m' level_to_str_mapping = { - b"\x00": lambda: ("OK", GREEN + "OK" + COLOR_DEFAULT), - b"\x01": lambda: ("WARN", YELLOW + "WARN" + COLOR_DEFAULT), - b"\x02": lambda: ("ERROR", RED + "ERROR" + COLOR_DEFAULT), - b"\x03": lambda: ("STALE", RED + "STALE" + COLOR_DEFAULT), + b'\x00': ('OK', GREEN + 'OK' + COLOR_DEFAULT), + b'\x01': ('WARN', YELLOW + 'WARN' + COLOR_DEFAULT), + b'\x02': ('ERROR', RED + 'ERROR' + COLOR_DEFAULT), + b'\x03': ('STALE', RED + 'STALE' + COLOR_DEFAULT), } @@ -82,10 +63,9 @@ def open_file_for_output(csv_file: str) -> TextIO: folder_exists = pathlib.Path(base_dir).is_dir() if not folder_exists: raise Exception( - f"Folder {csv_file} not exists" + f'Folder {csv_file} not exists' ) # pylint: disable=[broad-exception-raised] - f = open(csv_file, "w", encoding="utf-8") - return f + return open(csv_file, 'w', encoding='utf-8') class ParserModeEnum(IntEnum): @@ -108,7 +88,7 @@ def __init__( self.__mode = mode self.__run_once = run_once self.__verbose = verbose - self.__levels_info = ",".join(levels) if levels is not None else "" + self.__levels_info = ','.join(levels) if levels is not None else '' self.__levels = DiagnosticsParser.map_level_from_name(levels) def set_render(self, handler): @@ -129,9 +109,9 @@ def map_level_from_name(levels: List[str]) -> List[bytes]: return b_levels map_levels = { - "info": lambda: b_levels.append(b"\x00"), - "warn": lambda: b_levels.append(b"\x01"), - "error": lambda: b_levels.append(b"\x02"), + 'info': lambda: b_levels.append(b'\x00'), + 'warn': lambda: b_levels.append(b'\x01'), + 'error': lambda: b_levels.append(b'\x02'), } for level in levels: @@ -141,23 +121,23 @@ def map_level_from_name(levels: List[str]) -> List[bytes]: @staticmethod def render(status: DiagnosticStatus, time_sec, verbose): _, level_name = convert_level_to_str(status.level) - item = f"{status.name}: {level_name}, {status.message}" + item = f'{status.name}: {level_name}, {status.message}' print(item) if verbose: kv: KeyValue for kv in status.values: - print(f"- {kv.key}={kv.value}") + print(f'- {kv.key}={kv.value}') def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: - """Run handler for each DiagnosticStatus in array - filter status by level, name, node name + """ + Filter DiagnosticStatus by level, name and node name. Args: msg (DiagnosticArray): _description_ """ counter: int = 0 status: DiagnosticStatus - print(f"--- time: {msg.header.stamp.sec} ---") + print(f'--- time: {msg.header.stamp.sec} ---') for status in msg.status: if self.__name_filter: result = re.search(self.__name_filter, status.name) @@ -165,24 +145,22 @@ def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: continue if self.__filter_level(status.level): continue - self.__status_render_handler(status, msg.header.stamp.sec, self.__verbose) + self.__status_render_handler( + status, msg.header.stamp.sec, self.__verbose) counter += 1 if not counter: - print(f"No diagnostic for levels: {self.__levels_info}") + print(f'No diagnostic for levels: {self.__levels_info}') def register_diagnostics_topic(self): - """ - create ros node and - subscribe to /diagnostic topic - """ + """Create ros node and subscribe to /diagnostic topic.""" if self.__mode == ParserModeEnum.LIST: handler = diagnostic_list_handler else: handler = self.diagnostics_status_handler rclpy.init() - node = rclpy.create_node("ros2diagnostics_cli_filter") + node = rclpy.create_node('ros2diagnostics_cli_filter') node.create_subscription( DiagnosticArray, TOPIC_DIAGNOSTICS, @@ -200,21 +178,20 @@ def register_diagnostics_topic(self): def diagnostic_list_handler(msg: DiagnosticArray) -> None: - """Group diagnostics Task by node - print group data as yaml to stdout + """ + Print group data as yaml to stdout. Args: - msg (DiagnosticArray): /diagnostics topic message - http://docs.ros.org/en/noetic/api/diagnostic_msgs/html/msg/DiagnosticArray.html + msg (DiagnosticArray): /diagnostics topic message """ status: DiagnosticStatus data: Dict[str, List[str]] = {} - print(f"--- time: {msg.header.stamp.sec} ---") + print(f'--- time: {msg.header.stamp.sec} ---') for status in msg.status: - if ":" in status.name: - node, name = status.name.split(":") + if ':' in status.name: + node, name = status.name.split(':') else: - node = "---" + node = '---' name = status.name name = name.strip() if node in data: @@ -226,16 +203,16 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: def add_common_arguments(parser: ArgumentParser): - """common arguments for csv and show verbs""" - parser.add_argument("-1", "--once", action="store_true", help="run only once") + """Add common arguments for csv and show verbs.""" + parser.add_argument('-1', '--once', action='store_true', help='run only once') parser.add_argument( - "-f", "--filter", type=str, help="filter diagnostic status name" + '-f', '--filter', type=str, help='filter diagnostic status name' ) parser.add_argument( - "-l", - "--levels", - action="append", + '-l', + '--levels', + action='append', type=str, - choices=["info", "warn", "error"], - help="levels to filter, can be multiple times", + choices=['info', 'warn', 'error'], + help='levels to filter, can be multiple times', ) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py b/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py index 577f02aea..352973eac 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py @@ -32,6 +32,7 @@ class DiagCommand(CommandExtension): + def __init__(self): super(DiagCommand, self).__init__() self._subparser = None @@ -39,9 +40,13 @@ def __init__(self): def add_arguments(self, parser, cli_name): self._subparser = parser add_subparsers_on_demand( - parser, cli_name, '_verb', "ros2diagnostics_cli.verb", required=False) + parser, + cli_name, + '_verb', + 'ros2diagnostics_cli.verb', + required=False) - def main(self, *, parser, args): + def main(self, *, _, args): if not hasattr(args, '_verb'): self._subparser.print_help() return 0 diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index e3f5b1ada..956450296 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -28,43 +28,49 @@ from typing import TextIO + +from diagnostic_msgs.msg import DiagnosticStatus, KeyValue + from ros2cli.verb import VerbExtension from ros2diagnostics_cli.api import ( + add_common_arguments, + convert_level_to_str, DiagnosticsParser, open_file_for_output, - add_common_arguments, - ParserModeEnum, - convert_level_to_str + ParserModeEnum ) -from diagnostic_msgs.msg import DiagnosticStatus, KeyValue class CSVVerb(VerbExtension): - """export /diagnostics message to csv file""" + """Export /diagnostics message to csv file.""" def __init__(self): super().__init__() self.csv: TextIO = None - def add_arguments(self, parser, cli_name): + def add_arguments(self, parser, _): add_common_arguments(parser) parser.add_argument( - "--output", "-o", type=str, required=True, help="export file full path" + '--output', + '-o', + type=str, + required=True, + help='export file full path' ) parser.add_argument( - "--verbose", - "-v", - action="store_true", - help="export DiagnosticStatus values filed", + '--verbose', + '-v', + action='store_true', + help='export DiagnosticStatus values filed', ) def render(self, status: DiagnosticStatus, time_sec, verbose=False): level_name, _ = convert_level_to_str(status.level) - if ":" in status.name: - node, name = status.name.split(":") + if ':' in status.name: + node, name = status.name.split(':') else: - node = "---" + node = '---' name = status.name name = name.strip() line = [ @@ -80,7 +86,7 @@ def render(self, status: DiagnosticStatus, time_sec, verbose=False): for kv in status.values: line.append(kv.value) - s_line = ",".join(line) + "\n" + s_line = ','.join(line) + '\n' print(s_line) self.csv.write(s_line) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py index 9169208ff..2f7610bdf 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py @@ -32,7 +32,7 @@ class ListVerb(VerbExtension): - """List all diagnostic status items group by node name""" + """List all diagnostic status items group by node name.""" def main(self, *, args): diagnostic_parser = DiagnosticsParser(ParserModeEnum.LIST) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py index 61eb409ac..1ee9d821c 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py @@ -29,22 +29,22 @@ from ros2cli.verb import VerbExtension from ros2diagnostics_cli.api import ( - DiagnosticsParser, add_common_arguments, + DiagnosticsParser, ParserModeEnum, ) class ShowVerb(VerbExtension): - """Show diagnostics status item info""" + """Show diagnostics status item info.""" def add_arguments(self, parser, cli_name): add_common_arguments(parser) parser.add_argument( - "--verbose", - "-v", - action="store_true", - help="Display more info.", + '--verbose', + '-v', + action='store_true', + help='Display more info.', ) def main(self, *, args): diff --git a/ros2diagnostics_cli/setup.py b/ros2diagnostics_cli/setup.py index 0e5fda977..a08d428ea 100644 --- a/ros2diagnostics_cli/setup.py +++ b/ros2diagnostics_cli/setup.py @@ -1,5 +1,5 @@ -from setuptools import setup from setuptools import find_packages +from setuptools import setup PACKAGE_NAME = 'ros2diagnostics_cli' From ccd80a2dcaa33368433296154df5c124bcbcb174 Mon Sep 17 00:00:00 2001 From: Christian Henkel Date: Wed, 17 May 2023 13:49:42 +0200 Subject: [PATCH 21/26] fixing pep257 errors Signed-off-by: Christian Henkel --- ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index ef74762c1..10616b095 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -133,7 +133,9 @@ def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: Filter DiagnosticStatus by level, name and node name. Args: + ---- msg (DiagnosticArray): _description_ + """ counter: int = 0 status: DiagnosticStatus @@ -182,7 +184,9 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: Print group data as yaml to stdout. Args: + ---- msg (DiagnosticArray): /diagnostics topic message + """ status: DiagnosticStatus data: Dict[str, List[str]] = {} From 811209363c77aa91fef5cbd0e45d3b9bd5ee3c67 Mon Sep 17 00:00:00 2001 From: Russ Webber Date: Sat, 7 Oct 2023 12:35:16 +1100 Subject: [PATCH 22/26] fix: address suggestions --- ros2diagnostics_cli/package.xml | 15 ++++--- .../ros2diagnostics_cli/api/__init__.py | 45 ++++++++++--------- .../ros2diagnostics_cli/verb/csv.py | 6 +-- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/ros2diagnostics_cli/package.xml b/ros2diagnostics_cli/package.xml index c17591dce..cc388b334 100644 --- a/ros2diagnostics_cli/package.xml +++ b/ros2diagnostics_cli/package.xml @@ -2,18 +2,23 @@ ros2diagnostics_cli - 3.1.2 - diagnostic command for ROS2 command line, parse and show /diagnostics topic + 1.0.0 + diagnostic command for ROS2 command line, parse and show diagnostics topic + Christian Henkel BSD-3-Clause + rclpy + ros2cli + python3-yaml + osrf_pycommon + ament_copyright ament_flake8 - python3-pytest - ros2cli - python3-yaml + + diagnostic_msgs ament_python diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py index 10616b095..73dc20ef9 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py @@ -28,7 +28,7 @@ from argparse import ArgumentParser from enum import IntEnum -import pathlib +from pathlib import Path import re from typing import Dict, List, TextIO, Tuple @@ -36,21 +36,17 @@ import rclpy from rclpy.qos import qos_profile_system_default +from osrf_pycommon.terminal_color import format_color import yaml TOPIC_DIAGNOSTICS = '/diagnostics' -COLOR_DEFAULT = '\033[39m' -GREEN = '\033[92m' -RED = '\033[91m' -YELLOW = '\033[93m' - level_to_str_mapping = { - b'\x00': ('OK', GREEN + 'OK' + COLOR_DEFAULT), - b'\x01': ('WARN', YELLOW + 'WARN' + COLOR_DEFAULT), - b'\x02': ('ERROR', RED + 'ERROR' + COLOR_DEFAULT), - b'\x03': ('STALE', RED + 'STALE' + COLOR_DEFAULT), + DiagnosticStatus.OK: ('OK', format_color("@{greenf}OK@{reset}.")), + DiagnosticStatus.WARN: ('WARN', format_color("@{yellowf}WARN@{reset}.")), + DiagnosticStatus.ERROR: ('ERROR', format_color("@{redf}ERROR@{reset}.")), + DiagnosticStatus.STALE: ('STALE', format_color("@{redf}STALE@{reset}.")), } @@ -59,13 +55,12 @@ def convert_level_to_str(level: bytes) -> Tuple[str, str]: def open_file_for_output(csv_file: str) -> TextIO: - base_dir = pathlib.Path(csv_file).parents[0] - folder_exists = pathlib.Path(base_dir).is_dir() - if not folder_exists: - raise Exception( - f'Folder {csv_file} not exists' - ) # pylint: disable=[broad-exception-raised] - return open(csv_file, 'w', encoding='utf-8') + csv_path = Path(csv_file) + if not csv_path.parent.is_dir(): + raise FileNotFoundError( + 'Cannot write file, directory does not exist' + ) + return csv_path.open('w', encoding='utf-8') class ParserModeEnum(IntEnum): @@ -98,9 +93,7 @@ def run(self): self.register_diagnostics_topic() def __filter_level(self, level): - if not self.__levels: - return False - return level not in self.__levels + return False if not self.__levels else level not in self.__levels @staticmethod def map_level_from_name(levels: List[str]) -> List[bytes]: @@ -208,9 +201,17 @@ def diagnostic_list_handler(msg: DiagnosticArray) -> None: def add_common_arguments(parser: ArgumentParser): """Add common arguments for csv and show verbs.""" - parser.add_argument('-1', '--once', action='store_true', help='run only once') parser.add_argument( - '-f', '--filter', type=str, help='filter diagnostic status name' + '-1', + '--once', + action='store_true', + help='run only once' + ) + parser.add_argument( + '-f' + '--filter', + type=str, + help='filter by diagnostic status name' ) parser.add_argument( '-l', diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py index 956450296..659f00c57 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py @@ -82,9 +82,7 @@ def render(self, status: DiagnosticStatus, time_sec, verbose=False): status.hardware_id, ] if verbose: - kv: KeyValue - for kv in status.values: - line.append(kv.value) + line.extend(kv.value for kv in status.values) s_line = ','.join(line) + '\n' print(s_line) @@ -95,7 +93,7 @@ def main(self, *, args): try: self.csv = open_file_for_output(args.output) except Exception as error: - print(str(error)) + print(error) return try: From 2dd80c0929be5fa1d04a0bd670ba9aaa4aad9893 Mon Sep 17 00:00:00 2001 From: Russ Webber Date: Sat, 7 Oct 2023 12:47:01 +1100 Subject: [PATCH 23/26] fix: remove _cli suffix --- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yaml | 2 +- {ros2diagnostics_cli => ros2diagnostics}/README.md | 0 .../package.xml | 2 +- .../resource/ros2diagnostics | 0 .../ros2diagnostics}/__init__.py | 0 .../ros2diagnostics}/api/__init__.py | 2 +- .../ros2diagnostics}/command/__init__.py | 0 .../ros2diagnostics}/command/diagnostics.py | 2 +- .../ros2diagnostics}/verb/__init__.py | 0 .../ros2diagnostics}/verb/csv.py | 2 +- .../ros2diagnostics}/verb/list.py | 2 +- .../ros2diagnostics}/verb/show.py | 2 +- ros2diagnostics/setup.cfg | 4 ++++ {ros2diagnostics_cli => ros2diagnostics}/setup.py | 14 +++++++------- .../test/test_copyright.py | 0 .../test/test_flake8.py | 0 ros2diagnostics_cli/setup.cfg | 4 ---- 18 files changed, 19 insertions(+), 19 deletions(-) rename {ros2diagnostics_cli => ros2diagnostics}/README.md (100%) rename {ros2diagnostics_cli => ros2diagnostics}/package.xml (96%) rename ros2diagnostics_cli/resource/ros2diagnostics_cli => ros2diagnostics/resource/ros2diagnostics (100%) rename {ros2diagnostics_cli/ros2diagnostics_cli => ros2diagnostics/ros2diagnostics}/__init__.py (100%) rename {ros2diagnostics_cli/ros2diagnostics_cli => ros2diagnostics/ros2diagnostics}/api/__init__.py (99%) rename {ros2diagnostics_cli/ros2diagnostics_cli => ros2diagnostics/ros2diagnostics}/command/__init__.py (100%) rename {ros2diagnostics_cli/ros2diagnostics_cli => ros2diagnostics/ros2diagnostics}/command/diagnostics.py (98%) rename {ros2diagnostics_cli/ros2diagnostics_cli => ros2diagnostics/ros2diagnostics}/verb/__init__.py (100%) rename {ros2diagnostics_cli/ros2diagnostics_cli => ros2diagnostics/ros2diagnostics}/verb/csv.py (98%) rename {ros2diagnostics_cli/ros2diagnostics_cli => ros2diagnostics/ros2diagnostics}/verb/list.py (96%) rename {ros2diagnostics_cli/ros2diagnostics_cli => ros2diagnostics/ros2diagnostics}/verb/show.py (98%) create mode 100644 ros2diagnostics/setup.cfg rename {ros2diagnostics_cli => ros2diagnostics}/setup.py (63%) rename {ros2diagnostics_cli => ros2diagnostics}/test/test_copyright.py (100%) rename {ros2diagnostics_cli => ros2diagnostics}/test/test_flake8.py (100%) delete mode 100644 ros2diagnostics_cli/setup.cfg diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d4575de9e..eb90cae3b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -36,5 +36,5 @@ jobs: diagnostic_aggregator diagnostic_common_diagnostics diagnostic_updater - ros2diagnostics_cli + ros2diagnostics self_test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ca9d8d089..f0b5647fc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -19,7 +19,7 @@ jobs: diagnostic_aggregator, diagnostic_common_diagnostics, diagnostic_updater, - ros2diagnostics_cli, + ros2diagnostics, self_test, ] distro: [humble, iron, rolling] diff --git a/ros2diagnostics_cli/README.md b/ros2diagnostics/README.md similarity index 100% rename from ros2diagnostics_cli/README.md rename to ros2diagnostics/README.md diff --git a/ros2diagnostics_cli/package.xml b/ros2diagnostics/package.xml similarity index 96% rename from ros2diagnostics_cli/package.xml rename to ros2diagnostics/package.xml index cc388b334..c5e8ae8b6 100644 --- a/ros2diagnostics_cli/package.xml +++ b/ros2diagnostics/package.xml @@ -1,7 +1,7 @@ - ros2diagnostics_cli + ros2diagnostics 1.0.0 diagnostic command for ROS2 command line, parse and show diagnostics topic diff --git a/ros2diagnostics_cli/resource/ros2diagnostics_cli b/ros2diagnostics/resource/ros2diagnostics similarity index 100% rename from ros2diagnostics_cli/resource/ros2diagnostics_cli rename to ros2diagnostics/resource/ros2diagnostics diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/__init__.py b/ros2diagnostics/ros2diagnostics/__init__.py similarity index 100% rename from ros2diagnostics_cli/ros2diagnostics_cli/__init__.py rename to ros2diagnostics/ros2diagnostics/__init__.py diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py b/ros2diagnostics/ros2diagnostics/api/__init__.py similarity index 99% rename from ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py rename to ros2diagnostics/ros2diagnostics/api/__init__.py index 73dc20ef9..b06a84052 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/api/__init__.py +++ b/ros2diagnostics/ros2diagnostics/api/__init__.py @@ -155,7 +155,7 @@ def register_diagnostics_topic(self): handler = self.diagnostics_status_handler rclpy.init() - node = rclpy.create_node('ros2diagnostics_cli_filter') + node = rclpy.create_node('ros2diagnostics_filter') node.create_subscription( DiagnosticArray, TOPIC_DIAGNOSTICS, diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/command/__init__.py b/ros2diagnostics/ros2diagnostics/command/__init__.py similarity index 100% rename from ros2diagnostics_cli/ros2diagnostics_cli/command/__init__.py rename to ros2diagnostics/ros2diagnostics/command/__init__.py diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py b/ros2diagnostics/ros2diagnostics/command/diagnostics.py similarity index 98% rename from ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py rename to ros2diagnostics/ros2diagnostics/command/diagnostics.py index 352973eac..85127c193 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/command/diagnostics.py +++ b/ros2diagnostics/ros2diagnostics/command/diagnostics.py @@ -43,7 +43,7 @@ def add_arguments(self, parser, cli_name): parser, cli_name, '_verb', - 'ros2diagnostics_cli.verb', + 'ros2diagnostics.verb', required=False) def main(self, *, _, args): diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/__init__.py b/ros2diagnostics/ros2diagnostics/verb/__init__.py similarity index 100% rename from ros2diagnostics_cli/ros2diagnostics_cli/verb/__init__.py rename to ros2diagnostics/ros2diagnostics/verb/__init__.py diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py b/ros2diagnostics/ros2diagnostics/verb/csv.py similarity index 98% rename from ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py rename to ros2diagnostics/ros2diagnostics/verb/csv.py index 659f00c57..6131d4708 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/csv.py +++ b/ros2diagnostics/ros2diagnostics/verb/csv.py @@ -32,7 +32,7 @@ from diagnostic_msgs.msg import DiagnosticStatus, KeyValue from ros2cli.verb import VerbExtension -from ros2diagnostics_cli.api import ( +from ros2diagnostics.api import ( add_common_arguments, convert_level_to_str, DiagnosticsParser, diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py b/ros2diagnostics/ros2diagnostics/verb/list.py similarity index 96% rename from ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py rename to ros2diagnostics/ros2diagnostics/verb/list.py index 2f7610bdf..a6b6fd6fd 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/list.py +++ b/ros2diagnostics/ros2diagnostics/verb/list.py @@ -28,7 +28,7 @@ from ros2cli.verb import VerbExtension -from ros2diagnostics_cli.api import DiagnosticsParser, ParserModeEnum +from ros2diagnostics.api import DiagnosticsParser, ParserModeEnum class ListVerb(VerbExtension): diff --git a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py b/ros2diagnostics/ros2diagnostics/verb/show.py similarity index 98% rename from ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py rename to ros2diagnostics/ros2diagnostics/verb/show.py index 1ee9d821c..02c522498 100644 --- a/ros2diagnostics_cli/ros2diagnostics_cli/verb/show.py +++ b/ros2diagnostics/ros2diagnostics/verb/show.py @@ -28,7 +28,7 @@ from ros2cli.verb import VerbExtension -from ros2diagnostics_cli.api import ( +from ros2diagnostics.api import ( add_common_arguments, DiagnosticsParser, ParserModeEnum, diff --git a/ros2diagnostics/setup.cfg b/ros2diagnostics/setup.cfg new file mode 100644 index 000000000..52906d605 --- /dev/null +++ b/ros2diagnostics/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/ros2diagnostics +[install] +install_scripts=$base/lib/ros2diagnostics diff --git a/ros2diagnostics_cli/setup.py b/ros2diagnostics/setup.py similarity index 63% rename from ros2diagnostics_cli/setup.py rename to ros2diagnostics/setup.py index a08d428ea..29bf31f94 100644 --- a/ros2diagnostics_cli/setup.py +++ b/ros2diagnostics/setup.py @@ -1,7 +1,7 @@ from setuptools import find_packages from setuptools import setup -PACKAGE_NAME = 'ros2diagnostics_cli' +PACKAGE_NAME = 'ros2diagnostics' setup( name=PACKAGE_NAME, @@ -21,13 +21,13 @@ tests_require=['pytest'], entry_points={ 'ros2cli.command': [ - 'diagnostics = ros2diagnostics_cli.command.diagnostics:DiagCommand', + 'diagnostics = ros2diagnostics.command.diagnostics:DiagCommand', ], - 'ros2diagnostics_cli.verb': [ - 'world = ros2diagnostics_cli.verb.world:WorldVerb', - 'csv = ros2diagnostics_cli.verb.csv:CSVVerb', - 'list = ros2diagnostics_cli.verb.list:ListVerb', - 'show = ros2diagnostics_cli.verb.show:ShowVerb' + 'ros2diagnostics.verb': [ + 'world = ros2diagnostics.verb.world:WorldVerb', + 'csv = ros2diagnostics.verb.csv:CSVVerb', + 'list = ros2diagnostics.verb.list:ListVerb', + 'show = ros2diagnostics.verb.show:ShowVerb' ] }, ) diff --git a/ros2diagnostics_cli/test/test_copyright.py b/ros2diagnostics/test/test_copyright.py similarity index 100% rename from ros2diagnostics_cli/test/test_copyright.py rename to ros2diagnostics/test/test_copyright.py diff --git a/ros2diagnostics_cli/test/test_flake8.py b/ros2diagnostics/test/test_flake8.py similarity index 100% rename from ros2diagnostics_cli/test/test_flake8.py rename to ros2diagnostics/test/test_flake8.py diff --git a/ros2diagnostics_cli/setup.cfg b/ros2diagnostics_cli/setup.cfg deleted file mode 100644 index 3f0786bae..000000000 --- a/ros2diagnostics_cli/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[develop] -script_dir=$base/lib/ros2diagnostics_cli -[install] -install_scripts=$base/lib/ros2diagnostics_cli From 38ab78b16cd99283187f13352b31a315897b2eb9 Mon Sep 17 00:00:00 2001 From: Russ Webber Date: Sun, 8 Oct 2023 18:19:37 +1100 Subject: [PATCH 24/26] wip --- .../ros2diagnostics/api/__init__.py | 72 +------------------ ros2diagnostics/ros2diagnostics/verb/list.py | 36 +++++++++- ros2diagnostics/ros2diagnostics/verb/show.py | 31 +++++++- 3 files changed, 63 insertions(+), 76 deletions(-) diff --git a/ros2diagnostics/ros2diagnostics/api/__init__.py b/ros2diagnostics/ros2diagnostics/api/__init__.py index b06a84052..4eacc9842 100644 --- a/ros2diagnostics/ros2diagnostics/api/__init__.py +++ b/ros2diagnostics/ros2diagnostics/api/__init__.py @@ -27,7 +27,6 @@ # POSSIBILITY OF SUCH DAMAGE. from argparse import ArgumentParser -from enum import IntEnum from pathlib import Path import re from typing import Dict, List, TextIO, Tuple @@ -38,7 +37,6 @@ from rclpy.qos import qos_profile_system_default from osrf_pycommon.terminal_color import format_color -import yaml TOPIC_DIAGNOSTICS = '/diagnostics' @@ -63,16 +61,9 @@ def open_file_for_output(csv_file: str) -> TextIO: return csv_path.open('w', encoding='utf-8') -class ParserModeEnum(IntEnum): - LIST = 1 - CSV = 2 - SHOW = 3 - - class DiagnosticsParser: def __init__( self, - mode: ParserModeEnum, verbose=False, levels=None, run_once=False, @@ -80,7 +71,6 @@ def __init__( ) -> None: self.__name_filter = name_filter self.__status_render_handler = DiagnosticsParser.render - self.__mode = mode self.__run_once = run_once self.__verbose = verbose self.__levels_info = ','.join(levels) if levels is not None else '' @@ -89,9 +79,6 @@ def __init__( def set_render(self, handler): self.__status_render_handler = handler - def run(self): - self.register_diagnostics_topic() - def __filter_level(self, level): return False if not self.__levels else level not in self.__levels @@ -121,38 +108,8 @@ def render(status: DiagnosticStatus, time_sec, verbose): for kv in status.values: print(f'- {kv.key}={kv.value}') - def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: - """ - Filter DiagnosticStatus by level, name and node name. - - Args: - ---- - msg (DiagnosticArray): _description_ - - """ - counter: int = 0 - status: DiagnosticStatus - print(f'--- time: {msg.header.stamp.sec} ---') - for status in msg.status: - if self.__name_filter: - result = re.search(self.__name_filter, status.name) - if not result: - continue - if self.__filter_level(status.level): - continue - self.__status_render_handler( - status, msg.header.stamp.sec, self.__verbose) - counter += 1 - - if not counter: - print(f'No diagnostic for levels: {self.__levels_info}') - - def register_diagnostics_topic(self): + def register_diagnostics_topic(self, handler): """Create ros node and subscribe to /diagnostic topic.""" - if self.__mode == ParserModeEnum.LIST: - handler = diagnostic_list_handler - else: - handler = self.diagnostics_status_handler rclpy.init() node = rclpy.create_node('ros2diagnostics_filter') @@ -172,33 +129,6 @@ def register_diagnostics_topic(self): rclpy.try_shutdown() -def diagnostic_list_handler(msg: DiagnosticArray) -> None: - """ - Print group data as yaml to stdout. - - Args: - ---- - msg (DiagnosticArray): /diagnostics topic message - - """ - status: DiagnosticStatus - data: Dict[str, List[str]] = {} - print(f'--- time: {msg.header.stamp.sec} ---') - for status in msg.status: - if ':' in status.name: - node, name = status.name.split(':') - else: - node = '---' - name = status.name - name = name.strip() - if node in data: - data[node].append(name) - else: - data[node] = [name] - - print(yaml.dump(data)) - - def add_common_arguments(parser: ArgumentParser): """Add common arguments for csv and show verbs.""" parser.add_argument( diff --git a/ros2diagnostics/ros2diagnostics/verb/list.py b/ros2diagnostics/ros2diagnostics/verb/list.py index a6b6fd6fd..1a557fb3f 100644 --- a/ros2diagnostics/ros2diagnostics/verb/list.py +++ b/ros2diagnostics/ros2diagnostics/verb/list.py @@ -26,14 +26,44 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from typing import Dict, List, TextIO, Tuple +import yaml from ros2cli.verb import VerbExtension -from ros2diagnostics.api import DiagnosticsParser, ParserModeEnum +from ros2diagnostics.api import DiagnosticsParser +from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue class ListVerb(VerbExtension): """List all diagnostic status items group by node name.""" def main(self, *, args): - diagnostic_parser = DiagnosticsParser(ParserModeEnum.LIST) - diagnostic_parser.run() + diagnostic_parser = DiagnosticsParser() + diagnostic_parser.register_diagnostics_topic(diagnostic_list_handler) + + +def diagnostic_list_handler(msg: DiagnosticArray) -> None: + """ + Print group data as yaml to stdout. + + Args: + ---- + msg (DiagnosticArray): /diagnostics topic message + + """ + status: DiagnosticStatus + data: Dict[str, List[str]] = {} + print(f'--- time: {msg.header.stamp.sec} ---') + for status in msg.status: + if ':' in status.name: + node, name = status.name.split(':') + else: + node = '---' + name = status.name + name = name.strip() + if node in data: + data[node].append(name) + else: + data[node] = [name] + + print(yaml.dump(data)) diff --git a/ros2diagnostics/ros2diagnostics/verb/show.py b/ros2diagnostics/ros2diagnostics/verb/show.py index 02c522498..5cbea0692 100644 --- a/ros2diagnostics/ros2diagnostics/verb/show.py +++ b/ros2diagnostics/ros2diagnostics/verb/show.py @@ -31,9 +31,11 @@ from ros2diagnostics.api import ( add_common_arguments, DiagnosticsParser, - ParserModeEnum, ) +from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue + + class ShowVerb(VerbExtension): """Show diagnostics status item info.""" @@ -49,10 +51,35 @@ def add_arguments(self, parser, cli_name): def main(self, *, args): diagnostic_parser = DiagnosticsParser( - mode=ParserModeEnum.SHOW, verbose=args.verbose, levels=args.levels, run_once=args.once, name_filter=args.filter, ) diagnostic_parser.run() + + def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: + """ + Filter DiagnosticStatus by level, name and node name. + + Args: + ---- + msg (DiagnosticArray): _description_ + + """ + counter: int = 0 + status: DiagnosticStatus + print(f'--- time: {msg.header.stamp.sec} ---') + for status in msg.status: + if self.__name_filter: + result = re.search(self.__name_filter, status.name) + if not result: + continue + if self.__filter_level(status.level): + continue + self.__status_render_handler( + status, msg.header.stamp.sec, self.__verbose) + counter += 1 + + if not counter: + print(f'No diagnostic for levels: {self.__levels_info}') From f9cb48c1f83f430d2a148bb7817b9553f0de389d Mon Sep 17 00:00:00 2001 From: Russ Webber Date: Sun, 8 Oct 2023 23:43:41 +1100 Subject: [PATCH 25/26] fix: list, show work --- ros2diagnostics/package.xml | 2 +- .../ros2diagnostics/api/__init__.py | 57 +++++++------------ .../ros2diagnostics/command/diagnostics.py | 9 +-- ros2diagnostics/ros2diagnostics/verb/csv.py | 5 +- ros2diagnostics/ros2diagnostics/verb/list.py | 2 +- ros2diagnostics/ros2diagnostics/verb/show.py | 25 +++++--- ros2diagnostics/setup.py | 3 +- 7 files changed, 46 insertions(+), 57 deletions(-) diff --git a/ros2diagnostics/package.xml b/ros2diagnostics/package.xml index c5e8ae8b6..b2559bac3 100644 --- a/ros2diagnostics/package.xml +++ b/ros2diagnostics/package.xml @@ -2,7 +2,7 @@ ros2diagnostics - 1.0.0 + 3.1.2 diagnostic command for ROS2 command line, parse and show diagnostics topic diff --git a/ros2diagnostics/ros2diagnostics/api/__init__.py b/ros2diagnostics/ros2diagnostics/api/__init__.py index 4eacc9842..44a6ca857 100644 --- a/ros2diagnostics/ros2diagnostics/api/__init__.py +++ b/ros2diagnostics/ros2diagnostics/api/__init__.py @@ -28,7 +28,6 @@ from argparse import ArgumentParser from pathlib import Path -import re from typing import Dict, List, TextIO, Tuple from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue @@ -49,7 +48,7 @@ def convert_level_to_str(level: bytes) -> Tuple[str, str]: - return level_to_str_mapping[level]() + return level_to_str_mapping[level] def open_file_for_output(csv_file: str) -> TextIO: @@ -61,53 +60,37 @@ def open_file_for_output(csv_file: str) -> TextIO: return csv_path.open('w', encoding='utf-8') +def map_level_from_name(levels: List[str]) -> List[bytes]: + b_levels = [] + if levels is None: + return b_levels + + map_levels = { + 'info': lambda: b_levels.append(b'\x00'), + 'warn': lambda: b_levels.append(b'\x01'), + 'error': lambda: b_levels.append(b'\x02'), + } + + for level in levels: + map_levels[level]() + return b_levels + + class DiagnosticsParser: def __init__( self, verbose=False, levels=None, run_once=False, - name_filter=None, ) -> None: - self.__name_filter = name_filter - self.__status_render_handler = DiagnosticsParser.render self.__run_once = run_once self.__verbose = verbose self.__levels_info = ','.join(levels) if levels is not None else '' - self.__levels = DiagnosticsParser.map_level_from_name(levels) - - def set_render(self, handler): - self.__status_render_handler = handler + self.__levels = map_level_from_name(levels) - def __filter_level(self, level): + def filter_level(self, level): return False if not self.__levels else level not in self.__levels - @staticmethod - def map_level_from_name(levels: List[str]) -> List[bytes]: - b_levels = [] - if levels is None: - return b_levels - - map_levels = { - 'info': lambda: b_levels.append(b'\x00'), - 'warn': lambda: b_levels.append(b'\x01'), - 'error': lambda: b_levels.append(b'\x02'), - } - - for level in levels: - map_levels[level]() - return b_levels - - @staticmethod - def render(status: DiagnosticStatus, time_sec, verbose): - _, level_name = convert_level_to_str(status.level) - item = f'{status.name}: {level_name}, {status.message}' - print(item) - if verbose: - kv: KeyValue - for kv in status.values: - print(f'- {kv.key}={kv.value}') - def register_diagnostics_topic(self, handler): """Create ros node and subscribe to /diagnostic topic.""" @@ -138,7 +121,7 @@ def add_common_arguments(parser: ArgumentParser): help='run only once' ) parser.add_argument( - '-f' + '-f', '--filter', type=str, help='filter by diagnostic status name' diff --git a/ros2diagnostics/ros2diagnostics/command/diagnostics.py b/ros2diagnostics/ros2diagnostics/command/diagnostics.py index 85127c193..3726268aa 100644 --- a/ros2diagnostics/ros2diagnostics/command/diagnostics.py +++ b/ros2diagnostics/ros2diagnostics/command/diagnostics.py @@ -31,11 +31,8 @@ from ros2cli.command import CommandExtension -class DiagCommand(CommandExtension): - - def __init__(self): - super(DiagCommand, self).__init__() - self._subparser = None +class DiagnosticsCommand(CommandExtension): + """Various diagnostics related sub-commands.""" def add_arguments(self, parser, cli_name): self._subparser = parser @@ -46,7 +43,7 @@ def add_arguments(self, parser, cli_name): 'ros2diagnostics.verb', required=False) - def main(self, *, _, args): + def main(self, *, parser, args): if not hasattr(args, '_verb'): self._subparser.print_help() return 0 diff --git a/ros2diagnostics/ros2diagnostics/verb/csv.py b/ros2diagnostics/ros2diagnostics/verb/csv.py index 6131d4708..636934910 100644 --- a/ros2diagnostics/ros2diagnostics/verb/csv.py +++ b/ros2diagnostics/ros2diagnostics/verb/csv.py @@ -37,7 +37,6 @@ convert_level_to_str, DiagnosticsParser, open_file_for_output, - ParserModeEnum ) @@ -98,12 +97,12 @@ def main(self, *, args): try: handler = DiagnosticsParser( - mode=ParserModeEnum.CSV, verbose=args.verbose, run_once=args.once, - name_filter=args.filter, levels=args.levels, ) + self.__name_filter = args.filter + handler.set_render(self.render) handler.run() finally: diff --git a/ros2diagnostics/ros2diagnostics/verb/list.py b/ros2diagnostics/ros2diagnostics/verb/list.py index 1a557fb3f..0c0c619e1 100644 --- a/ros2diagnostics/ros2diagnostics/verb/list.py +++ b/ros2diagnostics/ros2diagnostics/verb/list.py @@ -26,7 +26,7 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from typing import Dict, List, TextIO, Tuple +from typing import Dict, List import yaml from ros2cli.verb import VerbExtension diff --git a/ros2diagnostics/ros2diagnostics/verb/show.py b/ros2diagnostics/ros2diagnostics/verb/show.py index 5cbea0692..024e99bbb 100644 --- a/ros2diagnostics/ros2diagnostics/verb/show.py +++ b/ros2diagnostics/ros2diagnostics/verb/show.py @@ -26,17 +26,18 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +import re from ros2cli.verb import VerbExtension from ros2diagnostics.api import ( add_common_arguments, + convert_level_to_str, DiagnosticsParser, ) from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue - class ShowVerb(VerbExtension): """Show diagnostics status item info.""" @@ -50,13 +51,14 @@ def add_arguments(self, parser, cli_name): ) def main(self, *, args): - diagnostic_parser = DiagnosticsParser( + self.diagnostic_parser = DiagnosticsParser( verbose=args.verbose, levels=args.levels, run_once=args.once, - name_filter=args.filter, ) - diagnostic_parser.run() + self.__name_filter = args.filter + self.verbose = args.verbose + self.diagnostic_parser.register_diagnostics_topic(self.diagnostics_status_handler) def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: """ @@ -75,11 +77,20 @@ def diagnostics_status_handler(self, msg: DiagnosticArray) -> None: result = re.search(self.__name_filter, status.name) if not result: continue - if self.__filter_level(status.level): + if self.diagnostic_parser.filter_level(status.level): continue - self.__status_render_handler( - status, msg.header.stamp.sec, self.__verbose) + self.render(status, msg.header.stamp.sec, self.verbose) counter += 1 if not counter: print(f'No diagnostic for levels: {self.__levels_info}') + + @staticmethod + def render(status: DiagnosticStatus, time_sec, verbose): + _, level_name = convert_level_to_str(status.level) + item = f'{status.name}: {level_name}, {status.message}' + print(item) + if verbose: + kv: KeyValue + for kv in status.values: + print(f'- {kv.key}={kv.value}') diff --git a/ros2diagnostics/setup.py b/ros2diagnostics/setup.py index 29bf31f94..e048a0e58 100644 --- a/ros2diagnostics/setup.py +++ b/ros2diagnostics/setup.py @@ -21,10 +21,9 @@ tests_require=['pytest'], entry_points={ 'ros2cli.command': [ - 'diagnostics = ros2diagnostics.command.diagnostics:DiagCommand', + 'diagnostics = ros2diagnostics.command.diagnostics:DiagnosticsCommand', ], 'ros2diagnostics.verb': [ - 'world = ros2diagnostics.verb.world:WorldVerb', 'csv = ros2diagnostics.verb.csv:CSVVerb', 'list = ros2diagnostics.verb.list:ListVerb', 'show = ros2diagnostics.verb.show:ShowVerb' From 3935bf68ec8717afa00d4a6f5c136068051e7c00 Mon Sep 17 00:00:00 2001 From: Russ Webber Date: Sun, 8 Oct 2023 23:51:26 +1100 Subject: [PATCH 26/26] fix: rename show to echo --- ros2diagnostics/README.md | 32 +++++-------------- ros2diagnostics/package.xml | 2 +- .../ros2diagnostics/api/__init__.py | 2 +- .../ros2diagnostics/verb/{show.py => echo.py} | 4 +-- ros2diagnostics/setup.py | 4 +-- 5 files changed, 14 insertions(+), 30 deletions(-) rename ros2diagnostics/ros2diagnostics/verb/{show.py => echo.py} (97%) diff --git a/ros2diagnostics/README.md b/ros2diagnostics/README.md index db9355a53..bcca26ba8 100644 --- a/ros2diagnostics/README.md +++ b/ros2diagnostics/README.md @@ -1,12 +1,12 @@ -# ROS2 diagnostic cli +# ROS2 diagnostics CLI ROS2 cli to analysis and monitor `/diagnostics` topic It's alternative to `diagnostic_analysis` project that not ported yet to ROS2. -The project add `diagnostics` command to ROS2 cli with there verbs. +The project add `diagnostics` command to ROS2 cli with three verbs: - list -- show +- echo - csv ### list @@ -21,29 +21,13 @@ diagnostic_simple: - DemoTask2 ``` -### show +### echo Monitor `/diagnostics` topic and print the diagnostics_status data can filter by level and node/status name -```bash -ros2 diagnostics show -h -usage: ros2 diagnostics show [-h] [-1] [-f FILTER] [--verbose] [-l {info,warn,error}] - -Show diagnostics status item info - -options: - -h, --help show this help message and exit - -1, --once run only once - -f FILTER, --filter FILTER - filter diagnostic status name - --verbose, -v Display more info. - -l {info,warn,error}, --levels {info,warn,error} - levels to filter, can be multiple times -``` - #### demo ```bash title="show all diagnostics status" -ros2 diagnostics show +ros2 diagnostics echo # --- time: 1682528494 --- diagnostic_simple: DemoTask: WARN, running @@ -54,7 +38,7 @@ diagnostic_simple: DemoTask2: ERROR, bad ``` ```bash title="filter by level" -ros2 diagnostics show -l error +ros2 diagnostics echo -l error --- time: 1682528568 --- diagnostic_simple: DemoTask2: ERROR, bad --- time: 1682528569 --- @@ -63,7 +47,7 @@ diagnostic_simple: DemoTask2: ERROR, bad ``` ```bash title="filter by name" -ros2 diagnostics show -f Task2 +ros2 diagnostics echo -f Task2 # --- time: 1682528688 --- diagnostic_simple: DemoTask2: ERROR, bad @@ -72,7 +56,7 @@ diagnostic_simple: DemoTask2: ERROR, bad ``` ```bash title="verbose usage" -ros2 diagnostics show -l warn -v +ros2 diagnostics echo -l warn -v # --- time: 1682528760 --- diagnostic_simple: DemoTask: WARN, running diff --git a/ros2diagnostics/package.xml b/ros2diagnostics/package.xml index b2559bac3..1c0a04c64 100644 --- a/ros2diagnostics/package.xml +++ b/ros2diagnostics/package.xml @@ -3,7 +3,7 @@ ros2diagnostics 3.1.2 - diagnostic command for ROS2 command line, parse and show diagnostics topic + diagnostic command for ROS2 command line, parse and echo diagnostics topic Christian Henkel diff --git a/ros2diagnostics/ros2diagnostics/api/__init__.py b/ros2diagnostics/ros2diagnostics/api/__init__.py index 44a6ca857..33d62fc65 100644 --- a/ros2diagnostics/ros2diagnostics/api/__init__.py +++ b/ros2diagnostics/ros2diagnostics/api/__init__.py @@ -113,7 +113,7 @@ def register_diagnostics_topic(self, handler): def add_common_arguments(parser: ArgumentParser): - """Add common arguments for csv and show verbs.""" + """Add common arguments for csv and echo verbs.""" parser.add_argument( '-1', '--once', diff --git a/ros2diagnostics/ros2diagnostics/verb/show.py b/ros2diagnostics/ros2diagnostics/verb/echo.py similarity index 97% rename from ros2diagnostics/ros2diagnostics/verb/show.py rename to ros2diagnostics/ros2diagnostics/verb/echo.py index 024e99bbb..086dfd951 100644 --- a/ros2diagnostics/ros2diagnostics/verb/show.py +++ b/ros2diagnostics/ros2diagnostics/verb/echo.py @@ -38,8 +38,8 @@ from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue -class ShowVerb(VerbExtension): - """Show diagnostics status item info.""" +class EchoVerb(VerbExtension): + """Echo diagnostics status item info.""" def add_arguments(self, parser, cli_name): add_common_arguments(parser) diff --git a/ros2diagnostics/setup.py b/ros2diagnostics/setup.py index e048a0e58..2baa17041 100644 --- a/ros2diagnostics/setup.py +++ b/ros2diagnostics/setup.py @@ -16,7 +16,7 @@ zip_safe=True, maintainer='user', maintainer_email='robo2020@gmail.com', - description='diagnostic command for ROS2 command line, parse and show /diagnostics topic', + description='diagnostic command for ROS2 command line, parse and echo /diagnostics topic', license='BSD-3-Clause', tests_require=['pytest'], entry_points={ @@ -26,7 +26,7 @@ 'ros2diagnostics.verb': [ 'csv = ros2diagnostics.verb.csv:CSVVerb', 'list = ros2diagnostics.verb.list:ListVerb', - 'show = ros2diagnostics.verb.show:ShowVerb' + 'echo = ros2diagnostics.verb.echo:EchoVerb' ] }, )