Real VNC ServerをUbuntuにセットアップして別のネットワークからでも接続できるようにしてみた

転職して3ヶ月経ちましたが抱えている悩みがあります。

それはオフィスのデスクトップPCに家から接続したい。

出社がメインでオフィスではデスクトップPCを使って作業していますが、リモートワークもすることがありオフィスで作業した内容をコミットし忘れて作業が出来ないという課題がありました。

というわけでこの課題を解消するためにReal VNCを導入して別ネットワークから接続できるようにしてみました。

今回はUbuntuのデスクトップPCでのセットアップ方法を紹介します。

インストール

以下のサイトからReal VNC Serverをインストールファイルをダウンロードします。

www.realvnc.com

以下のコマンドでインストールします。

sudo dpkg -i ./VNC-Server-6.9.1-Linux-x64

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

sudo apt install libcanberra-gtk*

セットアップ

UbuntuVNC ServerをセットアップするにはReal VNCアカウントが必要です。

アカウントが無いときは以下のサイトから無料で作成できます。

https://manage.realvnc.com/en/

アカウント作成したらRealVNCを起動して画面の流れに沿ってセットアップしていきます。

基本的にはデフォルトの設定で進めればOKです。

これであとは接続するPCにVNC Viewerをインストールして右上からログインを行います。

ログインが完了し、先程のセットアップしたPCと接続できたら完了です。

ログアウト状態からでも接続できるようにする

このまま接続するのはいいのですが、PCを使わないときはログアウト状態が続きます。

Ubuntu 20.04だとデフォルトの設定ではログアウト状態が長続きするとスリープになってしまい、ネットワークも切断されます。

ocg.aori.u-tokyo.ac.jp

そこで↑を参考に以下のコマンドで自動スリープを無効にします。

sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

これでログアウト状態のPCにいつでも接続できるようになります。

Raspberry Pi zero 2 w開封レビュー

先日奇跡的にRaspberry Pi zero 2 wをスイッチサイエンスで買えました。(ちなみに右側の赤いボードは送料ケチるために買ったシリアル変換アダプタです)

今回は外観やちょっとした比較をしてみたいと思います。

外観

インターフェースが初代のzero Wと変わらないこともあってパッと見は何も変わりませんが、CPUにRaspberry PiのロゴがあったりGPUヒートシンクが付いてるところは大きく違います。

手元にあったzero Wと比較するとこんな感じです。ある程度スペースに余裕があるケースであれば互換性がありそうなぐらいよく似てます。

今のところはピンヘッダが実装されてないので、GPIO使いたいときにはピンヘッダを何処かで買う必要があります。

火入れ

64ビットのCPUになったので、試しに64ビットのRaspberry Pi OSにインストールして動かしてみました。

初回の起動は多少遅いですが、なんとか動かせました。

あまりに長いのでマジで焦りましたw。

ベンチマーク

今度は性能がどんなもんなのか調べるためにベンチマークを採ってみます。

当然、zero 2 wだけで測定しても意味がないので比較のために今現在市場に出回ってなくもない以下の4台を比較してみます。

そしてベンチマークの測定には比較的簡単に使えるUnix Benchで軽く確認してみます。

以下のコマンドでインストール・実行します。

sudo apt install git
git clone https://github.com/kdlucas/byte-unixbench
cd byte-unixbench/UnixBench
./Run

上記コマンドをそれぞれのボードに対して行いました。その中から恣意的に選んだ5つの項目のIndex値についてグラフで出力しました。

各項目の内容はこんな感じです。(参考:https://blog.idcf.jp/entry/cloud/unixbench/)

  • Dhrystone 2 using register variables:整数演算処理の伝統的なベンチマークツール
  • File Copy:ファイルのコピー(ライト→リード)を繰り返すテスト
  • Pipe Throughput:データをパイプ処理しこれを繰り返し実行することでスループットをテストしている
  • Shell Scripts:sort、od、grep、tee、wcコマンドやパイプやリダイレクトを使ったテキスト処理を繰り返すテスト
  • System Call Overhead:プロセスIDを返す単純なシステムコールを繰り返し実行するテスト

そしてベンチマーク全体を通してのインデックススコアのグラフがこちらです。

やはりzero 2 Wは zero Wよりも圧倒的に性能が向上しているのはすごいですね!

まとめ

今回はRaspberry Pi zero 2 Wの動作確認も兼ねた開封レビューをしました。

Wifiは2.4GHzしか対応してなかったりしますがzero Wよりも多少性能が上がったので高度な処理を必要とするロボットに組み込むにはぴったりなんじゃないかなと思いました。

今のところ1個しかないのでもう1個入手したら今作っているロボットに組み込んでみてもいいのかなと思いました。

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センサーを使ってロボットを動かしてみようと思います。