ROSで動かすロボットカー作り(きれいに地図を作成できるようにしてみた編)

前回はHector SLAMを使って地図が更新できるようにいろいろセットアップできるようにしてみました

supernove.hatenadiary.jp

ところが、ここで作成された地図は思ってたのと違う結果になってしまいどうしたことか…という状態でした。

そんな中久々にROS UG JPのLT大会があり、前回エントリーをネタにLTしてきました。

speakerdeck.com

このスライドをTwitterで投稿したら思いのほかバズって笑ってしまったのはここだけの話ですw。

で、そのツイートのリプに出力された地図がおかしい原因の解消に繋がりそうな有力情報がありました

それだぁぁーーーー!!!!

確かに言われて見れば地図を作成されるときってTFの座標系が表示されてそこから軌跡がrvizに出力されるはずなのにそんな様子がひとつもなかったんです。

というわけで、このリプをヒントに地図がきれいに生成されるように試してみました。(といっても頂いた記事の内容を参考にしただけですが…)

今回の成果物はいつもどおり以下のレポジトリのbacklog/step7ブランチで切っています。

github.com

mapping_default.launchの修正

以下の記事を参考にlauchファイルをいくつか修正していきます。

qiita.com

hector_slamディレクトリのhector_mapping/launch/mapping_default.launchのなかの5,6行目を修正します。

-  <arg name="base_frame" default="base_footprint"/>
-  <arg name="odom_frame" default="nav"/>
+  <arg name="base_frame" default="base_link"/>
+  <arg name="odom_frame" default="base_link"/>

そして54行目のコメントアウトを解除します。

-  <!--<node pkg="tf" type="static_transform_publisher" name="map_nav_broadcaster" args="0 0 0 0 0 0 map nav 100"/>-->
+  <node pkg="tf" type="static_transform_publisher" name="map_nav_broadcaster" args="0 0 0 0 0 0 map nav 100"/>

最終的に以下のようになっていれば大丈夫です。

<?xml version="1.0"?>

<launch>
  <arg name="tf_map_scanmatch_transform_frame_name" default="scanmatcher_frame"/>
  <arg name="base_frame" default="base_link"/>
  <arg name="odom_frame" default="base_link"/>
  <arg name="pub_map_odom_transform" default="true"/>
  <arg name="scan_subscriber_queue_size" default="5"/>
  <arg name="scan_topic" default="scan"/>
  <arg name="map_size" default="2048"/>
  
  <node pkg="hector_mapping" type="hector_mapping" name="hector_mapping" output="screen">
    
    <!-- Frame names -->
    <param name="map_frame" value="map" />
    <param name="base_frame" value="$(arg base_frame)" />
    <param name="odom_frame" value="$(arg odom_frame)" />
    
    <!-- Tf use -->
    <param name="use_tf_scan_transformation" value="true"/>
    <param name="use_tf_pose_start_estimate" value="false"/>
    <param name="pub_map_odom_transform" value="$(arg pub_map_odom_transform)"/>
    
    <!-- Map size / start point -->
    <param name="map_resolution" value="0.050"/>
    <param name="map_size" value="$(arg map_size)"/>
    <param name="map_start_x" value="0.5"/>
    <param name="map_start_y" value="0.5" />
    <param name="map_multi_res_levels" value="2" />
    
    <!-- Map update parameters -->
    <param name="update_factor_free" value="0.4"/>
    <param name="update_factor_occupied" value="0.9" />    
    <param name="map_update_distance_thresh" value="0.4"/>
    <param name="map_update_angle_thresh" value="0.06" />
    <param name="laser_z_min_value" value = "-1.0" />
    <param name="laser_z_max_value" value = "1.0" />
    
    <!-- Advertising config --> 
    <param name="advertise_map_service" value="true"/>
    
    <param name="scan_subscriber_queue_size" value="$(arg scan_subscriber_queue_size)"/>
    <param name="scan_topic" value="$(arg scan_topic)"/>
    
    <!-- Debug parameters -->
    <!--
      <param name="output_timing" value="false"/>
      <param name="pub_drawings" value="true"/>
      <param name="pub_debug_output" value="true"/>
    -->
    <param name="tf_map_scanmatch_transform_frame_name" value="$(arg tf_map_scanmatch_transform_frame_name)" />
  </node>
    
  <node pkg="tf" type="static_transform_publisher" name="map_nav_broadcaster" args="0 0 0 0 0 0 map nav 100"/>
</launch>

tutorial.launchの修正

続いて同じくhector_slamディレクトリのhector_slam_launch/launch/tutorial.launchuse_sim_timefalseにしてリアルタイム動作にします。

これで実機で問題なく地図が作成されるようになります。

<?xml version="1.0"?>

