Raspberry Pi Zero WHでAE-GYSFDMAXBを使用したgpsd+ntp server

 ようやく追い込めてきました。Raspberry Pi Zero WHにAE-GYSFDMAXBを接続してGPSロガー(車載)とNTP Server(据置)両方を試作しているのですけどそれぞれで問題がありログが途切れたりoffsetやjitterが安定しなかったり、一見良さそうでも情報通信研究機構(以下NICT)やmfeedから取得できるNTP経由での日本標準時から10msec以上乖離したり…。一応、収束してきましたのでメモを公開します。多くのWebサイトを参考にしました。全ては紹介しきれませんけど感謝しています。

 まず、現状の据置Raspberry Pi Zero WHでの状況です。ここまで追い込むのにカレンダー上1ヶ月以上掛かっています。1PPSの信号をいかに正確に取り込むかが精度向上の鍵になっています。GNSSモジュールの受信状況が悪化や故障してもインターネット接続が途絶してもできる限り正確な時刻を維持し続けることを目指しています。

$ ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*SHM(2)          .SHM2.           1 l    3    8  377    0.000   -0.019   0.004
 LOCAL(0)        .LOCL.          10 l  879   64    0    0.000    0.000   0.000
+ntp2.jst.mfeed. 133.243.236.17   2 u   15   64  377   22.029    1.784   0.535
+ntp1.v6.mfeed.a 133.243.236.17   2 u   21   64  377   22.772    0.150   1.079
-ntp2.v6.mfeed.a 133.243.236.17   2 u   22   64  377   20.671    1.789   0.723
-ntp3.v6.mfeed.a 133.243.236.17   2 u   24   64  377   22.118   -0.299   1.029

 無線LANの区間があるためどうしてもdelayが大きくなっています。IPv4, IPv6での有意差は無さそうです。NICTを外しているのは負荷に制限(1時間平均で20回)があるためです。GNSSモジュールからのoffsetとjitterが一気に小さくなったのは/etc/ntp.confを以下の設定に変更したことによります。

