Bash
screen
ROS
systemd
ROSDay 4

ROSを自動起動する

ROS を自動起動にする

ROSって実際に動かし始めようとなると、いくつもの端末を開くことになりがちじゃないですか?
ROSを使っている皆さんは、主にロボット開発をしている方かと思いますが、いくつもの端末を操作しないと動かないロボットってなかなか面倒だと思ってしまいません?
特に自分は屋外で動くロボットの開発をメインにしているので、ロボットを起動させる度に端末を開くなんてやってられないことが多いんです。
そこで、ロボットに搭載した制御用PCが起動したときに、自動的にROSも起動するように設定しています。
今回はその設定について共有しつつ他の方の方法とかコメントでしれたらいいなぁなんて思ってます。
ちなみに,従来よく使われていた /etc/rc.local は新しい Ubuntu では起動シーケンスの最後に実行される保証がなくなったようなので,ROSを自動起動するにはおすすめできません.

ここで紹介する設定のポイントは、

  • 普段のROS環境に手を加えない
  • 自動起動後でも端末で状況を確認できる
  • Ubuntuの標準的な仕組みをつかう
  • できるだけ余分なスクリプトとか書きたくない

あたりになります。

地味に必要知識が多いので、こまごまと説明が入ってしまい読みにくくなっていると思いますがご勘弁を…

前提

自分が動作確認している環境は下記のとおりです。
systemdを利用するので、Ubuntuがこれより古いと面倒かもしれません

  • Ubuntu 16.04
  • ROS kinetic
  • ログインシェル Bash

また、説明上必要な環境依存なものは下記のとおりで説明します。

  • ユーザ名 robo
  • ホームディレクトリ /home/robo/
  • ROSのsetupスクリプト /home/robo/catkin_ws/devel/setup.bash

自動起動化する

概要

  1. 必要なものをインストールする
  2. ROSの設定を移動する
  3. systemdの設定を書く
  4. テストする
  5. 自動起動化する

必要なものをインストールする

起動後に端末にアクセス出来るようにするため、GNU screenを利用します。

sudo apt install screen

ROSの設定を移動する

通常ROSインストール時にインストールマニュアルにかかれている通りに設定していれば、、ROSの環境変数は、ホームディレクトリ以下の .bashrc ファイルにかかれた次の一行で設定されます。

source /opt/ros/kinetic/setup.bash

開発用にcatkin ワークスペースを持っている場合は同様にして .bashrc に

source /home/robo/catkin_ws/devel/setup.bash

を追加しているかと思いますが、今回はこの行を別のファイルに移動します。

.bashrc は、端末でログインしたときに読み込まれるbashの設定ファイルですが、自動起動時は実際に端末でログインするわけではないので、この設定が読み込まれません。
そのような場合のために、 .bashrc ではなく、 .bash_profile というファイルを置いておくと、そちらが読み込まれるようになります。(参考 : .bash_profile ? .bashrc ? いろいろあるけどこいつらなにもの? )
そこで、次のような内容の .bash_profile ファイルを作ります。

source ${HOME}/.bashrc
source ${HOME}/catkin_ws/devel/setup.bash

export ROS_MASTER_URI='http://localhost:11311/'
export ROS_IP=${hostname -I}

注意点は、 .bash_profile があると、端末ログイン時に .bashrc が読み込まれなくなってしまうので、 最初に .bashrc を読むようにする必要があることです。
また、 .bashrc 内にかかれている、端末の設定をするスクリプトがこける場合があるので、ファイルの最後まで実行されない可能性があります。
その対策として、 .bashrc の末尾に追加されている source の行を .bash_profile に移動します。

また、その他に必要なROSの環境変数をその後で設定します。
ここでは、リモート上の他のroscoreとつなぐことも考えて、IPの設定をするようにしている例です。

systemdの設定を書く

Ubuntu 16.04 では、起動時に systemd という仕組みを利用して、さまざまな依存関係などを解決しつつ、プログラムを起動していきます。
今回は、その systemd で ROS を起動できるようにします。

がんばって一つの launch ファイルに記述して、一つの systemd サービスですべてが起動するようにしてもよいのですが、一部のノードだけエラーがあった場合などに、すべて再起動する必要が出てしまうのですこし不便です。
そこで、 launch ファイルは機能単位で分かれたままにし、複数の systemd 設定を書くことにします。

systemd では、udevと連携して、デバイスの起動を条件にすることもできるため、例えばセンサ接続用のUSB-シリアル変換デバイスの接続を確認してから launch する、といった処理が可能になります。
このことからも、 launch をわけておくと、依存関係もわかりやすくなるかと思います、

systemd の設定ファイルは、 /etc/systemd/systerm/ 以下に置くのが一般的なようです。

roscore の自動起動化

まず、 roscore を単体で起動する設定ファイルを用意します。
他のPCと通信しているときに roscore を再起動してしまうと、他のPC側のノードも再起動が必要になってしまい面倒です。
そのため、 roscore は単独で起動させておき、問題のあったノードだけを再起動出来るようにします。

[Unit]
Description=ROSCORE launcher
After=networking.service
Requires=networking.service

