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.
``PeriodicLogger(Node)`` class:
__init__(self, node_name: str, period: float = 1.0)that callssuper().__init__(node_name), stores_periodand_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 withself.get_clock().now().to_msg().
``scripts/run_periodic_logger.py`` entry point:
Initialize
rclpy, instantiatePeriodicLoggerwith node name"periodic_logger"and period0.5.Wrap the spin in a
try/except KeyboardInterruptblock.Destroy the node and call
rclpy.shutdown()in thefinallyblock.
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.
``StringPublisher(Node)`` class:
__init__(self, node_name: str)that callssuper().__init__(node_name), creates a publisher on the topic"greeting"with message typestd_msgs/msg/Stringand queue depth10, initializes_count = 0, creates aString()message object, and creates a timer with period1.0s._timer_callback(self) -> None: setsself._message.datatof"Hello from ROS 2 -- message {self._count}", publishes the message, logs it with the ROS 2 logger, and increments_count.
Entry point
scripts/run_string_publisher.py:Standard lifecycle:
rclpy.init(), instantiateStringPublisher("string_publisher"),rclpy.spin(),destroy_node(),rclpy.shutdown().
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.
``StringSubscriber(Node)`` class:
__init__(self, node_name: str)that callssuper().__init__(node_name), creates a subscription on topic"greeting"with message typestd_msgs/msg/String, queue depth10, and a named callback._subscriber_callback(self, msg: String) -> None: logsf"Received: {msg.data}"using the ROS 2 logger.
Entry point
scripts/run_string_subscriber.py:Standard lifecycle matching Exercise 2.
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
Run both nodes simultaneously.
Observe that the subscriber callback is never invoked.
Run
ros2 topic info /qos_test -vand note the QoS printed for publisher and subscriber.Run
ros2 doctor --report | grep qosand record the output.Fix the mismatch by changing the subscriber to
ReliabilityPolicy.BEST_EFFORTand 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 -vshow 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?