Raspberry pi3とGPSモジュールを接続してNTPサーバを構築

# はじめに
趣味と実益を兼ねて、Raspberry pi3のNTPサーバ化に挑戦しました。まず第一段階として、Raspberry piにGPSモジュールを接続し、緯度・経度情報などを取得できるようにしました。そして、第二段階としてNTPの機能を導入し、Stratum1のNTPサーバを動かすことができました。

# 使用機材
- Raspberry pi3
- ブレッドボード
- ジャンパーワイヤー（オス-メス型）
- [GNSS受信キットAE-GNSS-EXTANT+ANT_SET](http://akizukidenshi.com/catalog/g/gK-13850/)


# 利用環境
```
pi@raspberrypi:~ $ uname -a
Linux raspberrypi 4.19.75-v7+ #1270 SMP Tue Sep 24 18:45:11 BST 2019 armv7l GNU/Linux
```
# 配線
[ここ](http://akizukidenshi.com/download/ds/akizuki/AE-GNSS_manual_r1.00s.pdf)の「外部機器との接続方法 （マイコンと接続する場合）」を参考にRaspbnerry piと接続すれば動作します。
Raspberry pi3のGPIOピンの図を参考に、5V同士、GND同士を接続し、Raspberry PiのTXにGPSモジュールのRXを、Raspberry PiのRXにGPSモジュールのTXを接続するようにしてください。
余裕があれば、後日図面を作成します。

# 設定変更＆コマンド入力
1. RaspberryにはOSインストールやキーボードなどの初期設定を行っておく
2. `sudo raspi-config`を入力
3. `5 Interfacing Options`を選択
4. `P6 Serial`を選択し、有効化
5. Escキーでコンソール画面に戻った後、下記のコマンドを入力

```terminal
sudo apt-get install gpsd gpsd-clients pps-tools
sudo timedatectl set-timezone Asia/Tokyo
sudo vim /boot/cmdline.txt #下記の設定例をもとに変更
sudo vim /boot/config.txt #下記の設定例をもとに変更
sudo vim /etc/modules #下記の設定例をもとに変更
sudo vim /etc/default/gpsd #下記の設定例をもとに変更
sudo systemctl status serial-getty@ttyS0.service #serial-getty@ttyS0.serviceの有効になっているか確認
sudo systemctl stop serial-getty@ttyS0.service
sudo systemctl disable serial-getty@ttyS0.service
sudo systemctl status serial-getty@ttyS0.service #serial-getty@ttyS0.serviceの無効になっているか確認
sudo reboot
```
上記の設定を終了した段階でGPSおよびPPSによるタイムパルスを受信することができる。

# 設定ファイル入力例

## /boot/cmdline.txt
`console=serial0,115200`の削除

```terminal
#dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=250d3bf4-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
dwc_otg.lpm_enable=0 console=tty1 root=PARTUUID=250d3bf4-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
```

## /boot/config.txt
`core_freq=250`と`enable_uart=1`、`dtoverlay=pps-gpio,gpiopin=18`を追記。
シリアルコンソールの通信速度をコアクロックを変更することで調整した。詳細は[ここの記事](https://qiita.com/komeda-shinji/items/03546ae1014c6b4edc92)で分かりやすく解説されている。
GPSモジュールの1PPSの配線を、Raspberry piの12番ピン（GPIO 18）に接続しているため、上記の記述を行う。
以前は拡張ピンであるGPIO 18をPPSドライバで使えるようにするため、カーネルをリビルドする必要があったようだが（[参考1](https://support.ntp.org/bin/view/Sandbox/HowtoPpsOnRaspberryPi)、[参考2](https://lowfreq.info/archives/18314)）、今回使用するRaspbianのバージョンではdtoverlayの文を記述すればよい。（前記の文献より、2014年以降に出たRaspbianは標準で1PPSの信号を受信できるようになっているらしいが、ソース元は未発見。いずれにせよ環境構築する際に最新のRaspbianを利用すれば問題ない）

```terminal
(省略)
[all]
#dtoverlay=vc4-fkms-v3d

core_freq=250
enable_uart=1
dtoverlay=pps-gpio,gpiopin=18
```

## /etc/modules
末尾に`pps-gpio`を追加

```terminal
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
pps-gpio
```

## /etc/default/gpsd
`DEVICES`にGPSモジュールを指定。
シリアル接続および1PPS信号の読み取りを行えるように設定。

`GPSD_OPTIONS`は`-n`を指定することで、NTPクライアントが一人もアクティブでない状態でも更新するように動作させられる（[ここ](https://gpsd.gitlab.io/gpsd/gpsd-time-service-howto.html)の「Running GPSD」の項目で確認可能）

```terminal
hub@iothub:~ $ sudo cat /etc/default/gpsd
# Default settings for the gpsd init script and the hotplug wrapper.

# Start the gpsd daemon automatically at boot time
START_DAEMON="true"

# Use USB hotplugging to add new USB devices automatically to the daemon
USBAUTO="false"

# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
DEVICES="/dev/ttyS0 /dev/pps0"

# Other options you want to pass to gpsd
GPSD_OPTIONS="-n"
```

# GPS起動およびタイムパルス受信確認
`gpsmon`または`cgps -s`でGPS情報が取れているか確認可能。
`gpsmon`でタイムパルスの受信確認が可能。

# NTPの設定
下記の手順でコマンドを投入。

```terminal
sudo apt-get install ntp
sudo vim /etc/ntp.conf
sudo reboot
```

/etc/ntp.confの設定例は以下の通り。

## /etc/ntp.conf

serverでgpsdが時刻を提供しているアドレスを、fudgeでハードウェア時計を指定する。poolは全てコメントアウトする。
fudge文にある「stratum」オプションで、参照先をどのstratumで扱うか設定できるため、`stratum 0`と指定すると参照先（GPS）をstratum0として扱うことができるため、それに同期するRaspberry piはstratum1のNTPサーバとして動作させられます。
また、`disable monitor`はNTP サーバの状態を確認する機能であるmonlistを無効化するオプションです。過去monlistを利用したDoS攻撃があったため、その対処として投入しています。[参考 : ntpd の monlist 機能を使った DDoS 攻撃に関する注意喚起](https://www.jpcert.or.jp/at/2014/at140001.html)

```terminal
# pool.ntp.org maps to about 1000 low-stratum NTP servers.  Your server will
# pick a different set every time it starts up.  Please consider joining the
# pool: <http://www.pool.ntp.org/join.html>
#pool 0.debian.pool.ntp.org iburst
#pool 1.debian.pool.ntp.org iburst
#pool 2.debian.pool.ntp.org iburst
#pool 3.debian.pool.ntp.org iburst
server 127.127.28.2 minpoll 3 maxpoll 3 true
fudge 127.127.28.2 refid SHM2 stratum 0
disable monitor
```

restrictで、NTPクライアントの所属するネットワークを指定する。Raspberry piは、このrestrictで指定されたネットワークとNTPで通信する。

```terminal
# Clients from this (example!) subnet have unlimited access, but only if
# cryptographically authenticated.
#restrict 192.168.123.0 mask 255.255.255.0 notrust
restrict 192.168.55.0 mask 255.255.255.0
```

# トラブルシューティング
## GPSモジュールから情報を取得できない
GPSモジュールからＧＰＳ情報を取得できず、時刻同期に失敗するケースが発生した。
`gpsd.socket`を確認すると、下記のようなログが出力された。

```terminal
pi@iothub:~ $ sudo systemctl status gpsd.socket
● gpsd.socket - GPS (Global Positioning System) Daemon Sockets
   Loaded: loaded (/lib/systemd/system/gpsd.socket; enabled; vendor preset: enabled)
   Active: failed (Result: resources)
   Listen: /var/run/gpsd.sock (Stream)
           [::1]:2947 (Stream)
           127.0.0.1:2947 (Stream)

Dec 01 00:27:23 iothub systemd[1]: gpsd.socket: Failed to create listening socket ([::1]:2947): Cannot assign requested address
Dec 01 00:27:23 iothub systemd[1]: gpsd.socket: Failed to listen on sockets: Cannot assign requested address
Dec 01 00:27:23 iothub systemd[1]: gpsd.socket: Failed with result 'resources'.
Dec 01 00:27:23 iothub systemd[1]: Failed to listen on GPS (Global Positioning System) Daemon Sockets.
Dec 01 00:34:08 iothub systemd[1]: gpsd.socket: Failed to create listening socket ([::1]:2947): Cannot assign requested address
Dec 01 00:34:08 iothub systemd[1]: gpsd.socket: Failed to listen on sockets: Cannot assign requested address
Dec 01 00:34:08 iothub systemd[1]: gpsd.socket: Failed with result 'resources'.
Dec 01 00:34:08 iothub systemd[1]: Failed to listen on GPS (Global Positioning System) Daemon Sockets.
```

gpsdでは基本的にIPv4とIPv6双方でソケットを生成し、ntpdに対してGPS情報を提供している。`gpsd.socket: Failed to create listening socket ([::1]:2947): Cannot assign requested address`とあるように、IPv6のリンクローカルアドレスをインタフェースで無効化すると、ソケット生成に失敗し、gpsdが異常終了してしまう。

そのため、`/etc/sysctl.conf`を編集し、IPv6アドレスをインタフェースで有効化すると、リンクローカルアドレスがいずれかのインタフェースに割り当てられ上記の問題が解決する。

## ppsの信号を受信しない
下記のような出力が表示される事象が発生。

```terminal
# ppstest /dev/pps0
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
time_pps_fetch() error -1 (Connection timed out)
time_pps_fetch() error -1 (Connection timed out)
time_pps_fetch() error -1 (Connection timed out)
```
また、`gpsmon`で確認すると、PPS・位置情報の欄に何も表示されていなかった。

結果としては、単にGPS信号を受信できていなかっただけだった。~~GPS信号の処理とPPS信号の処理は別々に動作してそうだったけれど、知識不足でわからないので、また調べようと思います。~~
[このサイト](https://network.oreda.net/knowledge/clock)を確認する限り、GPSから受信した信号をもとにPPSが生成されるため、上記の結果になるのは当然といえる。

# あとがき
Raspberry Piを再起動してから5～10分くらいしてから、NTPで正常に時刻同期できるようになります。Windowsマシンなどで時刻同期の確認が取れます。

また、NTPデーモンとしてchronyというものがあるのですが、apt-getコマンドを使ってインストールすると、勝手にntpdが排除（マスキング）されてしまうので注意してください。

（2019/10/26 追記）
chronyでも動作させてみました。詳しくは[ここ](https://qiita.com/RCA3610/items/0b3671cd272e430083d1)で。

# 参考サイト
- [Raspberry Piに「みちびき」対応GPSモジュールを接続](https://denor.jp/raspberry-pi%E3%81%AB%E3%80%8C%E3%81%BF%E3%81%A1%E3%81%B3%E3%81%8D%E3%80%8D%E5%AF%BE%E5%BF%9Cgps%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E6%8E%A5%E7%B6%9A)
- [Raspberry Pi起動時にGPSで現在時刻を設定(1PPS対応)](https://denor.jp/raspberry-pi%E8%B5%B7%E5%8B%95%E6%99%82%E3%81%ABgps%E3%81%A7%E7%8F%BE%E5%9C%A8%E6%99%82%E5%88%BB%E3%82%92%E8%A8%AD%E5%AE%9A1pps%E5%AF%BE%E5%BF%9C)
- [Raspberry PI Zero WでGPSのPPS信号に同期したntpサーバの構築](http://www.seekers.jp/?p=122)
- [4.NTPサーバーの立て方 - RaspberryPiで各種サーバー作り！ - ある阪大生の物置小屋](http://kassyjp.ninja-web.net/ras/jessie/ntp.htm)
- [/etc/ntp.conf のお勉強♪](https://oki2a24.com/2013/06/19/study-ntp-conf/)
- [gpsd + ntpdでStratum 1(GPSD NG版)](https://qiita.com/air-gh/items/7f38c449edaa86efa0f0)