TurtleBot3はRoombaのドライバ(create_driver)の通信できない
原因:TurtleBot3とRoombaで話す「言語(型)」が違った
以前、Kakipから直接コマンドを打ってRoombaが動いた時のコマンドは…
ros2 topic pub /cmd_vel geometry_msgs/Twist ...
この時、Twist(単純な速度データ)という型を使っていました。Roombaのドライバ(create_driver)は、この Twist 型を待ち受けています。
しかし、ログを見ると、TurtleBot3のキーボード操作ノード(teleop_keyboard)は TwistStamped(速度データ+時刻データが付与された新しい型)を送信しています。ROS 2の最新バージョン(Jazzyなど)では、より厳密な制御のためにTurtleBot3側が仕様変更されたようです。
ROS 2では、トピック名(/cmd_vel)が同じでも、メッセージの型が違うと「全く別の通信」とみなされ、お互いに接続を拒否します。 だからRoomba側は受信せず(Subscription count: 0)、コマンドが素通りしてしまっていたのです。
解決策:標準的な「Twist」を送るツールを使う
TurtleBot3専用の操作パッケージを使うのをやめ、ROS 2で最も標準的な(Twist型を送信する)キーボード操作ツールを使うことにしました。
Liderのドライバをインストールするプロセスで苦戦
SDKはインストールできたが、肝心のドライバがビルドできない
このエラーは、**ROS2 Jazzyの仕様変更(APIの厳密化)**が原因のようです。
エラーログにある error: no matching function for call to ‘rclcpp::Node::declare_parameter(const char [5])’ は、「パラメータの型が推論できないため、declare_parameterが実行できない」という内容です。
以前のバージョン(FoxyやHumbleなど)では node->declare_parameter("port"); のようにパラメータ名だけで宣言できましたが、Jazzy以降は**「型を明示する」か「デフォルト値を渡して型推論させる」**ことが必須になりました。現在提供されているYDLidarのドライバコードが古いROS2仕様のままになっているため、Jazzyのコンパイラに弾かれてしまっています。
対策:ソースコードの修正
根本的な解決として、該当するC++のソースコードを開き、手動で修正する必要があります。以下の手順で書き換えを行います。
- 該当ファイルをエディタで開く
端末(ターミナル)を開き、nanoなどのテキストエディタでエラーが出ているファイルを開きます。
Bash nano ~/create_ws/src/ydlidar_ros2_driver/src/ydlidar_ros2_driver_node.cpp
2. declare_parameter の箇所を修正する
44行目付近から node->declare_parameter(...); がズラリと並んでいる箇所があります。これらをすべて**「変数の型を明示する(<型>を追加する)」**形に修正します。
【修正前】(44行目付近〜)
C++
node->declare_parameter("port");
node->get_parameter("port", port);
node->declare_parameter("ignore_array");
node->get_parameter("ignore_array", ignore_array);
node->declare_parameter("frame_id");
node->get_parameter("frame_id", frame_id);
// ... (下へ続く)
node->declare_parameter("debug");`
【修正後】(以下のように書き換えてください)
C++
node->declare_parameter<std::string>("port");
node->get_parameter("port", port);
node->declare_parameter<std::string>("ignore_array");
node->get_parameter("ignore_array", ignore_array);
node->declare_parameter<std::string>("frame_id");
node->get_parameter("frame_id", frame_id);
node->declare_parameter<int>("baudrate");
node->get_parameter("baudrate", baudrate);
node->declare_parameter<int>("lidar_type");
node->get_parameter("lidar_type", lidar_type);
node->declare_parameter<int>("device_type");
node->get_parameter("device_type", device_type);
node->declare_parameter<int>("sample_rate");
node->get_parameter("sample_rate", sample_rate);
node->declare_parameter<int>("abnormal_check_count");
node->get_parameter("abnormal_check_count", abnormal_check_count);
node->declare_parameter<int>("intensity_bit");
node->get_parameter("intensity_bit", intensity_bit);
node->declare_parameter<bool>("fixed_resolution");
node->get_parameter("fixed_resolution", fixed_resolution);
node->declare_parameter<bool>("reversion");
node->get_parameter("reversion", reversion);
node->declare_parameter<bool>("inverted");
node->get_parameter("inverted", inverted);
node->declare_parameter<bool>("auto_reconnect");
node->get_parameter("auto_reconnect", auto_reconnect);
node->declare_parameter<bool>("isSingleChannel");
node->get_parameter("isSingleChannel", isSingleChannel);
node->declare_parameter<bool>("intensity");
node->get_parameter("intensity", intensity);
node->declare_parameter<bool>("support_motor_dtr");
node->get_parameter("support_motor_dtr", support_motor_dtr);
node->declare_parameter<float>("angle_max");
node->get_parameter("angle_max", angle_max);
node->declare_parameter<float>("angle_min");
node->get_parameter("angle_min", angle_min);
node->declare_parameter<float>("range_max");
node->get_parameter("range_max", range_max);
node->declare_parameter<float>("range_min");
node->get_parameter("range_min", range_min);
node->declare_parameter<float>("frequency");
node->get_parameter("frequency", frequency);
node->declare_parameter<bool>("invalid_range_is_inf");
node->get_parameter("invalid_range_is_inf", invalid_range_is_inf);
※各 declare_parameter の直後にある get_parameter の行はそのまま残して大丈夫です。
node->declare_parameter<bool>("debug");
もしファイル内に他にも <型> がついていない node->declare_parameter("..."); が残っている場合は、同じようにコンパイラに弾かれてしまうので、該当する変数に合わせて型
(<bool>, <int>, <float>, <std::string> のいずれか)を追記してください。
【修正前】
node->declare_parameter("m1_mode");
node->declare_parameter("m2_mode");
node->declare_parameter("m3_mode");
【修正後】
node->declare_parameter<int>("m1_mode");
node->declare_parameter<int>("m2_mode");
node->declare_parameter<int>("m3_mode");
ドライバをビルドし、インストールしてUSBに差し込んでも認識しない
LiderのUSB-シリアル変換チップはRoombaとの通信に使っているFTDIではなく、CP210xでした。よって、再びカーネルビルドに戻ってCP210xをmenuconfigで<M>にして再ビルド。このドライバを追加して認識させた。
※ここでうっかり新しいカーネルをビルドしてしまい、ドライバは無事にビルドできたがバージョンが違うのでカーネルに取り込めなくなってしまった。カーネルのバージョンを戻して対応し、ようやく認識できた。
今度はLiderのスキャンが出来ない(ydlidar_ros2_driver ydlidar_launch_view.py)が走らない
ydlidar_ros2_driver のLaunchファイルは、少し古いバージョンのROS 2(Foxyなど)の構文で書かれています。新しいROS 2(おそらくHumble以降)では、ノードを定義する際のパラメータの指定方法が変更されているため、「必須の引数(nameとnamespace)が渡されていない」というエラー(TypeError)が発生しています。
以下の手順でLaunchファイルを直接書き換えることで解決できました。
修正手順
- 対象のLaunchファイルを開く
ワークスペース内にある ydlidar_launch_view.py をテキストエディタで開きます。
Bash
cd ~/create_ws/src/ydlidar_ros2_driver/launch/
nano ydlidar_launch_view.p
- パラメータ名を変更する
ファイルの中にある LifecycleNode( または Node( で始まる記述を探します。その中の古い引数名を、新しいROS 2の仕様に合わせて以下のように書き換えてください。
node_name ➔ name
node_namespace ➔ namespace
node_executable ➔ executable (※この記述がある場合)
関連ファイルも確認する
ydlidar_launch_view.py は内部で同じディレクトリにある ydlidar_launch.py を呼び出している構造になっていることが多いです。エラーを完全に防ぐため、ydlidar_launch.py も開いて同様の古い記述がないか確認し、あれば修正してください。
- ワークスペースをリビルドする
Launchファイルを書き換えたら、変更をシステムに反映させるためにワークスペースを再ビルドします。
Bash
cd ~/create_ws
colcon build --packages-select ydlidar_ros2_driver
source install/setup.bash
RVizが動かない
エラー1:Lidarのパラメータ不足
【エラー内容】
Statically typed parameter 'intensity_bit' must be initialized.
【原因】
ROS 2(特に最新のJazzyなど)ではパラメータの型チェックが厳格になっています。Lidarのドライバが intensity_bit というパラメータを読み込もうとしましたが、設定ファイル(ydlidar.yaml)の中にその項目の記述がないため強制終了しています。
【解決策】
パラメータファイルに不足している項目を追記します。
ワークスペース内の ydlidar.yaml をエディタで開きます。
Bash
nano ~/create_ws/src/ydlidar_ros2_driver/params/ydlidar.yaml
ファイルの末尾の方(あるいは他のパラメータが並んでいる場所)に、以下の行(必要であれば intensity も)を追記して保存します。
ファイルの内容を以下のように修正してください(すでにある項目は残しつつ、足りないものを追加します)。特に後半の fixed_resolution や isSingleChannel などが重要です。
YAML
ydlidar_ros2_driver_node:
ros__parameters:
port: /dev/ydlidar
frame_id: laser_frame
ignore_array: ""
baudrate: 115200
lidar_type: 1
device_type: 0
sample_rate: 3
abnormal_check_count: 4
fixed_resolution: true
reversion: false
inverted: true
auto_reconnect: true
isSingleChannel: true
intensity: false
support_motor_dtr: true
angle_max: 180.0
angle_min: -180.0
range_max: 8.0
range_min: 0.1
frequency: 8.0
invalid_range_is_inf: false
intensity_bit: 0
invalid_range_is_inf: false
intensity_bit: 0
debug: false <-- これを追加
invalid_range_is_inf: false
intensity_bit: 0
debug: false
m1_mode: 0 <-- これを追加
その後も続く
(※ X2モデルの場合、baudrate は 115200、isSingleChannel は true になるのが一般的です)
エラー2:RViz(画面表示)の起動失敗
【エラー内容】
qt.qpa.xcb: could not connect to display
This application failed to start because no Qt platform plugin could be initialized.
【原因】
KakipボードにSSHでリモート接続しているため。
ydlidar_launch_view.py は、Lidarを動かすと同時に**RViz2(画面上にレーザーを表示するGUIツール)**を起動しようとします。しかし、Kakip側に画面を出力する環境がないためクラッシュしています。
【解決策】
Kakipボード上ではGUIツールであるRVizを起動せず、Lidarのドライバ(データの配信)だけを動かします。
実行するLaunchファイルを ydlidar_launch_view.py(RVizあり)から、**ydlidar_launch.py(RVizなし・ドライバのみ)**に変更します。
進展はしたが、ハード的にタイムアウトが発生してLiderのScanが出来ない
設定ファイルでボーレートを設定しても、c++のソースコード内で異なるボーレートの設定をしていた
YAMLファイルでいくら 115200 と設定しても反映されなかった理由は、Lidarを制御するC++のソースコードの中で、デフォルト値として 230400 が直接書き込まれていたからでした。YAMLの設定よりプログラム直書きの数値が優先されてしまっていたようです。
根本のC++プログラムを直接書き換えてしまいます。
- C++のソースコードを開く
以下のコマンドで、原因のファイルを開きます。
Bash
vi ~/create_ws/src/ydlidar_ros2_driver/src/ydlidar_ros2_driver_node.cpp
- 61行目付近の数値を書き換える
少し下へスクロールしていくと(61行目付近に)、以下の記述があります。
C++
int optval = 230400;
node->get_parameter("baudrate", optval);
この 230400 を 115200 に書き換えて保存します。
プログラムを再コンパイル(ビルド)する
C++のソースコードを書き換えたので、改めてビルドして変更を適用します。
Bash
cd ~/create_ws
colcon build --packages-select ydlidar_ros2_driver
source install/setup.bash
- 起動テスト!
いざ、実行です。
Bash
ros2 launch ydlidar_ros2_driver ydlidar_launch.py
起動ログの中で、 [/dev/ydlidar:115200] に変わるはずです。
そしてタイムアウト(Timeout count)が出ずに、画面がピタッと止まって待機状態になればOKです。
参考資料から離れてTurtleBot3用のNav2パッケージを使わないようにする
Nav2のパラメータファイル(nav2_params.yaml)は非常に巨大ですが、ゼロから全てを書く必要はありません。Nav2のデフォルト設定をベースに、Roombaの特性(差動二輪・円形)だけ変更します
解決手順:公式デフォルトパラメータのカスタマイズ
- 公式の設定ファイルをコピーする
新しいターミナルを開き、Nav2が標準で用意している完全なパラメータファイルを、先ほどと同じ場所に上書きコピーします。
Bash
cp /opt/ros/jazzy/share/nav2_bringup/params/nav2_params.yaml ~/roomba/nav2/roomba_nav2_params.yaml
- コピーしたファイルを編集する
コピーした roomba_nav2_params.yaml をテキストエディタで開きます。ファイルは長いですが、変更するのは以下の2箇所(4行)だけです。
・Roombaのサイズを教える(2箇所)
local_costmap と global_costmap の両方にある robot_radius を探して 0.17(17cm)に変更します。
変更前
robot_radius: 0.22
変更後
robot_radius: 0.17
・Lidarのトピック名を合わせる(必要に応じて)
もし前回お使いのYDLIDARのトピック名が /scan であれば、topic: scan となっている部分を topic: /scan に変更します。(※ scan のままでも動くことが多いですが、念のため / をつけると確実です。これも local と global の2箇所あります)。
マッピング(SLAM)が完了して map.yaml が用意できたら、いよいよこのパラメータファイルを読み込ませてNav2を起動します。
SLAMは完了して地図が生成できました!いよいよナビゲーションに入ります
手順1:各種ドライバとTFの起動
すでにRoombaのドライバと、YDLIDAR-X2のノードは起動している前提で進めます。これらに加えて、センサーの位置関係を配信します。
新しいターミナルを開き、以下のコマンドを実行してTFをパブリッシュします。(※以下の数値 0.0 0.0 0.2 は「Roombaの中心から高さ20cmの位置にLidarがある」という仮の設定です。実際の取り付け位置に合わせてX, Y, Zの値を変更してください)
Bash
ros2 run tf2_ros static_transform_publisher 0.0 0.0 0.2 0.0 0.0 0.0 base_link laser_frame
(※YDLIDAR側のフレーム名が laser_frame 以外の場合は適宜書き換えてください)
手順2:Nav2の標準起動(nav2_bringup)
いよいよ、作成したパラメータファイルと地図を使ってNav2を起動します。
新しいターミナルを開き、以下のコマンドを実行します。ファイルパスは実際の保存場所に合わせて変更してください。
Bash
ros2 launch nav2_bringup bringup_launch.py \
use_sim_time:=False \
map:=/絶対パス/または/相対パス/map.yaml \
params_file:=/絶対パス/または/相対パス/roomba_nav2_params.yaml
これで、バックグラウンドでNav2の各種サーバー(プランナー、コントローラー、リカバリーなど)が立ち上がります。
手順3:RViz2での可視化と操作
最後に、視覚的に操作を行うためのRViz2を起動します。Nav2にはあらかじめ設定済みの便利なRViz用のファイルが用意されています。
新しいターミナルを開き、以下を実行します。
Bash
ros2 run rviz2 rviz2 -d $(ros2 pkg prefix nav2_bringup)/share/nav2_bringup/rviz/nav2_default_view.rviz
手順4:初期位置の推定とナビゲーション実行
RViz2の画面が開いたら、以下の操作を行います。
初期位置の設定 (2D Pose Estimate):
画面上部のツールバーにある「2D Pose Estimate」ボタンをクリックし、マップ上の「実際のRoombaがいる位置」をクリックして、Roombaが向いている方向へドラッグします。
これにより、Lidarのスキャンデータとマップの壁の形がピタリと重なるはずです。
目的地の設定 (Nav2 Goal):
ツールバーの「Nav2 Goal」ボタンをクリックし、Roombaを移動させたい場所をクリックして、最終的に向かせたい方向へドラッグします。
これで、経路(グローバルパス)が計算され、先ほど nav2_params.yaml で設定した Twist 型のコマンド(cmd_vel)がRoombaに送信されて、自律移動が始まるはずです。
自律移動できず(時計に罠があった)
エラー文の以下の部分にご注目ください。
Requested time 1772961110 (RVizで初期位置を指示した現在時刻)
latest data is at time 1772809384 (Roombaから送られてきているオドメトリの最新時刻)
この2つの数字(UNIXタイム)を引き算すると、約42時間(約2日弱)もの大きな時間のズレが生じています。ROS2のTF(位置関係の計算)は時刻に非常に厳格なため、「42時間前のデータと、今のデータを繋ぎ合わせることはできない(過去や未来の推測は危険だから)」とNav2が安全装置を働かせてエラーを出しています。
インターネットの時計情報から時刻を自動設定するようにする
NTP(ネットワーク時刻同期)を有効にする
sudo timedatectl set-ntp true
状態を確認する
timedatectl status
dateコマンドで正しい時刻が表示されればOK
それでも自律走行がスタートしない
bt_navigator: Action server is inactive というログは、Nav2の頭脳(ナビゲーションの総指揮官)が「まだ準備完了(Active)状態になっていないので、命令を受け付けられません」と拒否している状態を表しています。
Nav2は非常に慎重なシステム(ライフサイクル管理)を採用しており、「プランナー」「コントローラー」「センサー」などの全機能が完璧に揃ってから、一斉に「Inactive(待機)」から「Active(稼働)」へ状態を切り替えます。もしどこか一つでも設定に問題があると、安全のために全体がInactiveのままストップしてしまいます。
この状態を突破するために、以下の2点を確認・操作してみてください。
- RViz2の「起動ボタン」を確認する
稀に、初期位置(2D Pose Estimate)を設定しただけでは自動でActiveに切り替わらないことがあります。
RViz2の画面の左下(または左ペイン)に、「Nav2」という小さな操作パネルが表示されていますでしょうか?
もしそこに 「Startup」(または Playマークのボタン)があれば、それをクリックしてみてください。これで強制的に全体がActiveに切り替わり、経路生成を受け付けるようになることがあります。
このあたりはまだ良く分かっていないので、動かしながら正しい手順を研究したいと思います