Exercises#

This page contains four take-home exercises that reinforce the concepts from Lecture 8. Each exercise asks you to write code from scratch based on a specification – no starter code is provided.

All files should be created inside your ~/enpm605_ws/src/first_pkg/ workspace.

Exercise 1 – Periodic Logger

Goal

Practice creating an OOP-based ROS 2 node with a timer callback, proper spin/shutdown lifecycle, and the ROS 2 logger.


Specification

Create the file first_pkg/periodic_logger.py that implements the following.

  1. ``PeriodicLogger(Node)`` class:

    • __init__(self, node_name: str, period: float = 1.0) that calls super().__init__(node_name), stores _period and _count = 0, and creates a timer with the given period.

    • _timer_callback(self) -> None: logs "[<count>] Tick at <timestamp>" using the ROS 2 logger and increments _count. Retrieve the current ROS 2 time with self.get_clock().now().to_msg().

  2. ``scripts/run_periodic_logger.py`` entry point:

    • Initialize rclpy, instantiate PeriodicLogger with node name "periodic_logger" and period 0.5.

    • Wrap the spin in a try/except KeyboardInterrupt block.

    • Destroy the node and call rclpy.shutdown() in the finally block.

  3. Register the entry point in setup.py:

    'periodic_logger = scripts.run_periodic_logger:main',
    

Expected behavior

Running ros2 run first_pkg periodic_logger should produce approximately two log lines per second:

[INFO] [<timestamp>] [periodic_logger]: [0] Tick at sec: 1741200001 nanosec: 123456789
[INFO] [<timestamp>] [periodic_logger]: [1] Tick at sec: 1741200001 nanosec: 623456789
[INFO] [<timestamp>] [periodic_logger]: [2] Tick at sec: 1741200002 nanosec: 123456789

Verification commands

ros2 node list           # should show /periodic_logger
ros2 node info /periodic_logger
ros2 topic hz /rosout    # should show ~2 Hz
Exercise 2 – String Publisher

Goal

Practice creating a publisher that sends std_msgs/String messages, registering it as a node executable, and inspecting it with ROS 2 introspection tools.


Specification

Create the file first_pkg/string_publisher.py that implements the following.

  1. ``StringPublisher(Node)`` class:

    • __init__(self, node_name: str) that calls super().__init__(node_name), creates a publisher on the topic "greeting" with message type std_msgs/msg/String and queue depth 10, initializes _count = 0, creates a String() message object, and creates a timer with period 1.0 s.

    • _timer_callback(self) -> None: sets self._message.data to f"Hello from ROS 2 -- message {self._count}", publishes the message, logs it with the ROS 2 logger, and increments _count.

  2. Entry point scripts/run_string_publisher.py:

    • Standard lifecycle: rclpy.init(), instantiate StringPublisher("string_publisher"), rclpy.spin(), destroy_node(), rclpy.shutdown().

  3. Register in setup.py:

    'string_publisher = scripts.run_string_publisher:main',
    

Verification

After building and running, in a second terminal:

ros2 topic list -t           # /greeting [std_msgs/msg/String]
ros2 topic echo /greeting    # prints each message
ros2 topic hz /greeting      # should show ~1.0 Hz
rqt_graph                    # node -> topic visible
Exercise 3 – String Subscriber

Goal

Practice creating a subscriber with a named callback, running both publisher and subscriber simultaneously, and verifying end-to-end communication.


Specification

Create the file first_pkg/string_subscriber.py that implements the following.

  1. ``StringSubscriber(Node)`` class:

    • __init__(self, node_name: str) that calls super().__init__(node_name), creates a subscription on topic "greeting" with message type std_msgs/msg/String, queue depth 10, and a named callback.

    • _subscriber_callback(self, msg: String) -> None: logs f"Received: {msg.data}" using the ROS 2 logger.

  2. Entry point scripts/run_string_subscriber.py:

    • Standard lifecycle matching Exercise 2.

  3. Register in setup.py:

    'string_subscriber = scripts.run_string_subscriber:main',
    

Expected behavior

Run the publisher from Exercise 2 in Terminal 1 and this subscriber in Terminal 2:

[INFO] [<timestamp>] [string_subscriber]: Received: Hello from ROS 2 -- message 0
[INFO] [<timestamp>] [string_subscriber]: Received: Hello from ROS 2 -- message 1
[INFO] [<timestamp>] [string_subscriber]: Received: Hello from ROS 2 -- message 2

Verification

ros2 topic info /greeting -v    # shows publisher and subscriber counts
rqt_graph                       # both nodes connected via /greeting
Exercise 4 – QoS Mismatch Investigation

Goal

Observe the effects of QoS incompatibility first-hand and use introspection tools to diagnose the problem.


Specification

This exercise has two parts.

Part A – Create a BEST_EFFORT publisher

Modify a copy of string_publisher.py saved as first_pkg/qos_publisher.py. Change the publisher to use an explicit QoSProfile with ReliabilityPolicy.BEST_EFFORT, DurabilityPolicy.VOLATILE, HistoryPolicy.KEEP_LAST, and depth=10. Keep the topic name "qos_test" and message type std_msgs/msg/String. Register as 'qos_publisher = ...' in setup.py.

Part B – Create a RELIABLE subscriber

Create first_pkg/qos_subscriber.py with a QoSProfile using ReliabilityPolicy.RELIABLE and all other policies at their defaults. Subscribe to "qos_test". Register as 'qos_subscriber = ...' in setup.py.

Investigation steps

  1. Run both nodes simultaneously.

  2. Observe that the subscriber callback is never invoked.

  3. Run ros2 topic info /qos_test -v and note the QoS printed for publisher and subscriber.

  4. Run ros2 doctor --report | grep qos and record the output.

  5. Fix the mismatch by changing the subscriber to ReliabilityPolicy.BEST_EFFORT and confirm that messages are now received.

Written reflection (include as a comment block at the top of qos_subscriber.py)

Answer these questions in 3-5 sentences:

  • What did ros2 topic info -v show before and after the fix?

  • Why does DDS produce no error when QoS is incompatible?

  • In what real-world scenario would a QoS mismatch cause a hard-to-debug failure?