<launch>

  <arg name="geotiff_map_file_path" default="$(find hector_geotiff)/maps"/>

  <param name="/use_sim_time" value="false"/>

  <node pkg="rviz" type="rviz" name="rviz"
    args="-d $(find hector_slam_launch)/rviz_cfg/mapping_demo.rviz"/>

  <include file="$(find hector_mapping)/launch/mapping_default.launch"/>

  <include file="$(find hector_geotiff)/launch/geotiff_mapper.launch">
    <arg name="trajectory_source_frame_name" value="scanmatcher_frame"/>
    <arg name="map_file_path" value="$(arg geotiff_map_file_path)"/>
  </include>

</launch>

このファイルから先程修正したmapping_default.launchを呼び出していることがわかると思います。

hector_slam.launchの修正

最後にmy_roberのlaunchディレクトリ内の hector_slam.launchを先程修正した tutorial.launchを呼び出すように修正します。

  <!-- hector_slam -->
-  <include file="$(find hector_mapping)/launch/mapping_default.launch" />
-  <include file="$(find hector_geotiff)/launch/geotiff_mapper.launch">
-    <arg name="trajectory_source_frame_name" value="scanmatcher_frame"/>
-    <arg name="map_file_path" value="$(arg geotiff_map_file_path)"/>
-  </include>
+  <include file="$(find hector_slam_launch)/launch/tutorial.launch"/> 
  
</launch>

動作確認

それでは動作確認です。

以前用意したJoystickノードを使ってゲームパッドでロボットを動かすことで地図が作成されていきます。

このときJoystickノードから速度を配信するときにはなるべくゆっくりに設定するのがコツです。特にangular.zに関してはめちゃくちゃゆっくりにしないとちゃんと軌跡が描画されずTFの設定がちゃんとできてないときの汚い地図になります。

それに気をつけながら地図作成してみると、動画ではわかりにくいですがちゃんと軌跡に沿って空間情報が作成されていることが確認できます。

youtu.be

そして完成した地図です、前回とは比べ物にならないぐらいきれいな地図ができました!

まとめ

今回はHector SLAM できれいに地図が作成されるようにリベンジをしてきました。

ROSで位置、姿勢推定するうえでtfはかなり重要なキーワードで理解するのが大変ですが、これがわからないとSLAMが出来ないということに気付かされました。アドバイスくれたnisshan_ さん、ありがとうございました!

これでナビゲーションも順調に行ってくれるといいな…

BIOSパスワードのかかったThinkpadを突破してみた

その昔大学入学した頃何もPCの知識がないときに大学推奨PCを使っていました。

大学推奨のPCはクソスペックな上に値段も馬鹿みたいに高いです。

PCを買い替えてから今までずっと放置されていましたが、ついにそんなPCを手放しました。大須の佐古前装備さんで買取お願いしたら一応まだ動くこともあって意外といい値段付きました。

PCパーツやラップトップ売るなら佐古前装備さん超オススメです!

で手放す代わりにおなじみ大須のパウでジャンクのThinkpadを買いました(どうしてそうなったw)

お店にはいくつかジャンクのThinkpadがあって、こいつが一番見た目のダメージがひどくなかったので選びましたが、BIOSロックがかかっているのでPCのスペックを確認できないのがジャンク理由です。

ちなみに裏に張ってあったシール跡からどうやらIBMで昔業務用に使われていたものだったみたいです(Thinkpadは元々IBMが製造してましたもんね)。

ググってみたら工夫すればパスワードを突破できるみたいなので今回はこのBIOSパスワードを突破してみたいと思います。

用意するもの

  • Thinkpad x201i(パウで購入、メモリなし)
  • メモリ 2GB(PC3-8500)
  • ドライバー
  • 縫い針
  • ピンセット

まずは動作確認

試しに電源入れてみます。

BIOSがかかっているだけで通電は大丈夫でした。バッテリーもまだ生きてます。

とりあえず見た目の損傷もないしますます直しがいありますね。

突破法1:BIOSリセット

まずは本体を分解してCMOSボタン電池を外してみます。

キーボードを固定しているネジ(底面にキーボードのマークがあるところ)をはずしてキーボードを外せます。

黄色いカバーがついてるところがボタン電池です。つながっているコネクターを外してしばらく放電させてみます。

つなげ直して電源を入れましたが解除できませんでした。

というわけでこのThinkpadにはスーパーバイザーパスワード(SVP)がかかっているので今度はSVPを解除する方法を試します。

突破法2:針を刺してみる

SVPがかかっていることが分かったところで突破方法を試してみます。

なるべく分解したくないので針を使ってeepromの信号を乱してみます。

v2ndev.blog.fc2.com

↑の記事を参考に縫い針をピアに当てて起動を試みましたが何度やってもだめでした…

突破法3:eepromのi2cの信号ピンをショートさせる

針でうまく信号を乱せなかったのでeepromの信号ピン(SDA, SCL)をショートさせます。

eepromは基板を外す必要があるので、ハードウェア保守マニュアルをみながら外せるところを外して分解します。

分解するときは写真を撮っておくと安心です。

https://download.lenovo.com/pccbbs/mobiles_pdf/x200_x200s_x200si_x201_x201i_x201s_hmm_en_43y6632_11.pdf