[Service]
User=robo
ExecStart=/usr/bin/screen -S roscore -ADm bash -l -c 'roscore'
Type=simple
Restart=always
RestartSec=1s

[Install]
WantedBy=multi-user.target

簡単に説明すると、

  • Description : このサービスの説明。
  • After : 起動順序の依存関係。ROSはネットワーク(≠インターネット)が生きてないと使えない。
  • Requires : これが必要という依存関係。≠順序なので注意。
  • User : 自動起動化するPCのユーザ名。ログインシェルを使えるようにする。
  • ExecStart : 実際に実行するコマンド。問題があるときは、これを単独実行して問題ないか確認する。
  • Type : 実行するコマンドの動作に応じて適切に選ぶ。simpleはコマンド終了時に端末が帰ってくる普通のコマンド。
  • Restart : ExecStartしたコマンドが終了した時の再起動設定。roscoreは生きてて欲しいので必ず再起動をかける。
  • RestartSec : 再起動するときのウェイト。

となっています。

これを roscore.service などとして、 /etc/systemd/system/ 以下に保存します。

その後、次のコマンドで起動することができます。

sudo systemctl start roscore

なお、systemdの設定ファイルを書き換えた後は、リロードが必要になります。

sudo systemctl daemon-reload

テストする

起動後は、

systemctl status roscore

で状態が確認できます。

もし、Failedと出ている場合は、

jounalctl -xe

とすることで(ある程度)詳細なエラーを確認出きるので、ミスを探します。

正常に起動できていれば、screenでroscoreが起動しているはずなので、次のコマンドで確認できます。

screen -ls

実行結果は次のような感じだと思います。

There is a screen on:
    3786.roscore    (02/12/2016 02:23:09 AM)    (Detached)

ここに、設定で書いた

/usr/bin/screen -S roscore

-S オプションの部分の名称が一覧にあればOKです。

一覧にある場合は、

screen -r roscore

でアタッチ可能です。
アタッチすると、普通にターミナルで起動したようにroscoreのメッセージを読むことができます。
アタッチ後、再びデタッチするときは、端末上で C^a C^d と入力すればOKです。

自動起動化する

有効化

テストが問題なければ、自動起動を有効にします。

sudo systemctl enable roscore

設定ファイルの [Install] に何も記述がないと enable しても自動起動しないので注意してください。

無効化

次のコマンドで自動起動しなように設定可能です。

sudo systemctl disable roscore

止め方

なにか問題があった場合や、設定などを書き換えたい場合は、起動しているものを止めたくなるかと思います。
ここで紹介した方法では、終了時に強制的に再起動してしまうため、roscoreをkillしても再起動します。

そのため、systemdのコマンドを使って終了させます。

sudo systemctl stop roscore

その後、起動させたいときに最初に書いたstartで実行すればOKです。

以上で基本の説明はおしまいです。
後は、同じようにしてroslaunchも自動実行化することができますが、いくつかまだ小ネタがあるので例を紹介します。

roslaunch の systemd 設定例

ここでは、mavrosパッケージのpx4.launchを起動する例を書きます。

[Unit]
Description=mavros px4 launcher
After=roscore.service dev-ttyUSB0.device
Requires=roscore.service dev-ttyUSB0.device

[Service]
User=robo
ExecStartPre=/bin/bash -l -c 'rostopc list'
ExecStart=/usr/bin/screen -S mavros -ADm bash -l -c 'roslaunch mavros px4.launch'
Type=simple
Restart=always
RestartSec=1s

[Install]
WantedBy=multi-user.target

このファイルを px4.service として保存し、

sudo systemctl start px4

などのコマンドでスタートしたりストップしたりはroscoreで紹介したのと同じです。

mavros は、mavlinkというUAVの制御向けプロトコルのブリッジ+便利ライブラリ群のパッケージで、px4.launchは、UAVの制御用コントローラと接続すためのノードです。
制御用コントローラはUSB-シリアル変換デバイスで接続する必要があるため、ノードを起動する前に、デバイスの接続が完了していないとlaunchできません。

そこで、AfterとRequiresにデバイス名を記入しています。
この様に、systemdではudevと連携してデバイス名を使った起動順序・依存性を記述できます。

ssytemdで利用可能なデバイス名については、

systemctl --t device -a --full

で確認することが出来ます。
基本的には、通常のパスのスラッシュをハイフンに置き換えたものとなります。

次に、 ExecStartPre で、ExecStart前に実行するコマンドを指定しています。
roscoreが起動していない状態でroslaunchすると、勝手にroscoreも起動してしまいます。
roscore.serviceをAfterに入れていることで、roscoreのサービスは事前に起動することが保証されますが、roscoreそのものががきちんと起動し終わっている保証がなく、roscoreが起動しおわったことを別の方法で確認する必要があります。
また、リモートPCでroscoreを起動しておいて接続しにいくような場合では、依存関係にroscore.serviceを書けないため同様に必要となります。

それが、ExecStartPreにかかれている、rostopic listです。
rostopic listは、roscoreが起動していないと、エラー終了するので、それを使ってroscoreの起動を検出しています。

