ABOUT ME

Today
Yesterday
Total
  • ROS2(Cyclone DDS)๋ฅผ ๋„คํŠธ์›Œํฌ๋กœ ์—ฐ๊ฒฐํ•˜์—ฌ ์‹คํ—˜ํ•˜๊ธฐ
    ROS2&MQTT 2025. 4. 4. 17:28

    โœ… ๋ชฉํ‘œ: ROS2(Cyclone DDS)๋ฅผ ๋„คํŠธ์›Œํฌ๋กœ ์—ฐ๊ฒฐํ•˜์—ฌ ์‹คํ—˜ํ•˜๊ธฐ

    [ํผ๋ธ”๋ฆฌ์…” ๋…ธํŠธ๋ถ1] โ”€โ”€โ”€โ”€โ”€โ†’ [Cyclone DDS (P2P)] โ”€โ”€โ”€โ”€โ”€โ†’ [์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ๋…ธํŠธ๋ถ2]

    โ—ROS2๋Š” MQTT์™€ ๋‹ค๋ฅด๊ฒŒ ๋ธŒ๋กœ์ปค๊ฐ€ ์—†๊ณ , Peer-to-Peer(DDS) ๊ตฌ์กฐ์ด๋‹ค.
    ๊ทธ๋ž˜์„œ ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ ์กฐ๊ฑด์ด ์กฐ๊ธˆ ๋” ๊นŒ๋‹ค๋กญ์ง€๋งŒ, ์ž˜๋งŒ ์„ค์ •ํ•˜๋ฉด MQTT๋ณด๋‹ค ๋‚ฎ์€ ๋ ˆ์ดํ„ด์‹œ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค


    Ubuntu์—์„œ ROS2 Humble ์„ค์น˜ (๊ณต์‹ ๋ฐฉ์‹, Ubuntu 22.04 ๊ธฐ์ค€)

     

    ๐Ÿ“ฆ 1๋‹จ๊ณ„: ์„ค์น˜ ์ „ ๊ธฐ๋ณธ ์„ค์ •

    sudo apt update && sudo apt install locales
    sudo locale-gen en_US en_US.UTF-8
    sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
    export LANG=en_US.UTF-8

     

    ๐Ÿ”‘ 2๋‹จ๊ณ„: ROS2 ํŒจํ‚ค์ง€ ์ €์žฅ์†Œ ์„ค์ •

    sudo apt install software-properties-common
    sudo add-apt-repository universe
    sudo apt update && sudo apt install curl -y
    sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo tee /usr/share/keyrings/ros-archive-keyring.gpg > /dev/null
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \
    https://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | \
    sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

     

    ๐Ÿ“ฅ 3๋‹จ๊ณ„: ROS2 Humble ์„ค์น˜

    sudo apt update
    sudo apt install ros-humble-desktop -y

    ๐Ÿ“Œ (๋””์Šคํฌ ์—ฌ์œ  ์—†์„ ๊ฒฝ์šฐ ros-humble-ros-base๋กœ ์ตœ์†Œ ์„ค์น˜๋„ ๊ฐ€๋Šฅ)

     

    4๋‹จ๊ณ„: ROS2 ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •

    echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
    source ~/.bashrc

     

    ์„ค์น˜ ํ™•์ธ

    ros2 --version
    # ์˜ˆ: ros2 0.9.8 (humble)
    ros2 run demo_nodes_cpp talker
    # โ†’ talker ๋…ธ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋ฉด ์„ฑ๊ณต!

     

    colcon ๋ฐ ์˜์กด ํŒจํ‚ค์ง€ ์„ค์น˜ (๋นŒ๋“œ์šฉ)

    sudo apt install python3-colcon-common-extensions -y

    โœ… 1. Cyclone DDS ๋„คํŠธ์›Œํฌ ํ†ต์‹  ์กฐ๊ฑด

    ๊ธฐ๋ณธ ์ „์ œ:

    • ROS2 ๋…ธ๋“œ๋Š” Cyclone DDS๋ฅผ ํ†ตํ•ด ๋ฉ€ํ‹ฐ์บ์ŠคํŠธ UDP๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์„œ๋กœ ํƒ์ƒ‰
    • ๋‹จ, ๋„คํŠธ์›Œํฌ ์„ค์ •์ด ๋‹ค๋ฅด๋ฉด ์„œ๋กœ ๋ชป ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค

    ๐Ÿ“Œ ๊ผญ ์ง€์ผœ์•ผ ํ•  ์กฐ๊ฑด:

    ์กฐ๊ฑด ์„ค๋ช…
    โœ… ๊ฐ™์€ ์„œ๋ธŒ๋„ท (์˜ˆ: 192.168.0.x) Multicast ํƒ์ƒ‰ ๊ฐ€๋Šฅํ•ด์•ผ ํ•จ
    โœ… ๋ฐฉํ™”๋ฒฝ ํ—ˆ์šฉ UDP 7400 ~ 7500 ํฌํŠธ ๋ฒ”์œ„ ์—ด๋ ค์•ผ ํ•จ
    โœ… ๋ธŒ๋ฆฌ์ง€ ๋„คํŠธ์›Œํฌ or ์œ ์„  ์—ฐ๊ฒฐ Parallels, VirtualBox ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ โ€œ๋ธŒ๋ฆฌ์ง€ ๋ชจ๋“œโ€ ํ•„์ˆ˜
    โœ… ๋™์ผ ROS2 ๋ฒ„์ „ & RMW ๋‘˜ ๋‹ค rmw_cyclonedds_cpp ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •

    โœ… 2. Cyclone DDS ํ†ต์‹  ํ™•์ธ ์‹คํ—˜

    A. ํผ๋ธ”๋ฆฌ์…” ๋…ธํŠธ๋ถ์—์„œ:

    ros2 run demo_nodes_cpp talker

    B. ์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ๋…ธํŠธ๋ถ์—์„œ:

    ros2 run demo_nodes_cpp listener

     

     ์ •์ƒ ์—ฐ๊ฒฐ๋˜๋ฉด listener ์ชฝ์—์„œ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ๋จ

    [publisher]

    [subscriber]


    โœ… 3. Cyclone DDS์—์„œ ํ†ต์‹ ์ด ์•ˆ ๋  ๊ฒฝ์šฐ ๋Œ€์ฒ˜๋ฒ•

    ๐Ÿ“ ~/.ros/ros2/์— cyclonedds.xml ์„ค์ • ํŒŒ์ผ ๋งŒ๋“ค๊ธฐ

     
    <CycloneDDS>
      <Domain>
        <General>
          <NetworkInterfaceAddress>192.168.0.101</NetworkInterfaceAddress> <!-- ๋ณธ์ธ์˜ IP -->
        </General>
        <Discovery>
          <Peers>
            <Peer address="192.168.0.102"/> <!-- ์ƒ๋Œ€๋ฐฉ IP -->
          </Peers>
        </Discovery>
      </Domain>
    </CycloneDDS>

    ๊ทธ๋ฆฌ๊ณ  ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •:

    export CYCLONEDDS_URI=file:///home/devjang/.ros/ros2/cyclonedds.xml

    ๋˜๋Š” .bashrc์— ์ถ”๊ฐ€!


    โœ… 4. ROS2 ํผํฌ๋จผ์Šค ์‹คํ—˜์„ ์œ„ํ•œ ์ฝ”๋“œ ๊ตฌ์„ฑ

     

    ์—ญํ•  ์˜ˆ์‹œ ํŒŒ์ผ์„ค๋ช…
    ํผ๋ธ”๋ฆฌ์…” dds_pub.py rclcpp::Publisher, dummy JSON ์ „์†ก
    ์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ dds_sub.py ์ˆ˜์‹  ํ›„ latency ๊ณ„์‚ฐ, ์†์‹ค๋ฅ  ์ธก์ • ๊ฐ€๋Šฅ
    ๋นŒ๋“œ ์‹œ์Šคํ…œ colcon build + setup.py  
    ์‹œ๊ฐํ™” visualize_result.ipynb๋กœ ๋™์ผํ•˜๊ฒŒ ๋ถ„์„ ๊ฐ€๋Šฅ  

    setup.py์™€ package.xml์€ ROS2์—์„œ ํŒจํ‚ค์ง€๋ฅผ ์ •์˜ํ•˜๊ณ  ๋นŒ๋“œ/๋ฐฐํฌํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํ•ต์‹ฌ ๊ตฌ์„ฑ ํŒŒ์ผ๋“ค์ด๋‹ค, ๋‘˜ ๋‹ค ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜์ง€๋งŒ ์šฉ๋„์™€ ํฌ๋งท์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ํ•จ๊ป˜ ์จ์•ผํ•œ๋‹ค.

    [setup.py ์ฝ”๋“œ]

    Python ๊ธฐ๋ฐ˜ ROS2 ํŒจํ‚ค์ง€์˜ ์„ค์น˜ ๋ฐ ์‹คํ–‰ ์Šคํฌ๋ฆฝํŠธ

    ๐Ÿ”น ์—ญํ• 

    • Python setuptools ๊ธฐ๋ฐ˜์œผ๋กœ ROS2 ๋…ธ๋“œ๋ฅผ ์ •์˜
    • ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์Šคํฌ๋ฆฝํŠธ(ros2 run ...)๋กœ ๋“ฑ๋ก
    • ์„ค์น˜ํ•  ํŒŒ์ผ, ์ด๋ฆ„, ๋ฒ„์ „, ์‹คํ–‰ entrypoint ๋“ฑ ์ง€์ •
    from setuptools import setup
    
    package_name = 'dds_test_py'
    
    setup(
        name=package_name,
        version='0.1.0',
        packages=[package_name],
        install_requires=['setuptools'],
        zip_safe=True,
        maintainer='Your Name',
        maintainer_email='you@example.com',
        description='Python ROS2 pub/sub test',
        license='Apache License 2.0',
        tests_require=['pytest'],
        entry_points={
            'console_scripts': [
                'dds_pub = dds_pub.dds_pub:main',
                'dds_sub = dds_sub.dds_sub:main',
            ],
        },
    )

    ๐Ÿ”บ ์ฃผ์˜: packages=[package_name] โ† ์ด๊ฑด __init__.py ํฌํ•จ๋œ ํด๋” ์ด๋ฆ„๊ณผ ๊ฐ™์•„์•ผ ํ•œ๋‹ค

     


    [package.xml ์ฝ”๋“œ]

    ROS2๊ฐ€ ํŒจํ‚ค์ง€๋ฅผ ์ธ์‹ํ•˜๊ณ , ์˜์กด์„ฑ/๋ฉ”ํƒ€์ •๋ณด๋ฅผ ์ •์˜ํ•˜๋Š” ํŒŒ์ผ

     

    ๐Ÿ”น ์—ญํ• 

    • ROS2์˜ ๋นŒ๋“œ ์‹œ์Šคํ…œ(colcon)์ด ์ด ํŒจํ‚ค์ง€๋ฅผ ์ธ์‹ํ•˜๊ฒŒ ํ•ด์คŒ
    • ์–ด๋–ค ํŒจํ‚ค์ง€์ธ์ง€ ์„ค๋ช… (์ด๋ฆ„, ๋ฒ„์ „, ์œ ์ง€๊ด€๋ฆฌ์ž, ๋ผ์ด์„ ์Šค ๋“ฑ)
    • ์–ด๋–ค ROS2 ํŒจํ‚ค์ง€๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์˜์กดํ•˜๋Š”์ง€ ์„ ์–ธ
    <package format="3">
      <name>dds_test_py</name>
      <version>0.1.0</version>
      <description>Python ROS2 pub/sub test</description>
      <maintainer email="you@example.com">Your Name</maintainer>
      <license>Apache 2.0</license>
      <!-- ํ•ต์‹ฌ: Python ํŒจํ‚ค์ง€๋กœ ์„ ์–ธ -->
      <build_type>ament_python</build_type>
      <!-- ์‹คํ–‰ ์‹œ ํ•„์š”ํ•œ ์˜์กด์„ฑ -->
      <exec_depend>rclpy</exec_depend>
      <exec_depend>std_msgs</exec_depend>
    </package>

    โœ… ๋‘˜์˜ ๊ด€๊ณ„ ์š”์•ฝ

    ํ•ญ๋ชฉ package.xml setup.py
    ์‚ฌ์šฉ ๋Œ€์ƒ ROS2 ์‹œ์Šคํ…œ Python/colcon ์„ค์น˜
    ๊ธฐ๋Šฅ ํŒจํ‚ค์ง€ ์ •๋ณด, ROS ์˜์กด์„ฑ Python ์„ค์น˜/์‹คํ–‰ ์„ค์ •
    ํ•„์ˆ˜ ์—ฌ๋ถ€ โœ… ROS2์—์„œ ๋ฐ˜๋“œ์‹œ ํ•„์š” โœ… Python ROS2 ๋…ธ๋“œ์— ํ•„์š”
    ๋น„์œ  "์ฑ… ํ‘œ์ง€์™€ ๋ชฉ์ฐจ" "์ฑ… ๋‚ด์šฉ๋ฌผ์˜ ์„ค์น˜ ์„ค๋ช…์„œ"

    ํ•จ๊ป˜ ์“ฐ์ผ ๋•Œ์˜ ํ๋ฆ„

    1. colcon build ๋ช…๋ น ์‹คํ–‰ ์‹œ
      • package.xml ์ฝ๊ณ  ์˜์กด์„ฑ ํ™•์ธ
      • Python ํŒจํ‚ค์ง€์ธ ๊ฒฝ์šฐ setup.py๋ฅผ ์‹คํ–‰ํ•ด ๋นŒ๋“œ/์„ค์น˜
    2. ๋นŒ๋“œ ํ›„:
      • ros2 run dds_test_py dds_pub โ†’ setup.py์—์„œ ๋“ฑ๋กํ•œ main() ํ•จ์ˆ˜ ์‹คํ–‰

    ์‹คํ–‰ ์˜ˆ์‹œ

    ๋นŒ๋“œ:

    colcon build --packages-select dds_test_py

     

    ์†Œ์Šค:

    source install/setup.bash

     

    ์‹คํ–‰:

    ros2 run dds_test_py dds_pub
    ros2 run dds_test_py dds_sub

    โœ… ๋ชฉํ‘œ

    ๋…ธ๋“œ ๊ธฐ๋Šฅ
    dds_pub.py id, timestamp, payload ํฌํ•จ ๋ฉ”์‹œ์ง€ ์ „์†ก + pub_log.csv ์ €์žฅ
    dds_sub.py ์ˆ˜์‹  ํ›„ latency ๊ณ„์‚ฐ + sub_log.csv, loss_result.csv ์ €์žฅ

    ๐Ÿ“ ํด๋” ๊ตฌ์กฐ (๊ธฐ๋ณธ)

    ros2/
    โ”œโ”€โ”€ dds_pub/
    โ”‚   โ”œโ”€โ”€ __init__.py
    โ”‚   โ””โ”€โ”€ dds_pub.py
    โ”œโ”€โ”€ dds_sub/
    โ”‚   โ”œโ”€โ”€ __init__.py
    โ”‚   โ””โ”€โ”€ dds_sub.py
    โ”œโ”€โ”€ log/
    โ”‚   โ”œโ”€โ”€ dds_pub_log.csv
    โ”‚   โ”œโ”€โ”€ dds_sub_log.csv
    โ”‚   โ””โ”€โ”€ loss_result.csv
    โ”œโ”€โ”€ setup.py
    โ”œโ”€โ”€ package.xml

    [dds_pub.py (ํผ๋ธ”๋ฆฌ์…”) ์ฝ”๋“œ]

    import rclpy
    from rclpy.node import Node
    from std_msgs.msg import String
    import json, time, csv, os
    
    class DDSPublisher(Node):
        def __init__(self):
            super().__init__('dds_publisher')
            self.publisher_ = self.create_publisher(String, 'dds_topic', 10)
            self.timer_period = 0.1  # 10Hz
            self.timer = self.create_timer(self.timer_period, self.timer_callback)
            self.msg_id = 0
            self.log_path = 'log/dds_pub_log.csv'
            os.makedirs('log', exist_ok=True)
            self.log_file = open(self.log_path, 'w', newline='')
            self.writer = csv.writer(self.log_file)
            self.writer.writerow(['id', 'timestamp', 'payload'])
            self.get_logger().info("DDS Publisher Started")
    
        def timer_callback(self):
            now = time.time()
            payload = {
                "id": self.msg_id,
                "timestamp": now,
                "data": {"sensor": "dds", "value": 123}
            }
            msg_str = json.dumps(payload)
            msg = String()
            msg.data = msg_str
            self.publisher_.publish(msg)
    
            self.writer.writerow([self.msg_id, now, msg_str])
            self.msg_id += 1
    
        def destroy_node(self):
            self.log_file.close()
            super().destroy_node()
    
    def main(args=None):
        rclpy.init(args=args)
        node = DDSPublisher()
        try:
            rclpy.spin(node)
        except KeyboardInterrupt:
            node.get_logger().info('Keyboard interrupt โ†’ stopping.')
        finally:
            node.destroy_node()
            rclpy.shutdown()

    [dds_sub.py (์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ + latency + ์†์‹ค๋ฅ )]

    import rclpy
    from rclpy.node import Node
    from std_msgs.msg import String
    import json, time, csv, os
    
    class DDSSubscriber(Node):
        def __init__(self):
            super().__init__('dds_subscriber')
            self.subscription = self.create_subscription(
                String,
                'dds_topic',
                self.listener_callback,
                10)
            self.subscription  # prevent unused variable warning
    
            self.received_ids = set()
            self.log_path = 'log/dds_sub_log.csv'
            self.loss_path = 'log/loss_result.csv'
            os.makedirs('log', exist_ok=True)
            self.log_file = open(self.log_path, 'w', newline='')
            self.writer = csv.writer(self.log_file)
            self.writer.writerow(['id', 'recv_time', 'latency_ms'])
            self.get_logger().info("DDS Subscriber Started")
    
        def listener_callback(self, msg):
            try:
                now = time.time()
                raw = json.loads(msg.data)
                msg_id = int(raw.get("id", -1))
                sent_time = float(raw.get("timestamp", 0))
                latency = (now - sent_time) * 1000
    
                self.received_ids.add(msg_id)
                self.writer.writerow([msg_id, now, round(latency, 3)])
            except Exception as e:
                self.get_logger().error(f"Error decoding message: {e}")
    
        def destroy_node(self):
            self.log_file.close()
            self.calculate_loss()
            super().destroy_node()
    
        def calculate_loss(self):
            try:
                with open('log/dds_pub_log.csv', newline='') as f:
                    reader = csv.DictReader(f)
                    pub_ids = set(int(row['id']) for row in reader)
            except FileNotFoundError:
                self.get_logger().warn("dds_pub_log.csv not found")
                return
    
            total_sent = len(pub_ids)
            total_recv = len(self.received_ids)
            loss = total_sent - total_recv
            loss_rate = (loss / total_sent) * 100 if total_sent > 0 else 0
    
            with open(self.loss_path, 'w', newline='') as f:
                writer = csv.writer(f)
                writer.writerow(["total_sent", "total_received", "loss_count", "loss_rate(%)"])
                writer.writerow([total_sent, total_recv, loss, round(loss_rate, 2)])
    
            self.get_logger().info(f"์†์‹ค๋ฅ : {loss_rate:.2f}% ({loss}/{total_sent})")
    
    def main(args=None):
        rclpy.init(args=args)
        node = DDSSubscriber()
        try:
            rclpy.spin(node)
        except KeyboardInterrupt:
            node.get_logger().info('Keyboard interrupt โ†’ stopping.')
        finally:
            node.destroy_node()
            rclpy.shutdown()

    ํ˜„์žฌ ๊ฒฝ๋กœ์—๋Š” ์•„์ง ์‹คํ—˜ ๊ฒฐ๊ณผ ๋กœ๊ทธ ํŒŒ์ผ๋“ค์ด ์กด์žฌํ•˜์ง€ ์•Š์•„์„œ ์‹œ๊ฐํ™”๋ฅผ ๋ฐ”๋กœ ์ง„ํ–‰ํ•  ์ˆ˜๋Š” ์—†์œผ๋ฉฐ ์‹คํ—˜์„ ๋๋‚ด๊ณ  ๋กœ๊ทธ ํŒŒ์ผ๋“ค๋งŒ ์ƒ์„ฑํ•˜๋ฉด ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ๋„๋ก, visualize_result.ipynb๋ฅผ ๊ตฌ์„ฑํ•˜์˜€๋‹ค.

    [visualize_result.ipynb ํ…œํ”Œ๋ฆฟ]

    import pandas as pd
    import matplotlib.pyplot as plt
    
    # ํŒŒ์ผ ๊ฒฝ๋กœ
    pub_log_path = "log/dds_pub_log.csv"
    sub_log_path = "log/dds_sub_log.csv"
    loss_result_path = "log/loss_result.csv"
    
    # ํผ๋ธ”๋ฆฌ์…” ๋กœ๊ทธ (์„ ํƒ)
    try:
        pub_df = pd.read_csv(pub_log_path)
        print("์ด ํผ๋ธ”๋ฆฌ์‹œ ์ˆ˜:", len(pub_df))
    except:
        print("โš ๏ธ ํผ๋ธ”๋ฆฌ์…” ๋กœ๊ทธ ์—†์Œ")
    
    # ์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ๋กœ๊ทธ (latency)
    sub_df = pd.read_csv(sub_log_path)
    
    plt.figure(figsize=(10, 4))
    plt.plot(sub_df["recv_time"], sub_df["latency_ms"], label="Latency (ms)")
    plt.xlabel("Receive Time (s)")
    plt.ylabel("Latency (ms)")
    plt.title("ROS2 Latency Over Time")
    plt.grid(True)
    plt.legend()
    plt.show()
    
    # ์†์‹ค๋ฅ  ์ถœ๋ ฅ
    loss_df = pd.read_csv(loss_result_path)
    print("\n๐Ÿ“Š Message Loss Summary:")
    display(loss_df)

    ์‹คํ—˜ ์ˆœ์„œ & ๋ช…๋ น์–ด

    ํ„ฐ๋ฏธ๋„์—์„œ ROS2 ํผ๋ธ”๋ฆฌ์…” ์‹คํ–‰

    ros2 run dds_test_py dds_pub

    ์‹คํ—˜ ์ข…๋ฃŒํ•˜๋ ค๋ฉด: Ctrl + C

     

    ๋‹ค๋ฅธ ํ„ฐ๋ฏธ๋„์—์„œ ROS2 ์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ์‹คํ–‰

    ros2 run dds_test_py dds_sub

     

    ์‹คํ—˜ ์ข…๋ฃŒํ•˜๋ ค๋ฉด: Ctrl + C

    ์ข…๋ฃŒ ์‹œ ์ž๋™์œผ๋กœ loss_result.csv ์ƒ์„ฑ๋จ

    ๋กœ๊ทธ ํŒŒ์ผ ํ™•์ธ

    ls log/
    # ์˜ˆ์ƒ: dds_pub_log.csv, dds_sub_log.csv, loss_result.csv

    ์‹œ๊ฐํ™”

    jupyter notebook

    โ†’ visualize_result.ipynb ์—ด๊ณ  latency plot + ์†์‹ค๋ฅ  ํ‘œ ํ™•์ธ

    'ROS2&MQTT' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

    MQTT - 1, ๊ธฐ๋ณธํ†ต์‹   (0) 2025.04.06
    What is ROS2?  (1) 2025.03.22

    ๋Œ“๊ธ€

Designed by Tistory.