ROSのために複数PC間で時刻同期する方法

# 時刻同期

ROS，とくにタイムスタンプを使った制御をしている場合(tfを使っているとか)で，制御PCが複数台ある場合，それらの間の時計があっていることが重要です．
そこで，確実に時計を合わせるために，NTPという仕組みを使います．

NTPはネットワークを利用して時計を合わせる一般的な仕組みで，Linuxだけでなく，windowsなどでも広く利用されているものです．
通常はインターネット上にある基準局と同期する設定になっているのですが，ロボットを動かすときにインターネットに繋がっていられない場合も多いため，独自に基準局を作ってその独自基準局に時計を合わせることにします．

# chrony

以前はntpdというもので時刻同期をしていましたが，今はchronyというより便利なものが出ているので，こちらを利用します．
chronyはNTPのサーバにもクライアントにもなれるソフトなので，まずはこれをインストールします．
chronyは，デーモンのchronydとクライアントソフトのchronycに分かれており，chronydを自動実行し，chronycで適宜操作したり情報表示したりすることになります．

インストールはaptで可能です．
aptでインストールすれば，下記で説明する設定ファイル類もインストールされます．

```
sudo apt install chrony
```

# 設定

```/etc/chrony/chrony.conf``` に設定ファイルがあり，これを編集して設定します．

方針としては，ロボットシステムのPC間での時刻同期を優先し，実時間への同期を目的にしないことにします．
そのため，ひとつの時刻の基準となるPCを決め，そこでNTPサーバを動かし，他のPCはクライアントとしてサーバに繋ぎに行く形で運用することにします．

基準となるPCはネットワークにさえ繋がっていればいいので，地面上に固定されているPCでも，roscoreが動いているロボット上のPCでも，なんでも構いません．

以下では必要最低限の説明のみとなります．
基盤となる情報はマニュアルを頼りましょう． https://chrony.tuxfamily.org/manual.html

## 基準局を作る

まず，複数PCのうち，基準となるPCを決めます．
インターネットに常時繋がるPCか否かで設定の方針が変わってします．

まず，基準局が信じる時計を設定します．

### インターネット上のNTPサーバを信じる

標準ではこの設定が有効になっています．
設定ファイルの上のほうに ```pool``` から始まる行があると思いますが，それがインターネット上のNTPサーバのアドレスがつまったものです．
```server``` から始まっている行も同様にネットワーク上の参照するサーバのアドレスが書かれています．
複数の参照先があると，どれかのサーバが落ちいていも同期が出来る反面，どれに同期するのかわからないため，ここでは一つのサーバだけ参照するようにします．
日本国内で信用に足る安定しているNTPサーバはNICTのものだと思うのでそれを使うことにします．

まず，上記の通りもともと設定ファイルに書かれている pool と server の行を消すかコメントアウトします．
chronyの設定ファイルは```#```を行頭に置くことでその行をコメントアウトできます．

次に設定ファイルに次の二行を追加します．
場所は基本的にはどこでも大丈夫です．

```
makestep 0.1 10
server ntp.nict.jp iburst prefer
```

makestep コマンドは，強制的に時刻を同期させるコマンドです．
通常のchronyは，もろもろのプログラムの誤動作を避けるために，ゆっくりと時間差を解消しようとしますが，ここの例のようにRTCが乗ってないコンピュータを同期したい場合などは，一気に大きな時間差を解消しないといけません．
それを実行するのがmakestepコマンドで，第一引数でmakestepする時間差を，第二引数で何回ステップ実行するか，を決められます．

### RTCを信じる

一般的なノートパソコンであれば，RTCというバックアップ電池を搭載した時計だけを動かすモジュールが乗っているため，電源がOFFの間も時計が進んでくれます．
ロボット制御用PC間の時刻を同期することだけが目的であれば，実際の時間と合わせる必要はないため，ある1台のPCのRTCを信じて，他のPCをそれに同期させに行く方針が建てられます．

chronyは標準では信頼度の低い時計しか参照できない場合には時刻を配信しないようになっています．
RTCは信頼度が低い時計に分類されるため，設定を変更して同期用時刻を配信するようにします．

```/etc/chrony/chrony.conf``` に次の一行のコメントを外します．

```
local stratum 10
```

stratum というのが時刻の信頼度のようなもので，値が小さいほど高信頼度の時計を参照にしています．
下記で扱うようなGPSや原子時計を参照している場合にstratumは1になります．

### GPSを使う

GPSは位置計算をするために非常に精度の良い時刻を知っています．
そこでそれを利用して正しい時刻に合わせる手法があります．
これを使えば，例えばRaspberryPiのようにRTCを搭載していない組み込みボードでも正しい時刻を得ることができるようになります．

