GPSでStratum1なNTPサーバを作る

RaspberyPiとGPSユニットを使って、Stratum 1なナノ秒単位で調整できるNTPサーバを作ってみた。

情報は色々とネットに落ちているけど、できるだけ簡素な工作で、簡易的な構築で、長期的な運用ができると思われる方法で作ってみる方針。

グラフ(NTP offset / NTP time difference)

材料

  • Raspberry Pi 4B
    • それと運用に必要なACケーブルやらケースやらmicroSDHCカードやら。
  • GPSユニット YIC GT-902PMGG
    • https://akizukidenshi.com/catalog/g/gM-12905/
    • アンテナ一体型でCOM(UART)出力とPPS出力があるGPSユニット。PPS出力とは”Pulse Per Second”、1秒ごとに信号が出力される端子のこと。この信号を拾ってナノ秒単位で時刻を調整する。
    • ケーブルの先端は端子加工されていない仕様だったのでハンダ付けが必要。
      赤=Vcc 橙=Tx 緑=Rx 茶=PPS 黒=GND。駆動電圧はVccは3V、Rx,Txは3.3V、ラズパイにちょうど良い。
    • シリアル通信のボーレートはデフォルトで9600bps。これを115200bpsあたりに上げないと、衛星を大量に捕捉しすぎたときにデータ転送が追いつかなくなってgpsdがバグる仕様あり。
    • ケーブルが1.5mしかないので、屋外まで引っ張る場合は5線以上のケーブルをハンダ付けして延長しても良い(精度はともかく)。
    • ついでにラズパイのピンヘッダをケース外まで延長して、ケース外で接続できるように実装する方が便利。
  • コネクタ付ケーブル 20cm 40P オスオス
  • コネクタ付ケーブル 20cm 40P メスメス
  • FTDI USBシリアル変換ケーブル (3.3V)
  • u-center (GNSS evaluation software for Windows)

準備

Raspberry Pi側のGPIOピンヘッダと、GPSユニットの結線

RaspberryPi 4BとGPSユニット YIC GT-902PMGGの場合

  • 1または17ピン(3.3V) – (Vcc)
  • 8ピン(UART_TX) – (Rx)
  • 10ピン(UART_RX) – (Tx)
  • 12ピン(GPIO18) – (PPS)
  • 14ピン(GND) – (GND)

参考: https://deviceplus.jp/raspberrypi/raspberrypi-gpio/ (ページ途中にRaspberryPi 4BのGPIOピンヘッダ配置図がある)

RaspberryPiのピンヘッダ引き出し

GPSユニットにメスピンケーブルを直接ハンダ付けしても良いが、メンテナンスのためにケース外で接続できた方が便利なので。

RaspberryPiと外出し用メスピン5Cケーブル
RaspberryPiと外出し用メスピン5Cケーブル

GPSユニットの配線加工

GPSユニットと、オスピンコネクタケーブルをハンダ付けして、RaspberryPiから引き出したメスピンケーブルに接続できるようにしておく。

GPSユニットとオスピン5Cケーブルをハンダ付け
GPSユニットとオスピン5Cケーブルをハンダ付け

GPSユニットの設定変更

シリアル通信のボーレートを9600bpsから115200bpsあたりに上げないと、衛星を大量に捕捉しすぎたときにCOMポートの通信が追いつかなくなってgpsdがバグる。
コレの仕様のせいで、起動直後は動作するのに1日ほどでバグってしまい、3日ほど悩んだ。

  • FTDI USBシリアル変換ケーブルでPCのCOMポートに接続
    • この時点ではまだボーレートは9600bpsのまま。
  • u-centerで接続
    • Receiver→Connection→COMn(PCに認識されているCOMポート)
    • モニタ画面に衛星や地図のプロットが動き出す
  • ボーレートの設定変更
    • View→Messages View
    • 左ペインで UBX→CFG→PRT
    • Poll
    • 右ペインで Target: 1 – UART1, Baudrate: 115200
    • Send
  • ボーレートを変えて接続
    • Receiver→Connection→Disconnect
    • Receiver→Baudrate→115200
    • Receiver→Connection→COMn
    • モニタが動き出すか確認
  • BBR(不揮発性メモリ)に書き込み
    • View→Messages View
    • 左ペインで UBX→CFG→CFG
    • 右ペインで Save current Configuration
    • Send

Raspberry Pi OS

今回はRaspberry Pi 4Bを使用した。

Raspberry Pi OS Lite 64bitを、SSH有効にしてmicroSDに書き込み。

構築

Raspberry Pi OSの設定

PCからTeraTermProでSSH接続して設定。