SDA、SCLの位置は以下の記事を参考にしました

www.ja.axxs.net

SDAとSCLは隣り合わせみたいなのでピンセットを使って両方のピンをはさみます。

そして電源を入れると無事にSVPを突破してBIOSにアクセスできました!そして画面もきれいなので普通にOS突っ込めば問題なく使える状態になりました。

パスワードも無効になって一安心です。

キーボードの交換&OSインストール

というわけで分解したやつを元に戻してPCとして使える状態に戻りました。ここでよくありがちなネジの締め忘れでネジが余るという事態は起きませんでした(逆にネジを紛失して不足する事態もありませんでしたw)

無事に元の状態に組み上がったところで魔改造をします。僕はキーボードは英語配列派なので英語配列のキーボードに交換します(簡単にキーボードを交換できるという理由でThinkpadを買っています)。

キーボードはヤフオクで唯一売られてた新品を買いました。

そしてキーボードだけ無駄に新品のPCが出来上がりましたw。

そしてSSDを用意してOSをインストールします。

mSATAのSSDを使いたかったですが残念ながらこのモデルはストレージには対応してないっぽかったので普通に2.5インチのやつを使いました。

インストールするOSは例のごとくUbuntuです。

Ubuntu 20.04のインストールは慣れたもんです。

supernove.hatenadiary.jp

そして、無事にOSが立ち上がりました。

キーボードの反応も問題ないし無事にパスワードがかかってたPCが復活しました!

まとめ

今回はパスワードがかかったThinkpadを使えるようにしました。見た目もひどくないからこそこういうパターンはちょっとクセがありますが動かせるとかなり格安でPCを使えるようになるのでぜひ挑戦してみましょう!

まぁ今回はキーボードを交換したので余計お金かかりましたけどねw

ROSで動かすロボットカー作り(SLAMをできるようにしてみた編その2)

前回はgmappingを使ってSLAMをやろうとするところまでで終わりました

supernove.hatenadiary.jp

このブログの中では地図が更新されなくて多分机の上で動かすだけだと更新されないと思ってました。

が、これは大きな勘違いで正確にはodometryのデータを一つも用意してなかったのが原因です。

gmappingではロボットの移動距離の情報としてodometryをロボット側で配信する必要がありますが、それができてませんでした。

ただ、今作っているロボットにはロータリーエンコーダーとかを使ってモーターの回転数取れればいいのですが、なるべく楽したいのでLidarセンサーだけで地図を作成する方法を模索しました。

今回の成果物もいつも通り以下のレポジトリの backlog/chapter6ブランチで切っています。

github.com

用意するもの

前回と同様のものですが、一応載せておきます

動作環境

今回もRaspberry Pi上で試すのでRaspberry Piの動作環境を紹介します

LidarセンサーだけでSLAMをやる方法

ググったらいくつか候補が出てきたので試しました。

候補1:gmapping + laser_scan_matcher

gmapping単体だとodomデータが足りなくて地図が更新されませんが、laser_scan_matcherを使ってLidarのデータの変化量から移動距離を算出します。

sudonull.com

この方法ならセンサーを用意したり自分でロジックを書かなくても地図が更新されるようになるみたいです。

ただ、Raspberry Pi OSだとlaser_scan_matcherを使うために必要な依存パッケージが多いのとビルドエラーが出まくっているので今回は却下しました。

候補2:hector_slam

gmappingの代わりにhector_slamを使う方法が候補にありました。

これならodomデータがなくてもパッケージ単体で地図を生成できるらしいです。

試しに動かしてみたらセンサー単体を動かすだけでも簡単に地図を更新できるようになったので今回はこの方法で地図を生成してみました。

hector_slamをインストール

まずは依存パッケージをインストールします。

sudo apt install libflann-dev libeigen3-dev libglvnd-dev

ワークスペースディレクトリ直下のsrcディレクトリ内にhector_slamのソースコードをクローンします。

git clone https://github.com/tu-darmstadt-ros-pkg/hector_slam

ビルドを実行します。

catkin build

パッケージを追加したのでセットアップスクリプトを実行します。

source devel/setup.bash

これで必要な物は揃いました。gmappingのときと同じぐらい簡単にインストールが終わりました。

とりあえず動かしてみる

インストールはできたので試しに机の上でセンサーを動かしてみます。

今回は以下のlaunchファイルを使います。

ファイル名は hector_slam.launchとします。