この部分については,ExecStartroslaunch--waitオプションを与えることで,roscoreの起動を待つ方法もあります(コメントで教えていただきました).
ただ,自分の環境でroscoreだけ立ちあげ,他のすべてのlaunchをwaitにするとroslaunchが途中でフリーズしてしまう現象が発生してしまいました.
そのため,現状でも上記のExecStartPreでroscoreの有無を監視しています.

以上のようにlaunchファイルについてもサービスを記述できるので、同様にして必要な数のサービスを書いて、それぞれを自動実行有効にしたり、止めたりすればよいです。

ほか細かいこと

ROSの環境変数

ROSは、いくつかの環境変数を利用します。
特にここで重要なのは、ROSのパッケージインストール先を示す ROS_PACKAGE_PATH で、これが正しく設定されていないと、ROSはパッケージを探すことが出来ません。

これらの環境変数は、取説にかかれている source /opt/ros/kinetic/setup.bash を実行することで設定されます。

また、開発している catkin ワークスペースにある setup.bash を読み込ませることで、パッケージのパスが追加されます。

catkin ワークスペースにある setup.bash は、ワークスペースを作った後、初めて catkin_make した時のROS環境変数を保持します。
また、 setup.bash は、source時に゛設定されている環境変数を上書きしてしまうので、複数のcatkinワークスペースを利用している場合には、初回の catkin_make をした順序が重要になります。
依存関係があるパッケージ同士が違うcatkinワークスペースにある場合は注意が必要です。

この、catkinワークスペース化のsetup.bashで設定される環境変数を再設定するためには、catkinワークスペースの builddevel ディレクトリを削除して、もう一度catkin_makeする必要があるようです。

そのため、複数のcatkinワークスペースを使っていてる場合は、次のような手順でsetup.bashを初期化しておく必要があります。

source /opt/ros/kinetic/setup.bash
echo $ROS_PACKAGE_PATH
cd catkin_ws1
rm -rf build devel
catkin_make
source devel/setup.bash
echo $ROS_PACKAGE_PATH
cd ../catkin_ws2
rm -rf build devel
catkin_make
source devel/setup.bash
echo $ROS_PACKAGE_PATH

echo $ROS_PACKAGE_PATH でパスが追加されているのが分かると思います。
逆に、 最後の状態から もう一度 source ~/catkin_ws1/devel/setup.bash してから ROS_PACKAGE_PATH を確認すると、パスが減ることも分かるとかと思います。

よって以降は、 catkin_ws2setup.bash のみを source するようにします。
.bash_profile に移した source についても同様です。

systemd

Systemdはここで紹介した以外にも多くの設定が可能なので、いろいろなドキュメントを読んでみてください。

自分はここをよく見てます。 https://www.freedesktop.org/software/systemd/man/systemd.service.html

screen

GNU screenも紹介した機能はごく一部ですので、他のドキュメントも参考にしてください。
とくに、操作方法は一部独特なので、あらかじめ確認しておくことをおすすめします。

今回紹介した内容であれば、ここのリンク先の説明がよいかと思います。 https://qiita.com/mgoldchild/items/e336618487eb7d90f6d4

その他

USB-シリアル変換のポート指定方法

上の例では、 /dev/ttyUSB0 という指定方法をしましたが、自分はあまりこの方法は使いません。
このポート番号は、基本的には認識した純で割り振られていくため、必ずしも起動時に同じ番号が振られる保証がないからです。
udev ルールを設定することで、デバイスと紐付けすることも可能ですが、同じUSB-シリアル変換デバイスを複数使おうと思ったときに、シリアル番号がすべて同じなデバイスも多く、VID、PIDからはデバイスを決定することができません。

そこで、自分は /dev/serial/by-path/ 以下を利用しています。
このディレクトリの下には、USB-シリアルデバイスへのシンボリックリンクが入っているのですが、その名前はUSBポートの物理的な接続位置に依存したものになります。
一度ロボットを組み上げて自動起動化したいという完成度のロボットなら、USB-シリアル変換デバイスをやたらめったら入れ替えることは少ないと思うので、ポート位置指定にしておくと間違いがないです。
また、USB-シリアルデバイスを違うものに交換したり、同じロボットを何台も作る場合についても、同じ設定のまま利用できるため便利です。

Read Only化

起動が自動化できるんなら終了も…と思うのが人情というものです。
自分のところでは、ロボット上のPCはすべてのファイルシステムをリードオンリー化し、OverlayFSを使ったRAMディスク上で動作するようにしています。
こうすることで、ファイルシステムに書き込みが発生しなくなり、電源をぶつ切りしてもファイルシステムが壊れないため、一応は問題なくなります。
とくにRaspberryPi程度であれば問題なく電源ぶつ切りシステムができるようになるので、ROSが動くロボットであっても、電源を入れたら起動し、終了時は電源を物理的にするだけでOKという構成ができます。
もっとまともなPCを使ってる場合は、ぶつ切りしてしまうと熱の問題とかもあるのであんまりおすすめしません。