なお、お好みによってご自由に。

# Raspberry Piのファームウェアアップデート
sudo rpi-eeprom-update

# Raspberry Pi OSのパッケージソフトウェアアップデート
sudo apt update
sudo apt -y dist-upgrade
sudo apt -y autoremove
sudo apt autoclean

# スワップファイルの無効化
sudo dphys-swapfile swapoff
sudo apt remove dphys-swapfile
sudo apt autoremove

# Raspberry PiとOSの設定
sudo raspi-config

  • System Options → Boot / Auto Login → Console
  • Display Options → Screen Blanking → No
  • Interface Options → Serial Port → No → Yes
  • Localisation Options → Timezone → Asia → Tokyo
  • Localisation Options → Keyboard → Generic 105-key (Intl) PC → Other → Japanese → Japanese → The default for the keyboard layout → No compose key

#VIMのキーバインド設定
vi .vimrc

set nocompatible
set backspace=indent,eol,start

# rootユーザー用VIMキーバインド(.vimrcと同じ内容)
sudo vi /root/.vimrc

set nocompatible
set backspace=indent,eol,start

# IPV6無効化
sudo vi /etc/sysctl.conf

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

# ネットワーク設定の変更(IPアドレスの設定が必要な場合)
sudo vi /etc/dhcpcd.conf

interface eth0
static ip_address=192.168.0.10/24
statoc routers=192.168.0.1
static domain_name_servers=192.168.0.2 192.168.1.2

# SSHの設定
sudo vi /etc/ssh/sshd_config

PermitRootLogin no

# DNS関係ツール(digやtracerouteなど)
sudo apt install dnsutils

gpsd

gpsdがGPSユニットとシリアル通信を行い、PPS(秒信号基準の補正)やSHM(タイムコード基準の補正)ポートを作ってくれる。

参考

sudo vi /boot/cmdline.txt

#console=tty1 console=serial0,115200 root=PARTUUID=478e1abe-02 rootfstype=ext4 fsck.repair=yes rootwait
console=tty1 root=PARTUUID=478e1abe-02 rootfstype=ext4 fsck.repair=yes rootwait
  • ちなみにcmdline.txtのオプションにipv6.disable=1を入れるとIPv6を完全に殺すことができるが、なぜかchronyがバグるので入れていない。

sudo vi /boot/config.txt

#dtoverlay=vc4-kms-v3d
#max_framebuffers=2

[all]
dtoverlay=pps-gpio,gpiopin=18,assert_falling_edge=true
dtoverlay=disable-bt
enable_uart=1
core_freq=250
  • core_freq指定しなくても動いているけどとりあえず設定しておいた。

# gpsdインストール
sudo apt install gpsd gpsd-clients pps-tools
# GPIOでPPSを受けるモジュールを組み込み
sudo vi /etc/modules

pps-gpio

# 諸々の設定反映のため再起動
sudo reboot

このあたりでGPSユニットを接続しておくと良い。

# gpsdの設定
sudo vi /etc/default/gpsd

#DEVICES=""
DEVICES="/dev/ttyAMA0 /dev/pps0"

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

# Automatically hot add/remove USB GPS devices via gpsdctl
#USBAUTO="true"
USBAUTO="false"

START_DAEMON="true"
  • raspi4はBluetoothを使用しないとttyS0ではなくttyAMA0になるみたい。
  • GPSD_OPTIONSのsオプションでシリアル通信のボーレートを 115200bpsに変更している。
    stty -F /dev/ttyAMA0 115200 でボーレートを設定してもgpsdが9600に戻してしまうため。

sudo vi /lib/systemd/system/gpsd.socket

#ListenStream=[::1]:2947

SocketMode=0600
#SocketMode=0666
#BindIPv6Only=yes
  • SocketModeは666に設定した方が良いとの情報があったが、設定せずに動いているのでそのままにしている。

# gpsdの自動起動と起動
sudo systemctl daemon-reload
sudo systemctl enable gpsd
sudo systemctl start gpsd
sudo systemctl enable gpsd.socket
sudo systemctl start gpsd.socket

sudo ppstest /dev/pps0
sudo gpsmon -n

NTPサーバ(chrony)

Raspberry Piにデフォルトでインストールされているsystemd-timesyncdはクライアント機能しか無いので、chronyに置き換える。
ちなみにRaspberryPiには内部保持時計がなく、毎回起動時に外部から時刻を取得しないといけない。

chronyがgpsdのPPSやSHMポートを参照して時刻補正を行ってくれる。
なお、chronyをインストールすると自動的にsystemd-timesyncdはアンインストールされる。