<launch>

  <!-- X2L -->
  <node name="ydlidar_node"  pkg="ydlidar_ros"  type="ydlidar_node" output="screen" respawn="false" >
    <param name="port"         type="string" value="/dev/ydlidar"/>
    <param name="baudrate"         type="int" value="115200"/>
    <param name="frame_id"     type="string" value="base_link"/>
    <param name="resolution_fixed"    type="bool"   value="true"/>
    <param name="auto_reconnect"    type="bool"   value="true"/>
    <param name="reversion"    type="bool"   value="false"/>
    <param name="angle_min"    type="double" value="-180" />
    <param name="angle_max"    type="double" value="180" />
    <param name="range_min"    type="double" value="0.1" />
    <param name="range_max"    type="double" value="12.0" />
    <param name="ignore_array" type="string" value="" />
    <param name="frequency"    type="double" value="8"/>
    <param name="samp_rate"    type="int"    value="3"/>
    <param name="isSingleChannel"    type="bool"   value="true"/>
  </node>

  <!-- tf -->
  <node pkg ="tf" type="static_transform_publisher" name="map_to_odom" args="0.0 0.0 0.0 0.0 0.0 0.0 /map /nav 40"/>
  <node pkg ="tf" type="static_transform_publisher" name="odom_to_base_link" args="0.0 0.0 0.0 0.0 0.0 0.0 /nav /base_footprint 40"/>
  <node pkg="tf" type="static_transform_publisher" name="base_link_to_laser" args="0.2245 0.0 0.2 0.0 0.0 0.0 /base_footprint /base_link 40" />
  
  <!-- hector_slam -->
  <include file="$(find hector_mapping)/launch/mapping_default.launch" />
  <include file="$(find hector_geotiff)/launch/geotiff_mapper.launch" />
</launch>

gmappingのときは直でパラメータを指定してましたが、hector_slamではデフォルトで用意されているlaunchファイルを呼び出すようにしています。

ちなみに上記のlaunchファイルは以下の記事を参考にX2L向けにアレンジしました

archit0994.wixsite.com

実際に動かしてみます。

以下のコマンドでlaunchファイルを動かします。

roslaunch my_rober hector_slam.launch

そして可視化は以下のコマンドでrvizを起動します。(前回の同じ設定ファイルを読み込んでますが、他のSLAMパッケージでも使えるのでファイル名を変えてます)

rosrun rviz rviz -d $(find $(pwd) -name slam.rviz)

起動したらセンサーをいろいろ動かしてみるとそれに合わせて地図が更新されていることが確認できます。

navigationパッケージのセットアップ

無事に動作することが分かったところで実際に地図を作成していきます。

ただ今のままだとロボットを走らせて地図を作成してもその地図を保存する手段がありません。

地図を保存するためには navigationパッケージの map_serverを使用します。

そこで地図を作成する前にnavigationパッケージをセットアップしていきます。

まずは以下のコマンドで依存ライブラリをインストールします。

sudo apt install libbullet-dev libsdl-image1.2-dev libsdl-dev

ワークスペースディレクトリ直下のsrcディレクトリ上でnavigationパッケージとその依存パッケージをクローンします。

git clone -b melodic-devel https://github.com/ros-planning/navigation.git
git clone -b melodic-devel https://github.com/ros/geometry2.git
git clone -b ros1 https://github.com/ros-planning/navigation_msgs.git

ビルドを実行します。

catkin build

hector_slamと同様にセットアップスクリプトを実行します。

source devel/setup.bash

地図を作成する

いよいよ地図を作成していきます。

まずは以下の記事を参考にJoystickでロボットを動かす準備をします。

supernove.hatenadiary.jp

地図を作成する場所にロボットを設置したら以下のコマンドでマッピング用のノードを起動します

roslaunch my_rober hector_slam.launch

これでゲームコントローラーでロボットを操作しながら地図を作成します。

一通り地図を作成したら以下のコマンドで地図のデータを保存します。

 rosrun map_server map_saver

作成した地図はこんな感じです。

Lidarのみで作成しているだけに結構ぐちゃぐちゃした地図になってますね…

まとめ

今回はhector_slamを使ってSLAMに挑戦しました。

Lidarだけでマッピングできると思ってやってましたが、やっぱり急な方向転換に弱いせいか精度は今ひとつでしたね。

今度は精度の高い地図を作成するためにどうすればいいか試行錯誤していこうと思います。

ROSで動かすロボットカー作り(SLAMをできるようにしてみた編)

※最初に

このエントリーのオチはパッケージの僕の理解不足で地図が作成されませんでした。

とにかくてっとり速く動いたパッケージのセットアップ方法を知りたい方は以下のエントリーをどうぞ

supernove.hatenadiary.jp

以前Lidarセンサーを動かして無事にセンサーの情報を取得することができたので、今回はこのLidarセンサーを使ってSLAMをできるようにしてみます。

Lidarセンサーを試した記事はこちら

supernove.hatenadiary.jp

今回の成果物はいつものように以下のレポジトリの backlog/step5ブランチで切っています。

github.com

用意するもの

ロボットには他にもいくつかパーツ使っていますが、今回のブログで使う機材だけを紹介します

動作環境

今回はRaspberry Pi上で試すのでRaspberry Piの動作環境を紹介します

Gmappingのインストール

まずはSLAMに必要なパッケージをインストールします。

今回は最もメジャーなSLAMパッケージであるGmappingをインストールします。