# 28; SHM(2), gpsd: PPS data from shared memory provided by gpsd
# # http://doc.ntp.org/current-stable/drivers/driver28.html
server  127.127.28.2  minpoll 3  maxpoll 3  true
fudge   127.127.28.2  refid SHM2  stratum 1
# https://www.raspberrypi.org/forums/viewtopic.php?t=191050
# http://denor.daa.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
server  127.127.1.0
fudge   127.127.1.0 stratum 10
server ntp.jst.mfeed.ad.jp
server ntp1.v6.mfeed.ad.jp
server ntp2.v6.mfeed.ad.jp
server ntp3.v6.mfeed.ad.jp

 上記設定内のコメントにも記載していますが、Raspberry Pi起動時にGPSで現在時刻を設定(1PPS対応)(http://denor.daa.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)の記事で紹介されていますSHM(2)がこれまで私が試した中では最良の結果を出しています。このサイトの他のGPSに関連する記事も参考になりました。
 据置ではGNSSモジュールの受信状況悪化はしにくいためtrueオプションを付けたほうがいいと思いますがトンネルなど不安定化しやすい車載用では外すかもしれません。2 台の NTP サーバーを、「プライマリ」および「バックアップ」と指定して使用することはできますか?(https://access.redhat.com/ja/solutions/441873)にこのオプションのわかりやすい説明があります。
 また、据置LAN内GNSS NTP Serverを参照するWindows PCの設定についてはwindowsでNTP時刻同期の精度を向上させるには(https://qiita.com/ymfj/items/380d0b3ea5f6cf3dfdb6)のW32Timeサービスの設定とNTPによる同期について(http://zokibayashi.hatenablog.com/entry/2015/04/11/003004)から複数台設定が特に参考になりました。Windows10 Pro 1809の場合はレジストリで設定しなくても以下のコマンドで設定できるようです。192.168.1.1はLAN内NTPサーバーの仮アドレスです。

w32tm /config /manualpeerlist:"192.168.1.1,0x8 ntp.jst.mfeed.ad.jp,0x8" /syncfromflags:MANUAL /update
net start w32time
sc config w32time start= delayed-auto
w32tm /resyncで手動で合わせてw32tm /query /peersで192.168.1.1がアクティブならばOKのはずです。IPv6のサーバを指定する場合はfe80::aaaa:bbbb:cccc:dddd%11,0x8というようにゾーンインデックスまで入れる必要があります(少なくともリンクローカルアドレスの場合)。

レジストリの設定では上記Qiitaの記事にあるUpdateIntervalは記事中にあるように100程度まで下げなければ100msec以下の精度が得られないようです。100msec以上1秒以内のズレで問題ないならばレジストリ設定は不要かと思います。
 他には127.127.46.u (GPSD-NG client driver)や127.127.22.u(PPS Clock Discipline)、127.127.20.u(Generic NMEA GPS Receiver)も試しました。次点で良かったのがserver 127.127.20.0 mode 82ですがNAVSTAR衛星の位置に因ってかCPUの負荷か?time2の微調整を行っても時に1msec以上のoffsetが発生しました。据置ではgpsdは止めていましたが車載用の精度を高めていったら据置を超えてしまったのでSHM(2)へ統一することにしました。
 この設定で完璧かといいますとそうでも無いようです。コールドスタートするとちっともSHM(2)を読みにいかず、gpsmonやcgpsを手動で叩くといきなり動き出すという謎の挙動をします。対策として上のサイトでは起動時に一度gpspipeでアクセスすることで動作しているようなのでcrontabに以下の乱暴な対策を追加して暫定対策にしています。(後日修正)rc.localに以下の空読みを加えています。cronで何度も読んでもgpxloggerのランダム停止は防げませんでした。gpsd.serviceが動き出してから最初の1回だけで十分なようです。

cat << 'EOS'| sudo tee -a /etc/rc.local
/usr/bin/gpspipe -w | /usr/bin/head -1 > /dev/null 2>&1
EOS

※Webで調べる限り、脈絡なしのハングアップはsmsc95xx.turbo_mode=Nと関係するのではないか?と疑っています。→3/20追記)ようやく対策の目処が付きました。gpxloggerのログ途絶対策についてはRaspberry Pi Zero WHにgpsd 3.18.1をビルド・上書きインストール(https://kadono.xsrv.jp/2019/03/20/8052)を参照してください。
 上記以外にもUART 115200bps化など細かいチューニングを行っていますので追って時間を見つけて編集します。
以下は設定メモです。あちこちからの寄せ集めで十分な確認をしていませんので注意してください。

sudo cp /boot/cmdline.txt /boot/cmdline.txt.orig
sudo sed -i -e 's/console=serial0,115200 //g' /boot/cmdline.txt
sudo systemctl stop serial-getty@ttyS0.service
sudo systemctl disable serial-getty@ttyS0.service
sudo bash -c "echo enable_uart=1 >> /boot/config.txt"
cat << 'EOS'| sudo tee -a /boot/config.txt
dtoverlay=pps-gpio,gpiopin=18,assert_falling_edge=true
EOS
cat << 'EOS'| sudo tee -a /etc/modules
gps-gpio
EOS
sudo apt-get install -y gpsd gpsd-clients pps-tools
sudo mv /etc/default/gpsd /etc/default/gpsd.orig
cat << 'EOS'| sudo tee -a /etc/default/gpsd
START_DAEMON="true"
USBAUTO="false"
DEVICES="/dev/gps0 /dev/pps0"
GPSD_OPTIONS="-b -n"
EOS
sudo systemctl enable gpsd.socket
ls -alF /dev/serial*
cat << 'EOS'| sudo tee -a /etc/udev/rules.d/99-pps.rules
KERNEL=="ttyAMA0",SYMLINK+="gps0"
KERNEL=="pps0",OWNER="root",GROUP="dialout",MODE="0660",SYMLINK+="gpspps0"
EOS
sudo apt-get install -y setserial
cat << EOS | sudo tee -a /usr/lib/systemd/system/ttysetup.service
[Unit]
Description=Prepare tty UART for GNSS
Before=gpsd.service
After=setserial.service
[Service]
Type=oneshot
ExecStartPre=/bin/setserial /dev/ttyAMA0 low_latency
ExecStart=/bin/stty -F /dev/ttyAMA0 raw 115200 cs8 clocal -parenb -cstopb
[Install]
WantedBy=multi-user.target
EOS
sudo systemctl daemon-reload
sudo systemctl enable ttysetup

sudo shutdown -r now

 後日追記) 115200bps化のためsttyで切り替えようとしても/etc/rc.localに書いたのではgpsdが起動した後になるようです。これを防ぐにはsystemdで明示的にgpsdよりも前に設定が入るようにする必要があり上記設定を更新しました。115200bps化についてはGPSモジュールの測定周期を10Hzに変更できたが gpsdを起動すると1Hzに戻される(https://qiita.com/snz/items/624544d508867e6473c2)が参考になっています。NTPではPPSが支配的なため測定周期を上げる必要は感じていませんがGPSロガーでは試してみたいと思っています。systemdでの起動順序についてはsystemdのサービスの起動順序を決める(https://takeg.hatenadiary.jp/entry/2017/02/15/220536)で説明されているsystemd-analyze plot > systemd.svgでファイルを作成してChromeブラウザで見ると分かりやすかったです。
 更に追記) 上記で追加したttysetup.service中の順序設定ですけど当初Before=chronyd.service gpsd.socketとしていてRaspberry Pi Zero WHでは動作していたのですがマルチコアCPUの影響か?起動タイミングがまるで異なるRaspberry Pi 3 Model Bではgpsdが起動時点で止まってしまいました。
 追記その3) After=setserial.serviceを追加しなければsetserialが有効になる前にttysetupが走るようなので追加しました。また、GNSSモジュールに対するコマンドも見直して、gpsdが上書きする(?)$PMTK220と$PMTK314は削除し、DGPS RTCM, SBAS Enableを追加しました。DGPSの効果は今ひとつ不明ですけど、SBASは2019年2月現在PRN 137, MTSAT-2を捉えることができるようです。MTSAT-2運用終了後は分かりません。
 追記その4) 新品のモジュールで動作を見たところホスト側のUARTが9600bpsでコマンドを送れていないようでした。対策として、明示的に9600bpsにして115200のコマンドを発行する行を追加しました。バックアップ電池等でモジュール側が既に115200bpsの場合は無視されるはずです。再起動ないし以下の手順で切り替えることができるはずです。例によって検証は不十分です、

