<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>SLAM on M1KE BL0G</title><link>https://www.m1ke.org/tags/slam/</link><description>Recent content in SLAM on M1KE BL0G</description><generator>Hugo -- gohugo.io</generator><language>ja-jp</language><copyright>mike</copyright><lastBuildDate>Wed, 14 Oct 2026 11:00:00 +0900</lastBuildDate><atom:link href="https://www.m1ke.org/tags/slam/index.xml" rel="self" type="application/rss+xml"/><item><title>差動二輪ロボットの駆動系とAMCL・SLAM・Nav2</title><link>https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/</link><pubDate>Wed, 14 Oct 2026 11:00:00 +0900</pubDate><guid>https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/</guid><description>&lt;img src="https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/canon.png" alt="Featured image of post 差動二輪ロボットの駆動系とAMCL・SLAM・Nav2" /&gt;&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.m1ke.org/p/ros2%E3%81%AE%E6%A6%82%E5%BF%B5%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A1%E3%83%A2/" &gt;ROS2の概念を理解するためのメモ&lt;/a&gt;でTF2・SLAM・Nav2・Gazeboの概念をざっと整理したが、「ホイールのオドメトリとLiDARが、具体的にAMCL・SLAM・Nav2のどこに入り、どう使われるのか」という駆動系（ドライブトレイン）側の繋がりが曖昧だった&lt;/li&gt;
&lt;li&gt;そこで、Gazebo上での差動二輪駆動（Differential Drive）のシミュレーションを起点に、オドメトリ・LiDARがAMCL・SLAM・Nav2にどう流れていくのかを整理する&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="全体像-駆動系からナビゲーションまでのデータの流れ"&gt;全体像: 駆動系からナビゲーションまでのデータの流れ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;大まかな処理の流れは以下の通り&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;順序&lt;/th&gt;
&lt;th&gt;段階&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;駆動系シミュレーション&lt;/td&gt;
&lt;td&gt;Gazeboが、ホイールの関節の回転からロボットの移動をシミュレートし、オドメトリを計算する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;センサーシミュレーション&lt;/td&gt;
&lt;td&gt;Gazeboが、LiDARセンサーをシミュレートし、距離測定データを生成する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;自己位置推定&lt;/td&gt;
&lt;td&gt;AMCL（既知の地図がある場合）またはSLAM（地図がない場合）が、オドメトリとLiDARデータから、地図上での自己位置を推定する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;経路計画・制御&lt;/td&gt;
&lt;td&gt;Nav2が、推定された自己位置とLiDARベースのコストマップを使って、経路を計画し、速度指令（&lt;code&gt;cmd_vel&lt;/code&gt;）を出す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;駆動系への反映&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmd_vel&lt;/code&gt;がGazeboの駆動系シミュレーションに戻され、ロボットが実際に動く&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;つまり、駆動系（1・5）とセンサー（2）がGazebo側の入出力、自己位置推定（3）とナビゲーション（4）がROS 2側の処理という役割分担になっており、この2つがオドメトリ・LiDARデータ・&lt;code&gt;cmd_vel&lt;/code&gt;を通じて閉じたループを作っている&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="tfツリー全体像"&gt;TFツリー全体像&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;このループを理解する上で軸になるのが、TF2が管理する座標フレームのツリー&lt;/li&gt;
&lt;li&gt;代表的な構成は以下の通り&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;map -&amp;gt; odom -&amp;gt; base_footprint -&amp;gt; base_link -&amp;gt; base_laser（LiDARなど各センサーのフレーム）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;変換&lt;/th&gt;
&lt;th&gt;発行元&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;odom&lt;/code&gt; -&amp;gt; &lt;code&gt;base_footprint&lt;/code&gt;（実質的に&lt;code&gt;base_link&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;駆動系シミュレーション（Gazeboのプラグイン、実機ではモータードライバ側）&lt;/td&gt;
&lt;td&gt;ホイールの回転から計算した、ロボット自身の移動量（オドメトリ）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;map&lt;/code&gt; -&amp;gt; &lt;code&gt;odom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AMCLまたはSLAM&lt;/td&gt;
&lt;td&gt;オドメトリの誤差を、LiDARと地図の照合で補正した分のズレ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;base_footprint&lt;/code&gt; -&amp;gt; &lt;code&gt;base_link&lt;/code&gt;、&lt;code&gt;base_link&lt;/code&gt; -&amp;gt; &lt;code&gt;base_laser&lt;/code&gt;など&lt;/td&gt;
&lt;td&gt;&lt;code&gt;robot_state_publisher&lt;/code&gt;（URDFから自動計算）&lt;/td&gt;
&lt;td&gt;ロボット本体の構造に対する、各フレーム・センサーの固定された取り付け位置&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;重要なのは、AMCL・SLAMは&lt;code&gt;odom&lt;/code&gt;-&amp;gt;&lt;code&gt;base_link&lt;/code&gt;系の変換を書き換えるのではなく、それとは別に&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;の変換を発行するという点&lt;/li&gt;
&lt;li&gt;&lt;code&gt;odom&lt;/code&gt;-&amp;gt;&lt;code&gt;base_link&lt;/code&gt;系は駆動系が出し続けるオドメトリそのもの（ドリフトを含む、短期的には滑らかだが長期的にはズレていく値）であり、AMCL・SLAMはそのズレを、別のレイヤーで補正する役割分担になっている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tf2の仕組み-分散システム前提の座標変換ライブラリ"&gt;TF2の仕組み: 分散システム前提の座標変換ライブラリ&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;TF2は、複数のノードに分かれて動く分散システムであることを前提にした、座標変換のためのライブラリ&lt;/li&gt;
&lt;li&gt;各ノードが、自分の知っている2つのフレーム間の位置・姿勢関係を&lt;code&gt;transform_broadcaster&lt;/code&gt;で発行し、それらを集約することで、ロボット全体の全フレームが繋がった1つのツリーが構築される&lt;/li&gt;
&lt;li&gt;位置・姿勢を知りたい側は、&lt;code&gt;transform_listener&lt;/code&gt;でこのツリーをたどり、任意の2フレーム間の変換を取得する&lt;/li&gt;
&lt;li&gt;位置は$x, y, z$（メートル単位）、姿勢はクォータニオン（四元数）で表現される&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/ros-view-frames.jpg"
width="1896"
height="3154"
srcset="https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/ros-view-frames_hu_db8723f01de7cfd8.jpg 480w, https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/ros-view-frames_hu_5769e8c94a150ae9.jpg 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="60"
data-flex-basis="144px"
&gt;&lt;/p&gt;
&lt;h3 id="ros座標フレームの一覧"&gt;ROS座標フレームの一覧&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ここまで出てきた&lt;code&gt;map&lt;/code&gt;・&lt;code&gt;odom&lt;/code&gt;・&lt;code&gt;base_footprint&lt;/code&gt;・&lt;code&gt;base_link&lt;/code&gt;・&lt;code&gt;base_laser&lt;/code&gt;は、ROSのコミュニティで広く使われている座標フレームの命名規約（移動ロボット向けの標準規約REP-105ほか）に沿ったもので、それぞれの役割は以下の通り&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/ros-tf-tree.jpg"
width="634"
height="420"
srcset="https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/ros-tf-tree_hu_c7aed50964be87a7.jpg 480w, https://www.m1ke.org/p/%E5%B7%AE%E5%8B%95%E4%BA%8C%E8%BC%AA%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E9%A7%86%E5%8B%95%E7%B3%BB%E3%81%A8amclslamnav2/ros-tf-tree_hu_343a6d989ce1944a.jpg 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="150"
data-flex-basis="362px"
&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;フレーム&lt;/th&gt;
&lt;th&gt;種類&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;map&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ワールド座標&lt;/td&gt;
&lt;td&gt;ロボットが位置する世界座標系。REP-105によれば、Z軸が上向きで、地図に対する位置は長期的にほとんどドリフトしないが、再位置推定のタイミングで不連続にジャンプすることがある&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;odom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ワールド座標（ローカルに連続）&lt;/td&gt;
&lt;td&gt;オドメトリ（車輪エンコーダなどの距離計）を積分した座標系。REP-105によれば連続的でなめらかな一方、実際の世界座標（&lt;code&gt;map&lt;/code&gt;）とは長期的にズレ（ドリフト）ていく。このズレを補正するのが、前述の&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;変換&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;base_footprint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ローカル座標&lt;/td&gt;
&lt;td&gt;&lt;code&gt;base_link&lt;/code&gt;を地面に投影したフレーム。XY座標・向きは&lt;code&gt;base_link&lt;/code&gt;と連動するが、Z座標は常に地面の高さ（0）に固定される。2Dのナビゲーションアルゴリズムが、ロボットを平面上の点として扱えるようにするためのフレーム&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;base_link&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ローカル座標&lt;/td&gt;
&lt;td&gt;ロボット本体に固定された基準フレーム。車体の実際の高さ・姿勢を含む、ロボットそのものの位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;base_laser&lt;/code&gt;（本記事では&lt;code&gt;laser_link&lt;/code&gt;と表記）&lt;/td&gt;
&lt;td&gt;ローカル座標&lt;/td&gt;
&lt;td&gt;LiDARなどのレーザースキャナが取り付けられている位置を表すフレーム。具体的な名前はロボットごとに異なる&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt;と&lt;code&gt;odom&lt;/code&gt;はどちらも世界座標だが、&lt;code&gt;map&lt;/code&gt;は長期的に正確な代わりに不連続、&lt;code&gt;odom&lt;/code&gt;は連続的だが長期的にはズレていくという、正反対の性質を持つ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;base_footprint&lt;/code&gt;・&lt;code&gt;base_link&lt;/code&gt;・&lt;code&gt;base_laser&lt;/code&gt;はどれもロボット本体を基準にしたローカル座標で、&lt;code&gt;odom&lt;/code&gt;-&amp;gt;&lt;code&gt;base_footprint&lt;/code&gt;の変換さえ分かれば、そこから先は固定されたロボットの構造（URDF）だけで計算できる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="gazeboでの駆動系シミュレーション"&gt;Gazeboでの駆動系シミュレーション&lt;/h2&gt;
&lt;h3 id="差動二輪駆動の仕組み"&gt;差動二輪駆動の仕組み&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;差動二輪駆動（Differential Drive）は、左右2つの独立した駆動輪の回転速度の差で、前進・後退・旋回を実現する機構&lt;/li&gt;
&lt;li&gt;左右輪の角速度を$\omega_L$、$\omega_R$、ホイール半径を$r$、ホイール間距離（トレッド）を$L$とすると、ロボット本体の並進速度$v$と角速度$\omega$は以下の式で計算される&lt;/li&gt;
&lt;/ul&gt;
$$
v = \frac{r(\omega_R + \omega_L)}{2}, \quad \omega = \frac{r(\omega_R - \omega_L)}{L}
$$&lt;ul&gt;
&lt;li&gt;Nav2やteleopノードが&lt;code&gt;cmd_vel&lt;/code&gt;（並進速度$v$・角速度$\omega$）を指令すると、駆動系はこの式を逆に解いて、左右輪それぞれの目標角速度を求める&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="gz-simのdiffdriveシステムプラグイン"&gt;gz-simのDiffDriveシステムプラグイン&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;現行のGazebo（gz-sim、旧Ignition Gazebo）では、&lt;code&gt;DiffDrive&lt;/code&gt;というシステムプラグインが、この差動二輪駆動のシミュレーションとオドメトリ計算をまとめて担う&lt;/li&gt;
&lt;li&gt;実際にソースを確認したところ、役割は以下の通り&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;入力トピック&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/model/&amp;lt;モデル名&amp;gt;/cmd_vel&lt;/code&gt;（&lt;code&gt;geometry_msgs/Twist&lt;/code&gt;、トピック名は&lt;code&gt;&amp;lt;topic&amp;gt;&lt;/code&gt;タグで変更可能）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;出力トピック（オドメトリ）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/model/&amp;lt;モデル名&amp;gt;/odometry&lt;/code&gt;（&lt;code&gt;&amp;lt;odom_topic&amp;gt;&lt;/code&gt;タグで変更可能）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;出力トピック（TF）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/model/&amp;lt;モデル名&amp;gt;/tf&lt;/code&gt;（&lt;code&gt;&amp;lt;tf_topic&amp;gt;&lt;/code&gt;タグで変更可能）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主なパラメータ&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;left_joint&amp;gt;&lt;/code&gt;・&lt;code&gt;&amp;lt;right_joint&amp;gt;&lt;/code&gt;（ホイールの関節名）、&lt;code&gt;&amp;lt;wheel_separation&amp;gt;&lt;/code&gt;（ホイール間距離、デフォルト1.0m）、&lt;code&gt;&amp;lt;wheel_radius&amp;gt;&lt;/code&gt;（ホイール半径、デフォルト0.2m）、&lt;code&gt;&amp;lt;odom_publish_frequency&amp;gt;&lt;/code&gt;（オドメトリ配信周波数、デフォルト50Hz）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;発行するTF&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_id&lt;/code&gt;は&lt;code&gt;&amp;lt;モデル名&amp;gt;/odom&lt;/code&gt;、&lt;code&gt;child_frame_id&lt;/code&gt;は&lt;code&gt;&amp;lt;モデル名&amp;gt;/&amp;lt;ホイールが接続されたリンク名&amp;gt;&lt;/code&gt;がデフォルト&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;つまり、SDF（Gazeboのロボット記述形式）やURDFの中でこのプラグインに左右ホイールの関節名と、ホイール半径・間距離を指定するだけで、&lt;code&gt;cmd_vel&lt;/code&gt;を受け取って物理的にホイールを回転させ、その回転量から上の式を使ってオドメトリを逆算し、&lt;code&gt;odom&lt;/code&gt;-&amp;gt;&lt;code&gt;base_link&lt;/code&gt;相当のTFとトピックを配信してくれる&lt;/li&gt;
&lt;li&gt;ROSとの橋渡し（&lt;code&gt;ros_gz_bridge&lt;/code&gt;）を使うことで、これらのGazebo側トピックを、ROS 2標準の&lt;code&gt;/cmd_vel&lt;/code&gt;・&lt;code&gt;/odom&lt;/code&gt;トピックに変換して繋ぎ込む&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="オドメトリのドリフトという前提"&gt;オドメトリのドリフトという前提&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;このDiffDriveプラグインが計算するオドメトリは、ホイールの回転量だけから並進・回転を積分しているため、実機ではスリップ（ホイールの空転・横滑り）やエンコーダのノイズによって、時間が経つほど実際の位置とズレていく（ドリフトする）&lt;/li&gt;
&lt;li&gt;Gazeboでも、タイヤと地面の摩擦パラメータ次第でこのスリップが再現されるため、オドメトリ単体では長時間・長距離の自己位置推定には使えない、という実機と同じ制約がシミュレーション上でも成り立つ&lt;/li&gt;
&lt;li&gt;このドリフトを補正するのが、後述のAMCL・SLAMの役割になる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="gazeboでのlidarシミュレーション"&gt;GazeboでのLiDARシミュレーション&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GazeboのLiDARセンサープラグインは、仮想空間内でレイを飛ばして周囲のオブジェクトとの交差点を計算することで、実際のLiDARと同じ形式の距離測定データ（&lt;code&gt;sensor_msgs/LaserScan&lt;/code&gt;、各角度ごとの距離値の配列）を生成する&lt;/li&gt;
&lt;li&gt;このセンサーも、URDF上で&lt;code&gt;base_link&lt;/code&gt;からの相対位置・向きを指定したリンク（例えば&lt;code&gt;laser_link&lt;/code&gt;）に取り付ける形で定義し、&lt;code&gt;robot_state_publisher&lt;/code&gt;がそのリンクのTFフレームを自動的に配信する&lt;/li&gt;
&lt;li&gt;出力は、ROS 2標準では&lt;code&gt;/scan&lt;/code&gt;トピックとして配信され、このトピックを後述のAMCL・SLAM・Nav2のコストマップが共通して利用する&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="amcl-オドメトリとlidarで自己位置を補正する"&gt;AMCL: オドメトリとLiDARで自己位置を補正する&lt;/h2&gt;
&lt;h3 id="パーティクルフィルタの基本"&gt;パーティクルフィルタの基本&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;AMCL（Adaptive Monte Carlo Localization）は、既知の地図上でのロボットの自己位置を、パーティクルフィルタで推定する手法&lt;/li&gt;
&lt;li&gt;「ロボットがここにいるかもしれない」という仮説を、数百〜数千個の粒子（パーティクル、それぞれが位置・向きの候補）として保持し、センサー情報が入るたびに、尤度の高い粒子を生き残らせ、低い粒子を淘汰する、というリサンプリングを繰り返す&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="動作モデルオドメトリと観測モデルlidar"&gt;動作モデル（オドメトリ）と観測モデル（LiDAR）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;実際にリポジトリで確認したところ、AMCLは&lt;code&gt;odom&lt;/code&gt;（オドメトリ）・&lt;code&gt;scan&lt;/code&gt;（LiDARのスキャン）・&lt;code&gt;map&lt;/code&gt;（既知の地図）・&lt;code&gt;initialpose&lt;/code&gt;（初期位置の手動指定）をsubscribeし、&lt;code&gt;particlecloud&lt;/code&gt;（粒子群の可視化用）・&lt;code&gt;amcl_pose&lt;/code&gt;（推定された自己位置）をpublishする&lt;/li&gt;
&lt;li&gt;動作モデルは、直前のオドメトリの変化量を使って、各粒子を同じだけ移動させる。ただし、ドリフトを考慮して、移動量にはランダムなノイズを加える&lt;/li&gt;
&lt;li&gt;観測モデルは、各粒子の位置から見たときに、今回のLiDARスキャンがどれだけ「地図と一致していそうか」を計算し、一致度が高い粒子ほど尤度を高くする&lt;/li&gt;
&lt;li&gt;この2つを組み合わせることで、「オドメトリでおおよその動きを予測し、LiDARと地図の照合でその予測を補正する」というベイズフィルタ的な推定を、観測が入るたびに繰り返す&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="map---odom変換の補正という役割"&gt;map -&amp;gt; odom変換の補正という役割&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;AMCLは、&lt;code&gt;odom&lt;/code&gt;-&amp;gt;&lt;code&gt;base_link&lt;/code&gt;のTFを書き換えるのではなく、&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;のTFを配信することで、地図座標系での自己位置を表現する&lt;/li&gt;
&lt;li&gt;つまり、駆動系（DiffDriveプラグイン）が出し続ける生のオドメトリはそのまま残しつつ、AMCLがその上に「このオドメトリ座標系は、実際の地図上ではこれだけズレている」という補正値を重ねる、というレイヤー構造になっている&lt;/li&gt;
&lt;li&gt;このため、AMCLが何らかの理由で推定に失敗しても、&lt;code&gt;odom&lt;/code&gt;-&amp;gt;&lt;code&gt;base_link&lt;/code&gt;自体は駆動系から配信され続けるため、システム全体が完全に座標情報を失うことはない&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="slam-地図がない場合はslam_toolboxが代わりを担う"&gt;SLAM: 地図がない場合はslam_toolboxが代わりを担う&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AMCLは「既知の地図」が前提になっているため、未知の環境ではそもそも使えない&lt;/li&gt;
&lt;li&gt;そこで使われるのが、2D LiDARベースのSLAMとしてよく使われる&lt;code&gt;slam_toolbox&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;AMCLとの違いは以下の通り&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;AMCL&lt;/th&gt;
&lt;th&gt;slam_toolbox&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;前提&lt;/td&gt;
&lt;td&gt;既知の地図が必要&lt;/td&gt;
&lt;td&gt;地図は不要（走行しながら作る）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;役割&lt;/td&gt;
&lt;td&gt;自己位置推定のみ&lt;/td&gt;
&lt;td&gt;自己位置推定＋地図作成（SLAM）を同時に行う&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使う情報&lt;/td&gt;
&lt;td&gt;オドメトリ＋LiDARスキャン＋既存の地図&lt;/td&gt;
&lt;td&gt;オドメトリ＋LiDARスキャンのスキャンマッチングで地図を構築・更新&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内部構造&lt;/td&gt;
&lt;td&gt;パーティクルフィルタ&lt;/td&gt;
&lt;td&gt;ポーズグラフ（過去の位置同士の関係をグラフ化）とループクロージャ（同じ場所に戻ったことを検出して誤差を補正）の最適化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;発行するTF・トピック&lt;/td&gt;
&lt;td&gt;&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;、および&lt;code&gt;/map&lt;/code&gt;（占有格子地図）、&lt;code&gt;/slam_toolbox/pose&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;slam_toolboxも、AMCLと同様にオドメトリとLiDARスキャンを使ってスキャンマッチングを行い、その結果をもとに&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;のTFを配信する、という同じレイヤー構造を取る&lt;/li&gt;
&lt;li&gt;つまり、AMCLとSLAMは「地図を使うか作るか」の違いであり、駆動系（オドメトリ）・LiDARからの入力の受け方、&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;という出力の形は共通している&lt;/li&gt;
&lt;li&gt;作成した地図を保存しておけば、以降はAMCLで既知の地図として使い回せる、という使い分けになる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="slamとamclは同時に動かさない"&gt;SLAMとAMCLは同時に動かさない&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AMCLとslam_toolboxは、どちらも&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;のTFを配信する役割を持つため、両者を同時に動かすとこの変換が競合してしまう&lt;/li&gt;
&lt;li&gt;通常の運用では、地図作成中はslam_toolboxが地図と自己位置の両方を提供し、地図が完成した後はAMCLに切り替えて、既知の地図上での自己位置推定に専念するという使い分けが基本になる&lt;/li&gt;
&lt;li&gt;つまり「地図作成フェーズ（SLAM）」と「運用フェーズ（AMCL）」を明確に切り分け、同じタイミングでは片方しか動かさないのが前提&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="発展-意味情報を加えるowlrdfとsemantic-slam"&gt;発展: 意味情報を加えるOWL/RDFとSemantic SLAM&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ここまでのSLAM・AMCLは、あくまで「空間のどこに壁や障害物があるか」という幾何学的な情報を扱う技術で、「その場所が何をする場所なのか」という意味までは扱わない&lt;/li&gt;
&lt;li&gt;OWL（Web Ontology Language）・RDF（Resource Description Framework）は、場所やオブジェクトに「101号室は病室」「薬品庫はスタッフ専用エリア」といった意味・関係をオントロジー・知識グラフとして記述するための標準規格で、SLAM・AMCLとは独立した、意味情報を載せるためのレイヤーにあたる&lt;/li&gt;
&lt;li&gt;通常のSLAMが「そこに壁や物体がある」という幾何学的な地図しか作らないのに対し、Semantic SLAM（意味的SLAM）は「それはドアなのか机なのか部屋なのか」といった意味ラベルも地図に結びつける研究分野で、OWL/RDFはこの意味情報を整理する手段の1つとして使われることがあるが、必須というわけではない&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.m1ke.org/p/ros4hri%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF/" &gt;ROS4HRIの仕組み&lt;/a&gt;のLLM対応チュートリアルで触れた&lt;code&gt;knowledge_core&lt;/code&gt;も、OWL/RDFベースの記号的な知識ベースであり、位置情報そのものではなく、その位置・場所が持つ意味を扱う用途で使われている&lt;/li&gt;
&lt;li&gt;整理すると、SLAM（地図を作る）→AMCL（地図上で位置を知る）→OWL/RDF（その位置・場所に意味を与える）という積み重ねで理解すると分かりやすいが、OWL/RDFはSLAM・AMCLの必須要素ではなく、必要に応じて追加する別レイヤー&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="nav2との接続-コストマップと速度指令"&gt;Nav2との接続: コストマップと速度指令&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AMCL・SLAMが&lt;code&gt;amcl_pose&lt;/code&gt;（またはそれに相当するTF）として自己位置を確定させると、Nav2はそれをもとにグローバルプランナーで大まかな経路を計画し、コントローラー（ローカルプランナー）が直近の障害物を避けながら実際の速度指令を計算する&lt;/li&gt;
&lt;li&gt;この「直近の障害物」を表すのがコストマップで、&lt;code&gt;/scan&lt;/code&gt;のLiDARデータがリアルタイムに反映される。地図上の静的な障害物（グローバルコストマップ）と、今まさにLiDARが検出している動的な障害物（ローカルコストマップ）の両方に、同じ&lt;code&gt;/scan&lt;/code&gt;データが使われる&lt;/li&gt;
&lt;li&gt;最終的にコントローラーが計算した速度指令が&lt;code&gt;cmd_vel&lt;/code&gt;として配信され、それが駆動系シミュレーション（DiffDriveプラグイン）に渡って実際にホイールが回り、ロボットが移動する。この移動がまた新しいオドメトリ・LiDARスキャンを生み、ループが続いていく&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="まとめ"&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Gazebo上の駆動系シミュレーション（差動二輪駆動の場合はgz-simのDiffDriveプラグイン）は、&lt;code&gt;cmd_vel&lt;/code&gt;を受けてホイールを回転させ、その回転量からオドメトリを逆算し、&lt;code&gt;odom&lt;/code&gt;-&amp;gt;&lt;code&gt;base_link&lt;/code&gt;のTFとトピックを配信する&lt;/li&gt;
&lt;li&gt;このオドメトリは、ホイールの回転だけから積分した値のため、実機・シミュレーション問わずドリフトする。この誤差を補正するのが、既知の地図があるときのAMCL、地図がないときのslam_toolboxの役割&lt;/li&gt;
&lt;li&gt;AMCL・slam_toolboxはどちらも、オドメトリ（動作モデル／スキャンマッチングの初期値）とLiDARスキャン（観測モデル／地図とのマッチング）を使い、&lt;code&gt;odom&lt;/code&gt;-&amp;gt;&lt;code&gt;base_link&lt;/code&gt;自体は書き換えず、別レイヤーの&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;というTFを配信することで、地図座標系での自己位置を補正する&lt;/li&gt;
&lt;li&gt;両者とも&lt;code&gt;map&lt;/code&gt;-&amp;gt;&lt;code&gt;odom&lt;/code&gt;を配信するため、同時には動かさず、地図作成フェーズ（SLAM）と運用フェーズ（AMCL）を切り分けて使う&lt;/li&gt;
&lt;li&gt;Nav2は、この自己位置と、LiDARスキャンから作られるコストマップをもとに経路計画・障害物回避を行い、最終的な速度指令&lt;code&gt;cmd_vel&lt;/code&gt;を駆動系に戻すことで、駆動系→センサー→自己位置推定→ナビゲーション→駆動系という1つの閉じたループが成立する&lt;/li&gt;
&lt;li&gt;SLAM・AMCLが扱うのはあくまで幾何学的な位置情報で、「その場所が何をする場所か」という意味はOWL/RDFのような別レイヤー（Semantic SLAMという研究分野も含む）で扱う、必須ではない拡張領域になる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="参考文献"&gt;参考文献&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.m1ke.org/p/ros2%E3%81%AE%E6%A6%82%E5%BF%B5%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A1%E3%83%A2/" &gt;ROS2の概念を理解するためのメモ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/gazebosim/gz-sim/tree/main/src/systems/diff_drive" target="_blank" rel="noopener"
&gt;gazebosim/gz-sim - DiffDrive system plugin - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/ros-navigation/navigation2/tree/main/nav2_amcl" target="_blank" rel="noopener"
&gt;ros-navigation/navigation2 - nav2_amcl - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/SteveMacenski/slam_toolbox" target="_blank" rel="noopener"
&gt;SteveMacenski/slam_toolbox - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/ros-navigation/navigation2" target="_blank" rel="noopener"
&gt;ros-navigation/navigation2 - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://memo.soarcloud.com/ros%E3%81%AE%E5%BA%A7%E6%A8%99%E5%A4%89%E6%8F%9Btf%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/" target="_blank" rel="noopener"
&gt;ROSの座標変換TFについて&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>ROS2の概念を理解するためのメモ</title><link>https://www.m1ke.org/p/ros2%E3%81%AE%E6%A6%82%E5%BF%B5%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A1%E3%83%A2/</link><pubDate>Tue, 25 Aug 2026 12:00:00 +0900</pubDate><guid>https://www.m1ke.org/p/ros2%E3%81%AE%E6%A6%82%E5%BF%B5%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A1%E3%83%A2/</guid><description>&lt;img src="https://www.m1ke.org/p/ros2%E3%81%AE%E6%A6%82%E5%BF%B5%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A1%E3%83%A2/ros2.png" alt="Featured image of post ROS2の概念を理解するためのメモ" /&gt;&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;業務でROS2を使う必要が出てきた&lt;/li&gt;
&lt;li&gt;しかし、正直あまりROS2を触っていない&lt;/li&gt;
&lt;li&gt;そこで、自分向けにROS2の基本的な考え方と用語をAIで整理したログ&lt;/li&gt;
&lt;li&gt;さくっと全体概念を理解するのが目的&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ros2とは"&gt;ROS2とは&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Robot Operating System 2の略だが、OS（オペレーティングシステム）そのものではない&lt;/li&gt;
&lt;li&gt;ロボット向けのソフトウェアを作るためのミドルウェア兼フレームワーク&lt;/li&gt;
&lt;li&gt;「センサーで取得した値を処理して、モーターに指令を送る」といった、ロボットの各機能をバラバラのプログラム（後述のノード）として作り、それらを通信でつなげて1つのロボットとして動かす、という考え方が中心にある&lt;/li&gt;
&lt;li&gt;前身のROS1に対して、以下のような改善が入っている
&lt;ul&gt;
&lt;li&gt;通信の基盤にDDSという業界標準の仕組みを採用し、単一障害点（後述）をなくした&lt;/li&gt;
&lt;li&gt;リアルタイム性や、Windows・組み込み環境への対応が強化された&lt;/li&gt;
&lt;li&gt;セキュリティ機能が追加された&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ディストリビューションとインストール"&gt;ディストリビューションとインストール&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ROS2は、毎年1回コードネームつきのバージョン（ディストリビューション）がリリースされる&lt;/li&gt;
&lt;li&gt;全てのバージョンがLTS（長期サポート）というわけではなく、偶数年リリースのものがLTS（サポート期間5年）、奇数年リリースのものは短期サポート（サポート期間約1年半）になっている
主なLTS版は以下の通り。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;コードネーム&lt;/th&gt;
&lt;th&gt;リリース年&lt;/th&gt;
&lt;th&gt;対応Ubuntu&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Humble Hawksbill&lt;/td&gt;
&lt;td&gt;2022年&lt;/td&gt;
&lt;td&gt;Ubuntu 22.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jazzy Jalisco&lt;/td&gt;
&lt;td&gt;2024年&lt;/td&gt;
&lt;td&gt;Ubuntu 24.04&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;迷ったら、その時点の最新LTS版を、対応するUbuntuのバージョンに入れるのが基本&lt;/li&gt;
&lt;li&gt;ROS1と違い、ROS2はOSのバージョンとの結びつきが強いため、Ubuntuのバージョンを先に決めてから、対応するROS2のバージョンを選ぶことになる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="全体アーキテクチャ"&gt;全体アーキテクチャ&lt;/h2&gt;
&lt;h3 id="ddsdata-distribution-service"&gt;DDS（Data Distribution Service）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ROS2の通信を裏で支えているミドルウェアの規格&lt;/li&gt;
&lt;li&gt;「マスターレス」という特徴があり、ROS1にあった&lt;code&gt;roscore&lt;/code&gt;のような、通信を仲介する中央サーバーが存在しない&lt;/li&gt;
&lt;li&gt;各ノードが自律的にお互いを見つけて（Discoveryと呼ぶ）、直接データをやり取りする&lt;/li&gt;
&lt;li&gt;中央サーバーがないため、その1台が落ちるとシステム全体が止まる、という単一障害点の問題が起きにくい&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ノードnode"&gt;ノード（Node）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ROS2における処理の最小単位&lt;/li&gt;
&lt;li&gt;1つのノードが、1つのプログラム（プロセス）に対応する&lt;/li&gt;
&lt;li&gt;例えば「カメラ画像を取得するノード」「物体検出をするノード」「モーターを制御するノード」のように、機能ごとにノードを分けて作る&lt;/li&gt;
&lt;li&gt;ノード同士は、後述のTopic・Service・Actionという3つの方式で通信する&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ノード間の通信"&gt;ノード間の通信&lt;/h2&gt;
&lt;p&gt;ノード同士の通信には、目的に応じて3つの方式がある。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方式&lt;/th&gt;
&lt;th&gt;通信の形&lt;/th&gt;
&lt;th&gt;用途の例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Topic&lt;/td&gt;
&lt;td&gt;非同期のpub/sub&lt;/td&gt;
&lt;td&gt;センサーの値を流し続ける&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service&lt;/td&gt;
&lt;td&gt;同期的なリクエスト/レスポンス&lt;/td&gt;
&lt;td&gt;一度だけの問い合わせ・設定変更&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action&lt;/td&gt;
&lt;td&gt;非同期＋進捗報告＋キャンセル可能&lt;/td&gt;
&lt;td&gt;時間のかかる動作（アーム移動など）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="topicトピック"&gt;Topic（トピック）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;特定の名前（トピック名）に対して、データを送り続ける側（Publisher）と、受け取り続ける側（Subscriber）が非同期に繋がる仕組み&lt;/li&gt;
&lt;li&gt;PublisherとSubscriberはお互いの存在を意識しなくてよく、同じトピック名にさえ繋がれば、何対何でも通信できる&lt;/li&gt;
&lt;li&gt;センサーの値のように、継続的に流れてくるデータに向いている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="serviceサービス"&gt;Service（サービス）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;関数呼び出しのように、リクエストを送って、その場でレスポンスを待つ同期的な通信&lt;/li&gt;
&lt;li&gt;「今の設定値を教えて」「このパラメータを変更して」のような、1回のやり取りで完結する処理に向いている&lt;/li&gt;
&lt;li&gt;レスポンスが返るまで基本的に待つことになるので、時間のかかる処理には向かない&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="actionアクション"&gt;Action（アクション）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Serviceに、進捗報告（フィードバック）とキャンセル機能を足したような仕組み&lt;/li&gt;
&lt;li&gt;「アームを目標位置まで動かす」のように、時間がかかり、途中経過を知りたかったり、途中で中断したくなったりする処理に向いている&lt;/li&gt;
&lt;li&gt;内部的にはTopicとServiceの組み合わせで実現されている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="メッセージ型インターフェース"&gt;メッセージ型・インターフェース&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Topic・Service・Actionでやり取りするデータの型（インターフェース）は、それぞれ&lt;code&gt;.msg&lt;/code&gt;・&lt;code&gt;.srv&lt;/code&gt;・&lt;code&gt;.action&lt;/code&gt;という専用のファイル形式で定義される
標準で用意されている型は、目的別のパッケージにまとまっている。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;パッケージ&lt;/th&gt;
&lt;th&gt;主な型&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;std_msgs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Int32・String・Boolなど、基本的な型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;geometry_msgs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Twist（速度）・Pose（位置・姿勢）など、幾何情報の型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensor_msgs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Image・LaserScan・PointCloud2など、センサーデータの型&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;標準の型で足りない場合は、自分のパッケージ内に&lt;code&gt;.msg&lt;/code&gt;ファイルなどを作って独自の型を定義できる&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 interface show &amp;lt;型名&amp;gt;&lt;/code&gt;で、その型がどんなフィールドを持つか確認できる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cmd_vel速度指令"&gt;cmd_vel（速度指令）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ロボットを動かすための速度指令に、事実上の標準として使われているトピック名&lt;/li&gt;
&lt;li&gt;&lt;code&gt;geometry_msgs/Twist&lt;/code&gt;（または&lt;code&gt;TwistStamped&lt;/code&gt;）という、並進速度（linear）と回転速度（angular）を持つメッセージ型を使う&lt;/li&gt;
&lt;li&gt;Nav2の出力や、キーボード操作用のteleopノードなど、様々なノードがこの&lt;code&gt;/cmd_vel&lt;/code&gt;にpublishし、ロボット側のモーター制御ノードがそれをsubscribeして実際に動かす、という流れが一般的&lt;/li&gt;
&lt;li&gt;複数のノードが同時に&lt;code&gt;/cmd_vel&lt;/code&gt;を出そうとすると競合するため、優先順位をつけて1つの指令にまとめる&lt;code&gt;twist_mux&lt;/code&gt;のようなノードがよく使われる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="qosquality-of-service"&gt;QoS（Quality of Service）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;DDSが持つ、通信の品質を細かく設定できる仕組み&lt;/li&gt;
&lt;li&gt;ROS1にはなかった、ROS2特有の重要な概念
代表的な設定項目は以下の通り。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;設定項目&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reliability&lt;/td&gt;
&lt;td&gt;通信の信頼性。多少データが欠けてもいいから速さを優先する（Best Effort）か、確実に届ける（Reliable）か&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durability&lt;/td&gt;
&lt;td&gt;新しく繋がったSubscriberに対して、過去のデータも渡すかどうか&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;History&lt;/td&gt;
&lt;td&gt;直近何件分のデータを保持しておくか&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;PublisherとSubscriberでQoSの設定が噛み合っていないと、通信自体が繋がらないことがあるため、初心者がハマりやすいポイントの1つ&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="開発の基本的な流れ"&gt;開発の基本的な流れ&lt;/h2&gt;
&lt;h3 id="ワークスペース"&gt;ワークスペース&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ROS2のプロジェクト全体を置く作業ディレクトリ&lt;/li&gt;
&lt;li&gt;中に&lt;code&gt;src/&lt;/code&gt;ディレクトリを作り、その下に複数のパッケージ（後述）を置く構成が基本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;colcon build&lt;/code&gt;というビルドツールで、ワークスペース内の全パッケージをまとめてビルドする&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="パッケージ"&gt;パッケージ&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ROS2における、機能をまとめる単位（1つ以上のノードやライブラリをまとめたもの）&lt;/li&gt;
&lt;li&gt;各パッケージには&lt;code&gt;package.xml&lt;/code&gt;（パッケージの情報・依存関係を書くファイル）が必須&lt;/li&gt;
&lt;li&gt;ビルド方式によって、以下のように使うファイルが変わる
&lt;ul&gt;
&lt;li&gt;C++の場合: &lt;code&gt;CMakeLists.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pythonの場合: &lt;code&gt;setup.py&lt;/code&gt;（&lt;code&gt;ament_python&lt;/code&gt;というビルドタイプを使う）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="rosdepとワークスペースのsource"&gt;rosdepとワークスペースのsource&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;パッケージが依存する外部ライブラリを、OSのパッケージマネージャ経由で解決してくれるツールが&lt;code&gt;rosdep&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;ワークスペースの&lt;code&gt;src/&lt;/code&gt;以下にある全パッケージの依存関係を、&lt;code&gt;rosdep install --from-paths src -y --ignore-src&lt;/code&gt;のようなコマンドでまとめて解決できる&lt;/li&gt;
&lt;li&gt;&lt;code&gt;colcon build&lt;/code&gt;でビルドした後は、&lt;code&gt;source install/setup.bash&lt;/code&gt;を実行しないと、ビルドしたパッケージがシェルから認識されない&lt;/li&gt;
&lt;li&gt;「ビルドは通ったのに&lt;code&gt;ros2 run&lt;/code&gt;できない」というのは、大抵このsourceのし忘れが原因で、初心者が最初につまずきやすいポイントの1つ&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="基本的なコマンド"&gt;基本的なコマンド&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ros2 run &amp;lt;パッケージ名&amp;gt; &amp;lt;実行ファイル名&amp;gt;&lt;/code&gt;: 1つのノードを実行する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 launch &amp;lt;パッケージ名&amp;gt; &amp;lt;launchファイル名&amp;gt;&lt;/code&gt;: 複数のノードをまとめて起動する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 node list&lt;/code&gt;: 今動いているノードの一覧を見る&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 topic list&lt;/code&gt; / &lt;code&gt;ros2 topic echo &amp;lt;トピック名&amp;gt;&lt;/code&gt;: トピックの一覧確認・流れているデータの確認&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 service list&lt;/code&gt;: サービスの一覧を見る&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rviz2&lt;/code&gt;: センサーの値やロボットの状態を可視化するツール&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rqt&lt;/code&gt;: ノードの構成やログをGUIで確認できるツール&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="最小限のコード例rclpy"&gt;最小限のコード例（rclpy）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ここまでは概念の説明だったので、実際に最小限のノードを書いてみる&lt;/li&gt;
&lt;li&gt;以下は、Pythonのクライアントライブラリ&lt;code&gt;rclpy&lt;/code&gt;を使った、シンプルなPublisherの例&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;rclpy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rclpy.node&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;std_msgs.msg&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MinimalPublisher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;minimal_publisher&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_publisher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;topic&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timer_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;timer_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hello World: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Publishing: &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#34;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rclpy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MinimalPublisher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rclpy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destroy_node&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rclpy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;ポイント
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Node&lt;/code&gt;を継承したクラスを作るのが基本形&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create_publisher&lt;/code&gt;でPublisherを作り、&lt;code&gt;create_timer&lt;/code&gt;で一定間隔ごとに&lt;code&gt;timer_callback&lt;/code&gt;を呼ぶ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rclpy.spin(node)&lt;/code&gt;で、ノードを動かし続ける（この中で、後述のExecutorがコールバックを回している）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Subscriberの場合は&lt;code&gt;create_subscription&lt;/code&gt;を使い、メッセージが届くたびに呼ばれるコールバック関数を登録する形になる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="executor"&gt;Executor&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ノード内の複数のコールバック（タイマー・Subscriberなど）を、実際にどう実行するかを管理する仕組み&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rclpy.spin(node)&lt;/code&gt;を呼ぶと、内部でデフォルトのExecutor（SingleThreadedExecutor）が動き、登録されたコールバックを順番に処理する&lt;/li&gt;
&lt;li&gt;シングルスレッドのExecutorだと、1つのコールバックの処理が終わるまで次のコールバックは実行されない
&lt;ul&gt;
&lt;li&gt;時間のかかる処理を1つのコールバックに書いてしまうと、他のコールバック（他のトピックの受信など）がその間ブロックされてしまう&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;複数のコールバックを並行して処理したい場合は、MultiThreadedExecutorを使う&lt;/li&gt;
&lt;li&gt;「トピックが来ているはずなのに反応が遅い/来ない」という不具合の原因が、実はExecutorのブロッキングだった、というのはよくあるハマりどころ&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="実行時の設定と制御"&gt;実行時の設定と制御&lt;/h2&gt;
&lt;h3 id="パラメータparameters"&gt;パラメータ（Parameters）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ノードに渡す設定値のこと。プログラムを書き換えずに、動作を外から調整できる仕組み&lt;/li&gt;
&lt;li&gt;例えば「カメラのフレームレート」「しきい値」のような値を、パラメータとして持たせることが多い&lt;/li&gt;
&lt;li&gt;コマンドでの確認・操作
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ros2 param list&lt;/code&gt;: あるノードが持つパラメータの一覧を見る&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 param get &amp;lt;ノード名&amp;gt; &amp;lt;パラメータ名&amp;gt;&lt;/code&gt;: 値を取得する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 param set &amp;lt;ノード名&amp;gt; &amp;lt;パラメータ名&amp;gt; &amp;lt;値&amp;gt;&lt;/code&gt;: 値を変更する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;実際の運用では、パラメータを1つずつ&lt;code&gt;set&lt;/code&gt;するのではなく、後述のLaunchファイルからYAMLファイルでまとめて渡すのが一般的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="namespaceremapping"&gt;Namespace・Remapping&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Namespaceは、ノード名やトピック名の前につける「名前空間」で、同じパッケージ・同じノードを複数のインスタンスとして区別して動かすときに使う
&lt;ul&gt;
&lt;li&gt;例: &lt;code&gt;robot1/cmd_vel&lt;/code&gt;、&lt;code&gt;robot2/cmd_vel&lt;/code&gt;のように、ロボットごとに名前空間を分ける&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Remappingは、ノードが元々使っているトピック名・サービス名などを、実行時に別の名前に付け替える仕組み
&lt;ul&gt;
&lt;li&gt;例: &lt;code&gt;ros2 run pkg node --ros-args -r cmd_vel:=robot1/cmd_vel&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;どちらも、コードを一切変更せずに、起動時のオプションやLaunchファイルの引数だけで名前を切り替えられるのがポイント&lt;/li&gt;
&lt;li&gt;複数台のロボットを同時に動かす場合や、他人が作ったパッケージをそのまま自分のシステムに組み込む場合に、ほぼ必須になる知識&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="launchファイル"&gt;Launchファイル&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;複数のノードを、パラメータ付きでまとめて起動するための設定ファイル&lt;/li&gt;
&lt;li&gt;Python・XML・YAMLのいずれかで書ける（最近はPythonで書くことが多い）&lt;/li&gt;
&lt;li&gt;実際のロボットは複数のノードが同時に動いて成り立っているため、1つずつ&lt;code&gt;ros2 run&lt;/code&gt;するのではなく、launchファイルでまとめて立ち上げるのが基本的な運用になる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ライフサイクルノードmanaged-nodes"&gt;ライフサイクルノード（Managed Nodes）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;通常のノードは起動したらすぐ全機能が有効になるが、ライフサイクルノードは明示的な状態管理を持つ
主な状態は以下の通り。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;状態&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unconfigured&lt;/td&gt;
&lt;td&gt;起動直後、まだ設定されていない状態&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;設定は終わっているが、まだ動いていない状態&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;実際に処理を行っている状態&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finalized&lt;/td&gt;
&lt;td&gt;終了処理が終わった状態&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;状態遷移は、&lt;code&gt;configure&lt;/code&gt;・&lt;code&gt;activate&lt;/code&gt;・&lt;code&gt;deactivate&lt;/code&gt;・&lt;code&gt;cleanup&lt;/code&gt;・&lt;code&gt;shutdown&lt;/code&gt;といったサービス呼び出しで制御する&lt;/li&gt;
&lt;li&gt;Nav2は、内部の各サーバー（Planner、Controllerなど）がこのライフサイクルノードとして作られていて、&lt;code&gt;lifecycle_manager&lt;/code&gt;というノードが、それらの状態をまとめて管理・遷移させている&lt;/li&gt;
&lt;li&gt;Nav2起動時に「なぜか動かない」場合、実はライフサイクルが&lt;code&gt;Active&lt;/code&gt;まで遷移していないだけ、ということもよくある&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ロボット特有の周辺技術"&gt;ロボット特有の周辺技術&lt;/h2&gt;
&lt;h3 id="rosbag2"&gt;rosbag2&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;トピックに流れているデータを記録・再生するツール（ROS1の&lt;code&gt;rosbag&lt;/code&gt;のROS2版）&lt;/li&gt;
&lt;li&gt;実機やシミュレーションで流れたデータをファイルに記録しておき、後から同じデータを何度でも再生できる&lt;/li&gt;
&lt;li&gt;実機を毎回動かさなくても、記録済みのデータでノードの開発・デバッグを進められるのが大きな利点&lt;/li&gt;
&lt;li&gt;主なコマンド
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ros2 bag record -a&lt;/code&gt;: 全トピックを記録する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 bag record &amp;lt;topic1&amp;gt; &amp;lt;topic2&amp;gt;&lt;/code&gt;: 指定したトピックだけ記録する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 bag play &amp;lt;bagファイル&amp;gt;&lt;/code&gt;: 記録したデータを再生する&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 bag info &amp;lt;bagファイル&amp;gt;&lt;/code&gt;: 記録内容（トピック・件数・期間など）を確認する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;保存形式は&lt;code&gt;sqlite3&lt;/code&gt;か&lt;code&gt;mcap&lt;/code&gt;が使われる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="urdfunified-robot-description-format"&gt;URDF（Unified Robot Description Format）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ロボットの物理的な構造（リンクとジョイント）を記述するXML形式&lt;/li&gt;
&lt;li&gt;リンク（Link）は剛体の部品（腕の一部、車体など）、ジョイント（Joint）はリンク同士をつなぐ関節（回転・スライドなど）を表す&lt;/li&gt;
&lt;li&gt;各リンクの見た目（形状・色）や、衝突判定用の形状、質量・慣性などの物理情報もここに書ける&lt;/li&gt;
&lt;li&gt;&lt;code&gt;robot_state_publisher&lt;/code&gt;というノードが、URDFとジョイントの角度情報から、後述のTF2の座標変換を自動的に計算・配信してくれる&lt;/li&gt;
&lt;li&gt;RVizでロボットの3Dモデルを表示したり、Gazeboでシミュレーションしたりする際の元データにもなる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tf2座標変換"&gt;TF2（座標変換）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ロボットに関わる複数の座標系（フレーム）の関係を、時系列で管理する仕組み&lt;/li&gt;
&lt;li&gt;例えば「地図（map）」「ロボット本体（base_link）」「カメラ（camera_link）」は、それぞれ別の座標系を持っていて、TF2はこれらの間の相対位置・姿勢を管理する&lt;/li&gt;
&lt;li&gt;各フレームは木構造（ツリー）でつながっていて、&lt;code&gt;map -&amp;gt; odom -&amp;gt; base_link -&amp;gt; camera_link&lt;/code&gt;のように親子関係を辿ることで、任意の2点間の座標変換を計算できる&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2 run rqt_tf_tree rqt_tf_tree&lt;/code&gt;で、今のフレーム構成をツリー図として確認できる&lt;/li&gt;
&lt;li&gt;Nav2のようなナビゲーションスタックは、TF2が正しく配信されていることが前提になっているため、ロボット開発で最初につまずきやすいポイントの1つ&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="slamsimultaneous-localization-and-mapping"&gt;SLAM（Simultaneous Localization and Mapping）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;自己位置推定（Localization）と地図作成（Mapping）を同時に行う技術&lt;/li&gt;
&lt;li&gt;ロボットが未知の環境を動き回りながら、センサー情報をもとに「今どこにいるか」と「周囲の地図」を同時に構築していく&lt;/li&gt;
&lt;li&gt;前述のNav2は自己位置推定を行わない（AMCLなどと組み合わせる前提）ため、Nav2を使うには、事前にSLAMで地図を作っておくか、SLAMをリアルタイムで動かしながらナビゲーションする、という使い方になる&lt;/li&gt;
&lt;li&gt;ROS2では、2D LiDARを使ったSLAMの標準的な実装として&lt;code&gt;slam_toolbox&lt;/code&gt;というパッケージがよく使われる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="visual-slam"&gt;Visual SLAM&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;LiDARなどの距離センサーではなく、カメラ映像を使って自己位置推定・地図作成を行うSLAM&lt;/li&gt;
&lt;li&gt;画像内の特徴点（際立った点）をフレーム間で追跡し、その動きからカメラ（ロボット）自身の移動量と、周囲の3次元構造を推定する&lt;/li&gt;
&lt;li&gt;LiDARより安価なセンサー（カメラ）で実現できる一方、照明条件の変化や、特徴点の少ない環境（真っ白な壁など）に弱いという弱点がある&lt;/li&gt;
&lt;li&gt;代表的な実装として、ORB-SLAMやRTAB-Map（ROS2にも対応）などがある&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cuvslam"&gt;cuVSLAM&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;NVIDIAが開発した、GPU（CUDA）で高速化されたVisual SLAMの実装&lt;/li&gt;
&lt;li&gt;Isaac ROS（NVIDIAが提供するROS2向けのGPUアクセラレーションパッケージ群）の一部として提供されている&lt;/li&gt;
&lt;li&gt;ステレオカメラの映像を使い、GPU上で並列処理することで、CPUベースのVisual SLAMより高速・低遅延に自己位置推定を行える&lt;/li&gt;
&lt;li&gt;Jetsonシリーズのようなエッジデバイス上でのリアルタイム動作を意識して設計されている&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="nav2navigation2"&gt;Nav2（Navigation2）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ROS2上で自律移動（ナビゲーション）を実現するための標準的なソフトウェアスタック&lt;/li&gt;
&lt;li&gt;地図上で目的地を指定すると、経路計画と障害物回避をしながら自律的に移動できるようにする
主な構成要素は以下の通り。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;構成要素&lt;/th&gt;
&lt;th&gt;役割&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Global Planner&lt;/td&gt;
&lt;td&gt;地図全体を使って、大まかな経路を計画する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controller（Local Planner）&lt;/td&gt;
&lt;td&gt;直近の障害物やロボットの動特性を考慮して、実際の速度指令（前述の&lt;code&gt;cmd_vel&lt;/code&gt;）を計算する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Costmap&lt;/td&gt;
&lt;td&gt;障害物や走行コストを表現する地図。全体用のGlobal Costmapと、直近用のLocal Costmapの2種類がある&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavior Tree&lt;/td&gt;
&lt;td&gt;経路計画→移動→リカバリー動作といった、Nav2全体の動作フローを制御する仕組み&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recovery Behaviors&lt;/td&gt;
&lt;td&gt;経路が見つからない・スタックしたときに行う復帰動作（その場回転、後退など）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Nav2自体は「自分が今どこにいるか」は推定しないため、AMCLなどの自己位置推定と組み合わせて使うのが前提&lt;/li&gt;
&lt;li&gt;launchファイルで各構成要素をまとめて立ち上げ、パラメータYAMLで細かい挙動をチューニングするのが基本的な使い方&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="順運動学fkと逆運動学ik"&gt;順運動学（FK）と逆運動学（IK）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Nav2・SLAMが移動ロボット向けの話だったのに対し、ここからはロボットアーム（マニピュレータ）向けの基礎知識&lt;/li&gt;
&lt;li&gt;順運動学（Forward Kinematics, FK）: 各関節の角度が分かっているときに、アームの先端（エンドエフェクタ）がどの位置・姿勢にあるかを計算すること
&lt;ul&gt;
&lt;li&gt;「関節の角度 → 先端の位置」という向きの計算で、URDFのリンク・ジョイント情報から比較的簡単に求まる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;逆運動学（Inverse Kinematics, IK）: 逆に、アームの先端を「この位置・姿勢にしたい」と決めたときに、そのために必要な各関節の角度を計算すること
&lt;ul&gt;
&lt;li&gt;「先端の位置 → 関節の角度」という向きの計算で、同じ先端位置に対して関節角度の組み合わせが複数存在したり、そもそも解が存在しない（届かない位置）こともあり、FKより計算が難しい&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dhパラメータとリンク長"&gt;DHパラメータとリンク長&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;FKを計算する際、隣り合う関節同士の座標系の関係を統一的に表現するための方法が、Denavit-Hartenberg（DH）パラメータ&lt;/li&gt;
&lt;li&gt;以下4つのパラメータの組み合わせで、隣り合う座標系同士の変換を表現する
&lt;ul&gt;
&lt;li&gt;リンク長$a_i$: $x_i$軸に沿って測った、$z_i$軸から$z_{i+1}$軸までの距離。そのリンク（関節と関節の間の剛体部分）の物理的な長さにあたる&lt;/li&gt;
&lt;li&gt;リンクのねじれ角$\alpha_i$: $x_i$軸まわりに測った、$z_i$軸から$z_{i+1}$軸までの角度&lt;/li&gt;
&lt;li&gt;リンクオフセット$d_i$: $z_i$軸に沿って測った、$x_{i-1}$軸から$x_i$軸までの距離&lt;/li&gt;
&lt;li&gt;関節角$\theta_i$: $z_i$軸まわりに測った、$x_{i-1}$軸から$x_i$軸までの角度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;回転関節の場合、可変なのは関節角$\theta_i$だけで、リンク長$a_i$を含む残り3つはロボットの機械構造によって決まる定数になる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="pid制御とpd制御"&gt;PID制御とPD制御&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;関節や車輪を目標値（角度・速度・位置など）に追従させるための、古典的で最も広く使われるフィードバック制御手法&lt;/li&gt;
&lt;li&gt;目標値と現在値の差（誤差）$e(t)$を使って、以下の3項を組み合わせて制御量$u(t)$を計算する&lt;/li&gt;
&lt;/ul&gt;
$$
u(t) = K_p e(t) + K_i \int_0^t e(\tau)\,d\tau + K_d \frac{de(t)}{dt}
$$&lt;ul&gt;
&lt;li&gt;比例項（P, Proportional）: 誤差の大きさに比例した制御量。誤差が大きいほど強く補正する&lt;/li&gt;
&lt;li&gt;積分項（I, Integral）: 誤差の累積に比例した制御量。比例項だけでは埋まらずにじわじわ残る誤差（定常偏差）を解消する&lt;/li&gt;
&lt;li&gt;微分項（D, Derivative）: 誤差の変化率に比例した制御量。急激な変化を抑え、目標値を行き過ぎるオーバーシュートを防ぐ&lt;/li&gt;
&lt;li&gt;PID制御: 上記3項全てを使う、最も一般的な構成&lt;/li&gt;
&lt;li&gt;PD制御: 積分項を使わず比例項と微分項だけを使う構成。積分項による巻き込み（積分ワインドアップ）を避けたい場合や、定常偏差が問題になりにくい速度制御などで使われる&lt;/li&gt;
&lt;li&gt;ROS2では、&lt;code&gt;ros2_control&lt;/code&gt;フレームワークの&lt;code&gt;control_toolbox&lt;/code&gt;パッケージにPID制御の実装があり、&lt;code&gt;ros2_controllers&lt;/code&gt;の&lt;code&gt;pid_controller&lt;/code&gt;から直接利用できる&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="クランプclamp"&gt;クランプ（Clamp）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;値を決められた上限・下限の範囲内に収まるように制限する処理のこと&lt;/li&gt;
&lt;li&gt;制御出力$u(t)$が、モーターの実際の駆動範囲を超えないように上限・下限で切り詰める、といった使い方をする&lt;/li&gt;
&lt;li&gt;積分ワインドアップへの対策の1つに、クランプ方式のアンチワインドアップ（Clamping Anti-Windup）がある
&lt;ul&gt;
&lt;li&gt;出力が上限・下限に達している間は、積分項の蓄積を止める（またはその値自体を制限する）ことで、誤差が解消された後に出力が行き過ぎるのを防ぐ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="command-interfaceとstate-interfaceactvel"&gt;Command InterfaceとState Interface（ActVel）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PID制御が誤差$e(t)$を計算するには、目標値だけでなく「今どうなっているか」という実測値が必要になる&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ros2_control&lt;/code&gt;では、コントローラーからハードウェアへの指令をCommand Interface、ハードウェアからコントローラーへのフィードバックをState Interfaceとして抽象化している&lt;/li&gt;
&lt;li&gt;代表的な種類には&lt;code&gt;HW_IF_POSITION&lt;/code&gt;（位置）・&lt;code&gt;HW_IF_VELOCITY&lt;/code&gt;（速度）・&lt;code&gt;HW_IF_EFFORT&lt;/code&gt;（トルク・力）があり、同じ関節でもCommand用とState用で別々のインターフェースとして扱われる&lt;/li&gt;
&lt;li&gt;速度のState Interface（実測速度）は、しばしば「ActVel（Actual Velocity）」と呼ばれる。モータードライバの標準規格であるCiA402（CANopenのドライブ用プロファイル）でも、指令速度とは別に「Velocity actual value」というオブジェクトが定義されており、ActVelという略称はここに由来することが多い&lt;/li&gt;
&lt;li&gt;前述の&lt;code&gt;cmd_vel&lt;/code&gt;が、ナビゲーションスタックなど上位からロボット全体への速度指令であるのに対し、ActVelは個々の関節・モーターレベルでの実測値であり、抽象化のレイヤーが異なる点に注意&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="moveit"&gt;MoveIt&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ROS2上でロボットアームの動作計画（モーションプランニング）を行うための標準的なソフトウェアスタック&lt;/li&gt;
&lt;li&gt;Nav2が移動ロボット向けの標準スタックなのに対し、MoveItはアーム向けの標準スタックという位置づけ&lt;/li&gt;
&lt;li&gt;前述のIKを解いて目標姿勢までの関節角度を求めつつ、障害物を避けながらの経路計画や衝突検知も行う&lt;/li&gt;
&lt;li&gt;URDFで定義したロボットの構造をそのまま利用でき、Gazeboのようなシミュレータ上での動作確認にも対応している&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="gazeboシミュレータ"&gt;Gazebo（シミュレータ）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ロボットの物理シミュレーションを行うためのソフトウェア&lt;/li&gt;
&lt;li&gt;前述のURDFで記述したロボットモデルを仮想空間に配置し、重力・摩擦・衝突などを考慮した動きをシミュレートできる&lt;/li&gt;
&lt;li&gt;実機がまだない、あるいは実機を壊すリスクがある動作を試したいときに、ROS2のノードをそのまま実機と同じように動かして検証できる&lt;/li&gt;
&lt;li&gt;カメラ・LiDARなどのセンサーのシミュレーションデータも出力できるので、Nav2のようなセンサー入力が前提のスタックのテストにもよく使われる&lt;/li&gt;
&lt;li&gt;最近は従来のGazebo Classicから、後継のGazebo（旧Ignition Gazebo）への移行が進んでいる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="まとめ"&gt;まとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ROS2は、ロボットの各機能をノードという単位に分けて、通信でつなげて動かすためのフレームワーク&lt;/li&gt;
&lt;li&gt;インストールは、その時点の最新LTSディストリビューションを、対応するUbuntuのバージョンに入れるのが基本&lt;/li&gt;
&lt;li&gt;通信の基盤はDDSで、ROS1にあった中央サーバー（単一障害点）がない&lt;/li&gt;
&lt;li&gt;ノード間の通信は、Topic（非同期の流し込み）・Service（同期的な問い合わせ）・Action（進捗報告つきの長時間処理）の3種類を目的に応じて使い分ける。QoSはその通信の品質を細かく設定する仕組み&lt;/li&gt;
&lt;li&gt;実際のコードは、&lt;code&gt;Node&lt;/code&gt;を継承したクラスにPublisher/Subscriberやコールバックを持たせる形で書き、&lt;code&gt;rclpy.spin&lt;/code&gt;が内部のExecutorでコールバックを回す&lt;/li&gt;
&lt;li&gt;開発は、ワークスペースの中にパッケージを作り、rosdepで依存関係を解決してcolconでビルドし、setup.bashをsourceして使う、という流れが基本&lt;/li&gt;
&lt;li&gt;動作の調整はパラメータで行い、Namespace・Remappingで名前を実行時に切り替えられる。複数ノードとパラメータをまとめて起動するのがLaunchファイルの役割&lt;/li&gt;
&lt;li&gt;rosbag2でデータを記録・再生できるので、実機を毎回動かさなくても開発・デバッグを進められる&lt;/li&gt;
&lt;li&gt;URDFでロボットの構造を記述し、そこからTF2が座標変換を計算・配信する。この2つはNav2などの前提になる基礎的な仕組み&lt;/li&gt;
&lt;li&gt;SLAM（Visual SLAM・cuVSLAMも含む）で自己位置推定と地図作成を行い、その地図をもとにNav2が自律移動する、という役割分担になっている&lt;/li&gt;
&lt;li&gt;速度指令は&lt;code&gt;cmd_vel&lt;/code&gt;という標準的なトピック名でやり取りされ、ライフサイクルノードで状態管理されたNav2が、その&lt;code&gt;cmd_vel&lt;/code&gt;を出す自律移動用の標準スタック&lt;/li&gt;
&lt;li&gt;ロボットアームの場合は、関節角度から先端位置を求めるFK、先端位置から関節角度を求めるIKが基本になり、MoveItがそれを担うNav2の対になる標準スタック&lt;/li&gt;
&lt;li&gt;Gazeboを使えば、実機なしでもロボットのシミュレーションができる&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>