ワークスペースディレクトリ直下のsrcディレクトリ内で以下のコマンドを実行してパッケージのソースコードをクローンしてきます。

cd rober_catkin_ws/src
git clone https://github.com/ros-perception/slam_gmapping

続いて、依存パッケージであるopenslam-gmappingも合わせてクローンします。

git clone https://github.com/ros-perception/openslam_gmapping.git

これで必要なパッケージのインストールができたので、ビルドします。

catkin build

エラーが出なければインストールは完了です。

lauchファイルの用意

続いてマッピング用のlaunchファイルを用意します。

Gmapping向けにX2LでSLAMできるようにLidar, gmapping, tfの各ノードの設定をしたlaunchファイルが以下の内容です。

ファイル名は slam_gmapping.launchとします

<launch>
  <!-- X2L -->
  <node name="ydlidar_node"  pkg="ydlidar_ros"  type="ydlidar_node" output="screen" respawn="false" >
    <param name="port"         type="string" value="/dev/ydlidar"/>
    <param name="baudrate"         type="int" value="115200"/>
    <param name="frame_id"     type="string" value="base_link"/>
    <param name="resolution_fixed"    type="bool"   value="true"/>
    <param name="auto_reconnect"    type="bool"   value="true"/>
    <param name="reversion"    type="bool"   value="false"/>
    <param name="angle_min"    type="double" value="-180" />
    <param name="angle_max"    type="double" value="180" />
    <param name="range_min"    type="double" value="0.1" />
    <param name="range_max"    type="double" value="12.0" />
    <param name="ignore_array" type="string" value="" />
    <param name="frequency"    type="double" value="8"/>
    <param name="samp_rate"    type="int"    value="3"/>
    <param name="isSingleChannel"    type="bool"   value="true"/>
  </node>

  <!-- gmapping -->
  <node pkg="gmapping" type="slam_gmapping" name="mapper">
    <param name="maxUrange" value="8.0" type="double" />
    <param name="delta" value="0.03" />
    <param name="xmax" value="30" type="double" />
    <param name="ymax" value="30" type="double" />
    <param name="xmin" value="-30" type="double" />
    <param name="ymin" value="-30" type="double" />
  </node>

  <!-- tf -->
  <node pkg ="tf" type="static_transform_publisher" name="map_to_odom" args="0.0 0.0 0.0 0.0 0.0 0.0 /map /nav 40"/>
  <node pkg ="tf" type="static_transform_publisher" name="odom_to_base_link" args="0.0 0.0 0.0 0.0 0.0 0.0 /nav /base_footprint 40"/>
  <node pkg ="tf" type="static_transform_publisher" name="base_link_to_laser" args="0.2245 0.0 0.2 0.0 0.0 0.0 /base_footprint /base_link 40" />
</launch>

動かしてみる

いよいよ動かしてみます。

以下のコマンドでslam_gmapping.launchを起動します

roslaunch my_rober slam_gmapping.launch

エラーなくログに以下の1行が表示されたらセンサーは正常に起動しています。

[YDLIDAR INFO] Now YDLIDAR is scanning ......

続いてRvizでマッピングした内容を表示します。

ワークスペースディレクトリ上で以下のコマンドを実行し、設定ファイルを読み込んでRvizを起動します。

rosrun rviz rviz -d $(find $(pwd) -name gmapping.rviz)

実行すると以下のようにLidarから作成した地図が表示されるようになります。

ACアダプターに接続していて大きく移動してないので地図は更新されることはありません。

多分ロボットが自由に動かせる状態にして動き回れば更新させられるはずです…

ここでSLAMで必要になる座標情報を扱うtfで配信されているフレームのtransformツリーを確認するために以下のコマンドを実行します。

rosrun tf view_frames

実行すると 以下のようにtransformツリーのframes.pdfが出力されます。

まとめ

今回はRaspberry PiでSLAMをできるようにしてみました。

SLAMはなかなか日本語の文献がなくて、しかもYDLidar向けだとなかなか情報が見つからなくてここまでできるのに結構時間がかかりました。

この辺の知識は学生時代に授業でやってたことだとは思うのですが、真面目に授業受ければよかったなと後悔しましたw。

これでSLAMがなんとかできるようになったので次回は実際に走行させて地図を作成していきます。

ROSで動かすロボットカー作り(ゲームコントローラーで操作できるようにしてみた編)

去年aliexpressの独身セールではいろいろ爆買いをしてたのですが、その中でノーブランドなUSBで接続できるコントローラーを買いました。

ja.aliexpress.com

このコントローラーは前回動かしたLidarセンサーを買ったときと同じタイミングで買いました。

supernove.hatenadiary.jp

僕は普段ゲームはマイクラをやるぐらいでコントローラーを使うようなゲームはしてません。

そう、まさにROSでゲームコントローラーを使いたくて買ってましたが半年ぐらい放置してましたw。

というわけで今回はこの積んでたコントローラーを使ってロボットカーを操作できるようにしてみました。