sudo systemctl stop gpsd
sudo systemctl stop gpsd.socket
/bin/stty -F /dev/ttyAMA0 raw 9600 cs8 clocal -parenb -cstopb
# cat /dev/ttyAMA0 # 必要ならばUART動作確認(CTRL+Cで止める)
/bin/echo -e "\$PMTK251,115200*1F\r\n" > /dev/ttyAMA0
/bin/stty -F /dev/ttyAMA0 raw 115200 cs8 clocal -parenb -cstopb
# cat /dev/ttyAMA0 # 必要ならばUART動作確認(CTRL+Cで止める)
sudo systemctl restart ttysetup
sudo systemctl start gpsd
gpspipe -w -n 2

 一旦gpsdが起動するとUARTをつかみっぱなしになるため一旦止めてからttysetupや手動でのコマンド書き込みを実行する必要があります。また、AE-GYSFDMAXBのマニュアルには明示されていませんけどPMTK220で5Hzに切り替わることは確認しました。NTPでは恩恵が無いのと一般道の速度では1Hzでも十分そうなので当面は1Hzで試験を続けます。
 追記その5)どうやらモジュールへの9600bps→115200bpsの切替を上記のttysetupスクリプトでは起動時に自動でうまく動かないようです。詳細を解析している暇がなく原因が分かりませんけど、切替待ち時間不足か何かと思います。GNSSモジュールにバックアップ電池が付いていれば、手動で一度書き込んでしまえば当分(たぶん半年以上)は保ちそうなのでとりあえず様子を見ます。GNSS側が切り替わったかはgpspipeコマンドの返り値二行目に"bps":115200と表示されることで確認できます。
 追記その6)PMTK314でGPRMC, GPGGAだけに設定してもgpsdが自動的にGNSSモジュールにコマンドを送って解除する点について/etc/default/gpsdに-bオプション(read-only)を入れることでgpsdからのコマンド送信を止めることができました。この状態でgpsmonを動かすとGPRMC, GPGGAで得られる情報だけが表示されます。NTPサーバにしてもGPSロガーにしても通常はこれで足りるため節電や精度向上のため設定を変更しました。
 追記その7)さらに調べると、NTP ServerとしてはGPRMCだけでも何とかなりそうなので更に削りました。あと、DGPSのモードはSBASが標準らしいのであえてPMTK313, 301を設定する必要はないのかもしれません。少なくとも、私が動かしている限りマニュアルのExampleどおりのRTCMではちっともDGPSにならずSBASに切り替えたところ即GPRMCの最後がDに変わりました。
 追記その8)cat /dev/ttyAMA0で表示が崩れる問題の対策としてraw 115200 cs8 clocal -parenb -cstopbというように仕様書の設定Data 8bit, Stop 1bit, パリティなし、フロー制御なしを明示的に設定することで対策できました。使用しているモジュールは異なりますけどRaspberry Pi3のシリアルポート UARTに VK16Eの GPSモジュールを接続する方法(http://www.neko.ne.jp/~freewing/raspberry_pi/raspberry_pi_3_serial_gps/)を参考にしました。
 追記その9)起動時のttysetupではUARTの設定変更だけにしてGNSSモジュールへの書き込みは手動の方が確実のようです。
 追記その10)UARTで書き込むGNSSモジュールへのコマンドについてはAE-GYSFDMAXB設定コマンドの謎
(https://kadono.xsrv.jp/2019/04/05/8154)
へ移しました。
据置型ハード現況。
主なハード部品(Raspberry Pi Zero WH スターターセット分は除く)
AE-GYSFDMAXB
AE-RasPi-Universal
PH-2x40SG
FHU-2x42SG
PH-1x40SG
FHU-1x42SG
USB CABLE A-MICROB(2A L0.15m)
USB-LAN100R
 OSは2018-11-13-raspbian-stretch-liteからアップデートを繰り返してLinux version 4.14.98+です。demsgで認識している据置用のモデルはMachine model: Raspberry Pi Zero W Rev 1.1になります。
 AE-GYSFDMAXBのファームウェアは2019年1月に購入したものはすべて0014(みちびき3機版)にアップデート済みでした。いちいちPCに接続し直さなくても$ /usr/bin/gpspipe -w -n 2で2行目の"subtype":"AXN_2.5_3339_17110325-0014"の末尾にバージョン"0014"が出ますのでRaspberry Piにセットアップ済みでも確認は可能です。
 追記その11)3ヶ月以上運用してNTP Server用としてAE-GYSFDMAXBは特に問題が無いのですけどGPSロガー用としては車載用GNSSモジュールをGT-902PMGGへ変更(https://kadono.xsrv.jp/2019/05/03/8256)のとおり変更しました。