# NTPクライアントを停止
sudo systemctl disable systemd-timesyncd
sudo systemctl stop systemd-timesyncd

# NTPサーバ(chrony)インストール
sudo apt install chrony
sudo vi /etc/chrony/chrony.conf

#pool 2.debian.pool.ntp.org iburst
pool NTP.JamFunk.jp noselect
pool ntp.nict.jp

#log tracking measurements statistics
log tracking measurements statistics rtc refclocks tempcomp

refclock PPS /dev/pps0 lock GPS refid PPS precision 1e-9 offset 0 poll 2
refclock SHM 0 refid GPS precision 1e-1 offset 0. poll 2
  • poolやserverで、もし自前で立てたGPS NTPを相互に参照する場合は、noselectオプションを後ろに付けて参照しないようにすること。
    • 捕捉しなくなったり誤差が大きくなりすぎた場合は、自動的に他のNTPサーバを参照する動きになっている。
    • お互いに捕捉しなくなった場合は、相互に参照しても仕方ないので。
  • logとlogdirで、/var/log/chronyに詳細な履歴が落ちてくる。
  • SHM(GPS)とPPSのrefclockのoffsetで大まかな測定誤差を修正する。
    • sudo gpsmon -nでGSA+PPSのTOFF値とPPS値を調べる。
    • TOFFが0.055xxxxxxならGPSをoffset 0.055あたりで設定する。
      TOFFの値はGPS捕捉情報(文字列)が送られてくるので誤差がどうしても大きくなってしまい、ms単位になっているはず。
      しかも捕捉数が変われば捕捉情報の量も変わるのでどうしても設定した後も変動してしまう。
      よって、本来であればほぼ1日監視するかログの統計を取って、捕捉の変動がどのくらいあるか見ないといけないらしい。
    • PPSのoffsetを調整する。
      -0.899999なら-0.900あたりで設定する。
      代替機器のタイミングでSHMほど変動はないはず。多分。
    • sudo chronyc sourcesで、PPSとGPS(SHM)のadjust offsetがus単位とms単位まで近づいていることと、PPSに*が付いていることを確認。
      数日監視してみること。
      後述のZabbixのchronyテンプレートで、referenceが変化したらトリガー発動を仕込んだら変動に気づきやすいかも。

# chrony設定反映
sudo systemctl restart chrony
sudo systemctl status chrony
# chrony参照先状況
chronyc sources -v

snmp

SNMP経由でサーバのチェックを行うならば。

sudo apt install snmpd snmp
sudo vi /etc/snmp/snmpd.conf

sysLocation Studio JamPack DCJn
sysContact support_at_jamfunk.net

#agentaddress 127.0.0.1,[::1]
agentaddress udp:161

#view systemonly included .1.3.6.1.2.1.1
#view systemonly included .1.3.6.1.2.1.25.1

#rocommunity public default -V systemonly
#rocommunity6 public default -V systemonly

#rouser authPrivUser authpriv -V systemonly

com2sec sec_all localhost snmp_all
com2sec sec_all 192.168.0.0/24 snmp_all

group grp_all v1 sec_all
group grp_all v2c sec_all
group grp_all usm sec_all

view view_all included .1 80

access grp_all "" any noauth exact view_all none none

disk /

sudo systemctl enable snmpd
sudo systemctl start snmpd

Zabbix Agent

chronyの参照状況をZabbix Agent経由でZabbix Serverに送るなら。

wget https://repo.zabbix.com/zabbix/5.0/raspbian/pool/main/z/zabbix-release/zabbix-release_5.0-2+debian11_all.deb
sudo dpkg -i zabbix-release_5.0-2+debian11_all.deb
sudo apt update
sudo apt install zabbix-agent

sudo vi /etc/zabbix/zabbix_agentd.conf

AllowKey=system.run[chronyc *]

Server=192.168.0.2  #Zabbix Serverのアドレス

ListenPort=10050

StartAgents=3

ServerActive=192.168.0.2  #Zabbix Serverにアクティブでデータを送るなら

Hostname=NTP-DCJn

sudo systemctl enable zabbix-agent
sudo systemctl start zabbix-agent

Zabbix Server用chrony監視テンプレート

Zabbix ServerからZabbix Agentのホスト経由でchronyの状況を取得するためのテンプレート

使用中のZabbix ServerのバージョンのXMLをダウンロードして、Zabbix Serverにテンプレートとしてインポートする。

ホストの設定をする。

グラフ(NTP offset / NTP time difference)
グラフ(NTP offset / NTP time difference)
最新データ画面
最新データ画面
chrony sources
chrony sources