今回の成果物は以下のレポジトリのbacklog/step4ブランチで切っています。ちなみに直進と回転のメッセージが同時に配信されたときに直進の値関係なく回転するというバグがあったので、前回切ったブランチからrober.pyのロジックを変更しています。

github.com

実行環境

とりあえずメッセージを受け取ってみる

まずはROSでジョイスティックのメッセージを配信するパッケージをインストールします。

以下のコマンドでパッケージをインストールできます。本来は有名メーカーのコントローラーのROS用ドライバーをインストールする必要がありますが、ビルドエラーが発生するのと今回のコントローラーはなくても動かせたのでドライバーはインストールせずいきます。

sudo apt install ros-noetic-joy

それではジョイスティックを動かすノードを起動します。

以下のコマンドでroscoreを立ち上げます。

roscore

コントローラーをパソコンに接続したら別ターミナルを開いて以下のコマンドでジョイスティックノードを起動します。

rosrun joy joy_node

更に別ターミナルを開いて以下のコマンドを実行するとjoy_nodeから配信されるメッセージを購読できます。

rostopic echo /joy

ボタンやレバーを操作すると以下のようにリストの値の変化を確認できます。

ボタンやレバーを操作したときと離したときでそれぞれメッセージが配信されることが確認できます。

---
header: 
  seq: 1613
  stamp: 
    secs: 1648992334
    nsecs: 825536911
  frame_id: "/dev/input/js0"