GPSを使ったchronyの設定は,ネット上にいくつも情報があるのでここでは割愛します．
※気が向いたら自分用にまとめを書くかも．

### ROS ntp_driver を使う

[ntpd_driver](http://wiki.ros.org/ntpd_driver) は，[TimeReference](http://docs.ros.org/melodic/api/sensor_msgs/html/msg/TimeReference.html)メッセージを読んでNTPの同期用通信をしてくれるパッケージです．
例えばGPSを使った位置制御をするロボットでは，前述の通り正しい時刻を知ることができるため，その時刻をTimeReferenceに投げておくと，このパッケージでPCの時刻を正しく設定することができます※1．
UAVの制御によく使われるmavrosパッケージでは，GPSによる時刻を標準でTimeReferenceに投げてくれるため，そのまま利用することができます．

ntp_driver は，chronyが受け取れるシェアードメモリに時刻データを書き込むところまでをやってくれるノードなので，前述のGPSを使う場合と同様にサーバ側のchronyを設定する必要があります．

※1　ROSでは，NavSatFixメッセージのタイムスタンプヘッダはGPSの出力するNMEAのタイムスタンプではなく，そのPCのタイムスタンプを利用することを推奨してます．
その代わり，正しい時刻をTimeReferenceに投げろ，と書かれています．

### 配信の許可

信じる時計を決定したら，配信を許可するクライアントのアドレス設定をします．
設定ファイル中の```allow```で始まる行がアクセスを許可するアドレスになります．
IPv6とIPv4の設定をそれぞれ出来るようになっていますが，ROSなのでIPv4の設定だけ記述すればよいです．

```
allow 0/0
```

の行が有効になっていると，すべてのIPv4アドレスからの接続が許可されます．
ローカルネットワークでしか使わないロボットならこれでもいいかもしれませんが，セキュリティを考えると許可範囲を絞ったほうがいいかもしれません(どれくらいの脅威になるんだろう?)．
その場合は

```
allow 192.168.0/24
```

のようにすると，192.168.0.*のアドレスを持つものからの接続のみを許可することになります．
このように```IPアドレス/マスク長```の形式で設定をします．

## クライアントを作る

基準局を参照しにいくクライアント側もchronyの設定だけで作ることができます．

基本的には，基準局側のインターネット上の時計を信じるの項で説明した内容を設定します．
参照先のサーバのアドレスをNICTのものではなく，自分が作った基準局のアドレスにするだけです．

# 動かし方

aptでインストールしていれば，systemdの設定ファイルができ，自動実行されるようになっているため，自分で実行する必要はありません．
ただ，設定を読み込ませたりするためには再起動したり，動作確認のめたに止めたり動かしたりする必要もあります．
それぞれsystemctlのオプションで次のとおりになります．

```
sudo systemctl restart chrony.service
sudo systemctl start chrony.service
sudo systemctl stop chrony.service
```

動作はサブコマンドの通りです．
設定ファイルを書き換えた場合はreloadではなくrestartが必要なようです．

基本的にどれもTab補完がききますが，例えばstartのあとにchrまで打ってTab補完が効かない場合などはすでにchronyが起動中の場合があります．

# 動作確認

```
chronyc sources
```

上記のコマンドで参照している時計をリストアップします．
リストの左が蓋文字目が?ではなく*になっていれば同期ができています．

## サーバ側の確認

```chronyc sources``` でNTPサーバやGPSのシェアードメモリが表示され，同期状態になっていればOKです．

```
sudo chronyc -a clients
```

で接続してきているクライアントの一覧を得ることができます．
クライアントを設定したのにうまく動いていない場合などに確認ができます．

## クライアント側の確認

```chronyc sources```で，自分が用意した基準局のアドレスのみが表示され，同期状態になっていればOKです．
そうでない場合は，そもそもネットワークが通じているか，NTPに必要なポートが経路上で塞がれていないかチェックします．

# 自動起動と組み合わせる
 
ROSを自動起動する設定を書いている場合，tfを使い始める前に時計がそろっていたほうが不具合無く動きます．
そこで，systemdのroslaunch設定で，時刻が同期したことを確認する項目を追加します．

同期の確認には```chronyc waitsync```を使います．

```
chronyc waitsync 1
```

waitsyncを引数なしで使えば，同期が完了するまで無限に待ちますが，第一引数に確認する回数を指定することもできます．
上記例では1回だけ確認するようにしているため，即座にコマンドが終了します．
この時，同期が確認できなければ戻り値に非ゼロが渡されるので，systemdで利用できます．

つまり，roslaunchするsystemdの設定ファイルに次の行を追加します．

```
ExecStartPre=/usr/bin/chronyc waitsync 1
```

ExecStartPreは複数書くことができ，それのすべてが成功したときにExecStartされるので，上記一行を追加するだけで利用できるはずです．