axes: [-0.0, 1.0, 0.0, 0.0, 0.0, 0.0]
buttons: [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
---
header: 
  seq: 1614
  stamp: 
    secs: 1648992334
    nsecs: 841519585
  frame_id: "/dev/input/js0"
axes: [-0.0, 1.0, 0.0, 0.0, 0.0, 0.0]
buttons: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
---
header: 
  seq: 1615
  stamp: 
    secs: 1648992334
    nsecs: 914614853
  frame_id: "/dev/input/js0"
axes: [-0.0, -0.0, 0.0, 0.0, 0.0, 0.0]
buttons: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
---

Pythonでロボットをコントロールする

これで無事にコントローラーで値を取れるようになったので今度はPythonでロボットを操作してみます。

実装

先程確認したjoy_nodeからのメッセージを参考にしながらロボットの操作を実装すると以下のコードになります。

ファイル名は control_rober_joystick.pyとします。

#! /usr/bin/env python3
import rospy
from rospy.exceptions import ROSInterruptException
from sensor_msgs.msg import Joy
from geometry_msgs.msg import Twist


class Contorller:
    def __init__(self):
        rospy.init_node('controller', anonymous=True)
        self.linear_x = 0
        self.angular_zr = 0
        self.angular_zl = 0
        self.pub = rospy.Publisher('cmd_vel', Twist, queue_size=10)
        self.sub = rospy.Subscriber('joy', Joy, self.joy_callback)
        rospy.on_shutdown(self.shutdown_callback)
        rospy.Timer(rospy.Duration(1.0), self.timer_callback)
    
    def joy_callback(self, joy_msg):
        # Get Joy Event
        self.linear_x = joy_msg.axes[1]
        self.angular_zr = joy_msg.buttons[1]
        self.angular_zl = joy_msg.buttons[3]

    def timer_callback(self, event):
        twist = Twist()
        twist.linear.x = int(self.linear_x) * 0.5
        twist.angular.z = int(self.angular_zl - self.angular_zr) * 0.5
        rate = rospy.Rate(10)
        for i in range(5):
            self.pub.publish(twist)
            rospy.loginfo("linear_x: %f, angular_z: %f", twist.linear.x, twist.angular.z)
            rate.sleep()

    def shutdown_callback(self):
        rospy.loginfo("Stop")
        rospy.sleep(1)


if __name__ == '__main__':
    try:
        rospy.loginfo("Start")
        Contorller()
        rospy.spin()
    except ROSInterruptException:
        pass

これまでのシリーズで書いてきたコードとよく似ていますが、いくつか解説したいと思います。

まずは、コントローラーからのイベントを受け取る方法ですが、以下のコールバック関数を使用します。

ジョイスティックのイベントがオブジェクトで格納されているのであとはそこから必要な値を受け取るようになっています。どの値を受け取るかどうかはコントローラーによって変わると思うので、実際に動かしながらjoy_nodeから配信されるメッセージを確認するのがいいかと思います。

    def joy_callback(self, joy_msg):
        # Get Joy Event
        self.linear_x = joy_msg.axes[1]
        self.angular_zr = joy_msg.buttons[1]
        self.angular_zl = joy_msg.buttons[3]

そして次にメッセージを配信するにあたり以下の1行は僕の中では割と工夫したことです。angularの指定はボタンを押したか押してないか(1か0)が値として格納されています。つまり片方が1であればもう片方は必ず0になるようになっています。仮に別のボタンのイベントを拾って両方反応したとしてもプラマイゼロになるので回転は0として送られるわけです。

twist.angular.z = int(self.angular_zl - self.angular_zr) * 0.5

lauchファイルを用意

今回のプログラムは先程動かしたjoy_nodeが動いていることでそこからメッセージを購読してロボットに動きを配信する挙動になっています。

つまりjoy_nodeを起動しているのが前提なので、プログラムとセットで起動できるようにlaunchファイルを用意します。

ファイル名は joystick_control.launchとします。

<launch>
    <node name="joystick" pkg="joy" type="joy_node" />
    <node name="control_rober" 
        pkg="my_rober" 
        type="control_rober_joystick.py" 
        output="screen"
    />
</launch>

動作確認

それでは動作確認です。

今回は遠隔でロボットを動かそうと思うので、以前の記事を参考にマスターとスレーブの設定をしておきます。

supernove.hatenadiary.jp

その後、マスターでroscoreを起動します。

roscore

ロボット側では以下のコマンドを実行して司令の入力待ち状態にしておきます。

rosrun my_rober rober.py

コントローラーをつないでいるPCでは以下のコマンドを実行してコントローラーを動かせるようにします。

roslaunch my_rober joystick_control.launch

実際にロボットを動かしてみたのがこちらの動画です。

ちゃんとコントローラーの操作に合わせて車輪の動きが変わっているのがお分かりいただけると思います。

まとめ

今回はゲームコントローラーを使ってロボットを操作してみました。

普通にゲームコントローラーを動かそうとすると専用のインターフェースやドライバーが必要だったりしますが、ROSにはそのようなパッケージも含まれているので本当に便利です。

これを応用すればアームロボットの操縦もコントローラーで簡単にできるようになるはずです。

これでロボットを操縦できる状態になったので、これでロボットのマッピングをするために必要なものが準備できました。

というわけで次回は以前動かしたLidarセンサーを使ってロボットを動かしてみようと思います。

Dell Venue 8 Proにlubuntuを入れてROSもインストールしてみた

つい最近、佐古前装備さんでジャンクPCを漁っていたらWindowsタブレットPCを見つけました。

それがDellの Venue 8 Proです。

www.dell.com

佐古前装備はパソコンやパーツ、周辺機器等を扱うお店でタブレットスマホはめったに販売することがありません。

そのせいかタブレットの価格設定がバグっててジャンク価格でなんと1100円でしたw。

ちなみにジャンク理由はタッチパネルの反応がおかしいとのことです。

ヤフオクではあまり出ていませんでしたが、それでも1000円台の販売はそんなにないのでかなりお買い得です。

以前買ったジャンクのタブレットPCと同じ価格で有名メーカーのタブレットが買えるのはかなりラッキーな気がして迷わず買いました。

supernove.hatenadiary.jp

買った後に電源を入れてみたら普通に画面が映りました!(OSはWindows8.1が買ったときから入ってました)

そしてタッチパネルに関しては画面の縁の反応が悪いぐらいで大方の画面が普通に反応してたので悪くないです。

これでLinux突っ込んでもちゃんと動く気がします。

というわけで今回はこのタブレットUbuntuを突っ込んでみます。

インストールメディアを起動しようとしたら…

以前タブレットにインストールしたときと同様にインストールメディアを起動しようとしたらめちゃくちゃ重くて結構イライラしましたw。

supernove.hatenadiary.jp

何度か再インストールしたりして試行錯誤して2、3日経過しましたがうまく行かなかったです…

というわけでUbuntuのインストールは断念してUbuntuよりも軽量な派生ディストリビューションのlubuntuをインストールしてみました。

lubuntuのインストール

インストールメディアの作成

まずはlubuntuのOSイメージをダウンロードすることにしました。

以下のリンクからLTSの20.04のダウンロードします。

lubuntu.me

OSのインストール

インストールメディアの作成手順は以下の記事の通りなので、割愛します。

Ubuntuの派生ディストリビューションで使用するイメージが違う程度なのでOSのインストールも同じ手順でやってもらえばOKです。

supernove.hatenadiary.jp

インストール完了したら、インストールメディアを外して再起動したら以下のようにデスクトップ画面が表示されたら成功です。

ROSのインストール

OSを入れたところで今度はlubuntuにROSを入れてみます。

インストールは以下のリンクの手順通りにできます。

Ubuntuの派生なのでUbuntuのときと同様の手順で大丈夫です。

wiki.ros.org

Turtlesimを動かしてみる

それでは試しにTurtlesimを動かしてみます。

ワークスペースの作成し適当な名前のパッケージを作成します(今回はワークスペース名はcatkin_ws、パッケージ名はhelloworldとします)。

cd ~/catkin_ws/src
catkin_create_pkg helloworld

helloworldディレクトリを開いて以下のlauchファイルを作成します(ファイル名はcontrol_turtle.launchとします)。

<launch>
 <group ns ="sim">
  <node name="turtlesim" type="turtlesim_node" pkg="turtlesim"/>
  <node name="teleop_key" type="turtle_teleop_key" pkg="turtlesim" />
 </group>
<launch>

このlaunchファイルを実行して以下のようにturtlesimをteleopkeyで動かせるようになったらROSは正常に動作しています。

まとめ

今回はジャンクのタブレットPCにROSを入れてみました。

以前のジャンクタブレットはタッチパネルが反応しなくて困っていましたが、今回のやつは特に特別なセットアップしなくてもタッチパネルがちゃんと反応してたので多分前回セットアップしたタブレットの調子が悪いだけということが分かりました。

ただ、今回のタブレットではUbuntuよりも軽量とはいえブラウザの挙動が固まったりして全体的に動作がもっさりしてたので、使い方に気をつけないといけないですね…

今後はこのタブレットでROSを動かしてちょっとしたロボットのモニタリングなんかやってみたいなと思います(処理落ちしないか心配ですが…)

Arducam Pico4ML TinyMLを触ってみた

Switch Scienceで面白そうなボードを見つけたので買ってみました。

www.switch-science.com

最近注目されているTiny MLのためのRaspberry Pico互換ボードです。

もともとマイコンのような安価なガジェットを使った高度な処理をするというところに魅力を感じていて、そのポテンシャルを触って体感したいなと思ってました。

というわけで今回は一通りデモを触るだけですがボードを動かしてみたいと思います。

開封の儀

付属品はマニュアルとスイッチ付きのマイクロUSBケーブルです。

このスイッチ付きっていうところがファームウェアを書き込む作業をするときのことを想定されていていいですね!

f:id:KMiura:20220319201736p:plain

環境構築

こちらで紹介されている方法でRaspberry Pi 400で構築していきます(デモプログラムを動かすだけなら飛ばしてもいいですが、今後自作モデルを動かすために一応やっておきます)。

github.com

以下のコマンドでスクリプトをクローンしてセットアップしていきます

git clone https://github.com/raspberrypi/pico-setup.git
./pico-setup/pico_setup.sh

スクリプトを実行したら一度Raspberry Piを再起動してUARTの設定を完了させます。

デモプログラムを動かす

環境構築を終えたら今回使用するデモプログラムを動かしてみます。

github.com

以下のコマンドでサンプルプログラムのレポジトリをクローンします。

git clone https://github.com/ArduCAM/pico-tflmicro
cd pico-tflmicro

このレポジトリにはビルド済みのファームウェアが用意されているのでいくつか試してみましょう。

micro speech

まずは特定の単語に反応するデモを試します。

書き込むときには BOOTSELボタンを押しながらボードの電源を入れて書き込みモードにします。(このときにスイッチ付きのケーブルを使えばそこで電源のON・OFFができるのでとても効率いいです)

f:id:KMiura:20220319215031j:plain

以下のコマンドでRaspberry Piのストレージとマイコンボードのストレージをマウントします。

sudo mkdir -p /mnt/pico
sudo mount /dev/sda1 /mnt/pico

マウントしたら以下のコマンドで使用するuf2ファイルをコピーします。

sudo cp bin/micro_speech.uf2 /mnt/pico
sudo sync

書き込みを終えると以下のようにボードに実装されたマイクに「yes」か「no」を話して認識するとディスプレイに文字が表示されるようになります。(動画ではyesしか認識してませんが頑張ればnoも認識できますw)

ちなみにスマートスピーカーのWake Word(「Alexa」とか「OK, Google」みたいに最初の声掛けのこと)の認識にはこのTiny MLが使われているそうです。

person detection

次はカメラを使って人を識別するデモを試します。

書き込みモードにしたら以下のコマンドでファームウェアを書き込みます。

sudo mkdir -p /mnt/pico
sudo mount /dev/sda1 /mnt/pico
sudo cp bin/person_detection_int8.uf2 /mnt/pico
sudo sync

書き込んだら以下のように人の画像にカメラを向けると人の認識率が表示されるようになります。

画像認識は定番のデモですが、やっぱり小さいガジェットで識別するとなると楽しいもんですね。

magic wand

最後はmagic wandという魔法の杖のようにボードを振り回してそのジェスチャーを認識するデモをやってみます。

書き込みモードにしたら以下のコマンドでファームウェアを書き込みます。

sudo mkdir -p /mnt/pico
sudo mount /dev/sda1 /mnt/pico
sudo cp bin/magic_wand_ble.uf2 /mnt/pico
sudo sync

以下のようにボードを持ちながら手で数字を書くジェスチャーをするとディスプレイにその軌跡と認識した数字・識別率が表示されます

これはマイコンならではのセンサーを使ったデモで面白いですね。

まとめ

というわけでTiny MLのマイコンボードを触ってみました。

ソースコードC++なのでソースコードをいじるにはちょっと気合が入りますが、いずれ自前モデルを用意してなにかデモ作ってみたいですね。

それはいつかまたやります(こういうときに限って結局やらなかったり…)