GPSレシーバーとツールソフトで実現できるラズパイの時刻合わせ、スタンドアローン運用に最適

Amazonのアソシエイトとして、ラズパイダ(raspida.com)は適格販売により収入を得ています。詳しくは当サイトのプライバシーポリシーをご覧ください。

ラズパイにGPSレシーバーを使ったプロジェクトです。使用したGPSレシーバーはUSB接続なのでGPIO端子を使わずに済み、電気系が苦手な人にとってお手軽でした。

Raspberry PiにはRTC(リアルタイムクロック)がないので、そういう意味で一般的なパソコンではないとラズパイダではお伝えしてきました。

通常、ラズパイがインターネットに接続できれば、ネット回線経由で時刻修正するため、ラズパイを起動する度に時刻は修正されます。完全にスタンドアローン(孤立)して使うと、時刻の修正はできないため時計(RTC)が付いていないRaspberry Piでは徐々に時間が狂ってしまいます。

セキュリティ関係や、物理的な問題でネットワークに接続させたくない事例もあるでしょう。特に産業用途などで見受けられます。

GPSレシーバーであれば、別電源の確保も必要無く、ラズパイとの接続もUSBケーブルだけで時刻修正できてしまうと便利ですよね。

今回のテストは屋内でしましたが、窓際に設置すればGPSデータをしっかりと受信できていました。——私の環境では、GPSモジュールを窓から5cmでも離すとデータがN/Aになりました。

終わってみれば、意外と簡単に実現できました。

この記事の主な流れ

今回使用したモノ

Raspberry Pi 4にRaspberry Pi OS bullseye(64bit)の環境です。

GPSレシーバーモジュールはGLONASS対応かどうかで、同じメーカーに2種類ありました。

今回使用したデバイス

信号プロトコル:NMEA-0183(通信速度 9600 bps)
感度:-148 dBm(コールドスタート時)
チップ:u-blox UBX-G7020-KTチップセット
コネクター&ケーブル:USB 71JA. 3m

よく見るとチップが違った。今回使用したタイプはGLONASS未対応、Android対応でした。片方はAndroid未対応。

対応衛星の名称

  • GPS(米国)
  • GLONASS(ロシア)
  • BeiDou(中国)
  • Galileo(欧州)
  • みちびき(日本)
  • NAVIC(インド)

USB接続のGPSレシーバーは、USB-CDC(Universal Serial Bus Communications Device Class)でデバイス間のデータをやり取りする規格で、昔からあるシリアルポート(RS-232C)と同じですね。

今回はGPIOに接続するようなデバイスではなく、USBをサクッと挿すだけのお手軽レシーバーです。だからPPSは使いません。

ドライバは要らない

購入した製品にはWindows用の確認用アプリを入手できるURLが載っていました。

Raspberry PiというかLinuxだと標準でドライバがあります。

接続した後、dmesgコマンドで確認すると、「u-blox 7 - GPS/GNSS Receiver」が「cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device」とUSB ACMデバイスとして認識されています。

ここで使うのは、ttyACM0というポートです。

dmesg

もしくは、マウントされているby-pathを表示してみます。(Raspberry Piだとby-idが無くてby-pathになっている)

ls -l /dev/serial/by-path/
platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0 -> ../../ttyACM0

このようにttyACM0になっていることが分かります。

デバイスポートgps0に変更

ttyACM0を使用して問題ないのですけど、他にも同種のシリアル通信するモノを繋いだ時など、どちらか一見して判断できません。

例えば「gps0」みたいに分かりやすいデバイスポート名にしたいので、少し面倒ですが設定しました。

この段落はすっ飛ばしても構いません。その場合は、これ以降のgps0の部分はttyACM0に置き換えて読んでください。

.rulesの追加

プログラムで使用するにも名前が分かりやすい方が良いです。デバイスポート名を「gps0」としてルールを設定してみます。

Raspberry Pi OSだと、ポートのルール定義ファイル「99-com.rules」があります。そこに書き込まず、新たにルールファイルを新規作成しました。ファイル名は数字の連番にしないとなりませんから、80−としました。

sudo nano /etc/udev/rules.d/80-com.rules

書き込むのは、次のようにベンダーIDとプロダクトIDで指定して設定しました。lsusbか、dmesgで調べた時、「idVendor=1546, idProduct=01a7」とありました。

lsusb

このベンダーIDとプロダクトIDを控えておきます。

これを改行しないで書き込みます。

コマンドであれば対話式で追記できます。もちろんnanoで書き込んでも同じです。

sudo cat << EOS | sudo tee /etc/udev/rules.d/80-com.rules

cat << EOSとteeコマンドで書き込みます。
コマンドプロンプト(>)が出るので、続けて上記の設定を入力しEnter、次にEOSと入力すればOKです。

書き込む内容は次の通りです。

KERNEL=="ttyACM0",  ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="gps0"

これで起動時にgps0が作られるので、ここで再起動します。

gps0の確認

起動したら確認してみます。

ls -al /dev/gps0

 表示がgps0 -> ttyACM0となっていればOKです。

USBにGPSモジュールを挿していれば、既に受信データの確認ができます。

cat /dev/gps0

これで$GPRMC〜$GPVTG〜といったデータが、ターミナルにズラズラっと出てくると思います。

ただこれだと何を表しているのか全く分からない。解析するシフトをインストールしていきます。

シリアル通信を確認するのに、catやdmesg以外にも、atpパッケージにはminicomやscreen、cuといったツールソフトもあるようですね。

gpsdのインストールと設定

GPSのデータは、NMEAフォーマット(NMEA0183)という形式です。この形式データを解析するためのソフトとして、GPS service daemon(gpsd)GPSd — Put your GPS on the net!が有名なのでインストールします。

sudo apt install gpsd gpsd-clients

続いて設定ファイルを修正します。

sudo nano /etc/default/gpsd

デバイス名を先程設定したgps0にし、起動オプションとして、-b -nとしました。これで上手く行きました。

他にも-F /var/run/gpsd.sock -b -nという書き方もありまして、ちょっと違いがわかりません。-Fで指定しているファイルはデフォルトでは指定していません。どうなんでしょう。

ヘルプで見ると、-bはBluetooth-safeなのであった方がいいと思いました。

  -b, --readonly            = bluetooth-safe: open data sources read-only
  -D, --debug integer       = set debug level, default 0 
  -F, --sockfile sockfile   = specify control socket location, default none
  -f, --framing FRAMING     = fix device framing to FRAMING (8N1, 8O1, etc.)
  -G, --listenany           = make gpsd listen on INADDR_ANY
  -l, --drivers             = list compiled in drivers, and exit.
  -n, --nowait              = don't wait for client connects to poll GPS
  -N, --foreground          = don't go into background
  -P, --pidfile pidfile     = set file to record process ID
  -p, --passive             = do not reconfigure the receiver automatically
  -r, --badtime             = use GPS time even if no fix
  -S, --port PORT           = set port for daemon, default 2947
  -s, --speed SPEED         = fix device speed to SPEED, default none
  -V, --version             = emit version and exit.
# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
DEVICES="/dev/gps0"

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

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

gpsdで使えるコマンド

gpsdで5種類の解析できるツールがあります。

  • gpsmon -n
  • cgps -s
  • xgps (文字化けする)
  • gpsplot
  • gpsprof(gnuplotがないと怒られる)

1と2はSSH経由でもOKで、3と4は図が描画される(別ウィンドウ)のでSSH経由では動作しません。

今回は1と2で時刻や緯度経度を確認しました。他はまたの機会に・・・。

各ツールは終了させるのに、画面上部のプロンプトへそのままquitまたはqと入力するか、Ctrl + C で終了できます。

gpsmon -n

cgps -s

これらで緯度経度が表示されているのが確認できれば、GPSからデータを取得していることになります。

ヒント

gpsdをシャットダウンしたい場合は、両方のユニットをシャットダウンする必要があります。gpsd.socketが再び起動する可能性があるため、gpsd.serviceをシャットダウンするだけでは不十分です。

これは少しハマりました。socketも指定しましょう。

sudo systemctl stop gpsd.socket gpsd.service

反映されないなら、どこか設定が異なっているかも知れません。再起動してみましょう。

トラブルシューティングのページは一読しておいた方が良かった。

今回使用したデバイス

■技適認可されたPi 5と安価になったPi 4

時刻合わせをさせる

さて、ここからが本題です。

タイトル通り、時刻の修正をするために使ってみます。

今回使用したGPSデバイスモジュールはUSBケーブルが3mと長めですから、なんとか窓まで移動させられました。

先程のGPSデータに時刻が出ていましたよね。UTC時刻です。早速、ラズパイの時刻を修正させてみましょう。

プログラミングしなくてもgpsdとchronyでOK

GPSレシーバーから取得した時刻は、プログラムで読み込ませてシステムの時計を合わせることができます。

Pythonで書こう!と考えたけど、私は非エンジニアでプログラマー経験もありません。多分、書けません。

仕方なく検索してみると、参考になるプログラムもありました。

例えばこちら。

ラズパイの時計をGPSモジュールと同期させる 実装編:名刺サイズの超小型PC「ラズパイ」で遊ぶ(第61回) - ITmedia NEWS

確かにこれでもOKなんですけど、用意したシェルスクリプトをcronで定期的に実行することになります。

上記サイトのプログラミング部分だけ転載すると、シェルスクリプトはこんな感じ。

#!/bin/sh
echo start at `/bin/date`
GPSDATE="`/usr/bin/gpspipe -w | /usr/bin/head -10 | /bin/grep TPV | /bin/sed -r 's/.*"time":"([^"]*)".*/\1/' | /usr/bin/head -1`"
echo $GPSDATE
/bin/date -s "$GPSDATE"
echo end at `/bin/date`

これでも構いません。しかし、

プログラミングしなくても、gpsdとchrony(またはntpd)を連携すればできることを知りました。

こちらならインストールと設定だけで済みそうです。うん、これがいい。

gpsdは既にインストールしてあるので、残りはchronyだけです。aptにあるntpdと同じntpサーバソフトです。

それに、gpsdの公式ページに”ネットがないスタンドアローン環境ではchronyの方が良い”という記述も見つかり、是非も無しって感じです。

タイムサービスデーモン
ntpdまたはchronyのいずれかをインストールする必要があります。パッケージシステムでUnixバリアントを実行している場合、パッケージはおそらく「ntp」(または「ntpsec」)と「chrony」または「chronyd」という名前になります。

ntpdとchronyの間で、ntpdはより古く、より人気のある選択肢です。したがって、異常な状況で助けが必要な場合は、最も確立されたピアコミュニティを持つものです。一方、chronyはセットアップと設定が簡単であるという評判があり、時計が大幅にドリフトするのに十分な期間、マシンをインターネットから切断する必要がある状況では優れています。

ntpdとchronyは異なる哲学を持っており、ntpdは複数のソースからコンセンサス時間を導き出すことに関心があり、chronyは単一の最良のソースを特定し、それを綿密に追跡しようとします。

GPSD Time Service HOWTO

既にgpsdとchronyは連携するようになっています。これは更に好都合です。

Chronyのインストールと設定

早速、chronyをインストールしていきましょう。

先にsystemd-timesyncdを止めて無効にします。Raspberry Pi OSはsystemd-timesyncdで時刻合わせしているからです。

実際にchronyをインストールしようとすると、systemd-timesyncdは必要無く削除されちゃいます。削除しなくても無効化しておけばOKですね。

sudo systemctl stop systemd-timesyncd
sudo systemctl disable systemd-timesyncd

インストールはいつものaptコマンド。

sudo apt install chrony

設定ファイル(conf)の修正と追記

confファイルで設定するのはおよそ5箇所、最低2箇所の修正です。ここがややこしくて難しく感じました。

sudo nano /etc/chrony/chrony.conf

設定した内容はこちら。赤字がコメントアウトした箇所で、黄色が追記した箇所です。

最後のallow IPアドレスは、ntpサーバとして外部から接続の必要がなければ、特に記載しなくても大丈夫です。別途、ラズパイのIPアドレスは固定化していますが、タイムサーバはホスト名でも指定できます。

makestepの箇所はslewにしています。これは閏秒の設定です。特に気にならないのであれば、ここも必要ではありません。

ラズパイだけ時刻合わせをするなら、僅かに2箇所の修正で試せます。

# Use Debian vendor zone.
#pool 2.debian.pool.ntp.org iburst

# SHM(shared memory)から読み取る設定を追加
refclock SHM 0 refid GPS
chrony.confの修正・追記後
# Welcome to the chrony configuration file. See chrony.conf(5) for more
# information about usable directives.

# Include configuration files found in /etc/chrony/conf.d.
confdir /etc/chrony/conf.d

# Use Debian vendor zone.
#pool 2.debian.pool.ntp.org iburst

# Use time sources from DHCP.
sourcedir /run/chrony-dhcp

# Use NTP sources found in /etc/chrony/sources.d.
sourcedir /etc/chrony/sources.d

# This directive specify the location of the file containing ID/key pairs for
# NTP authentication.
keyfile /etc/chrony/chrony.keys

# This directive specify the file into which chronyd will store the rate
# information.
driftfile /var/lib/chrony/chrony.drift

# Save NTS keys and cookies.
ntsdumpdir /var/lib/chrony

# Uncomment the following line to turn logging on.
#log tracking measurements statistics

# Log files location.
logdir /var/log/chrony

# Stop bad estimates upsetting machine clock.
maxupdateskew 100.0

# This directive enables kernel synchronisation (every 11 minutes) of the
# real-time clock. Note that it can’t be used along with the 'rtcfile' directive.
rtcsync

# Step the system clock instead of slewing it if the adjustment is larger than
# one second, but only in the first three clock updates.
#makestep 1 3
leapsecmode slew
maxslewrate 1000
smoothtime 400 0.001024 leaponly

# Get TAI-UTC offset and leap seconds from the system tz database.
# This directive must be commented out when using time sources serving
# leap-smeared time.
leapsectz right/UTC

# SHM(shared memory)から読み取る設定を追加
refclock SHM 0 refid GPS

allow 192.168.0.0/24
local stratum 10

ザックリと解説

makestep 1 3となっている箇所をコメントアウトし、公式ページの推奨である3行を追加しました。leapsecmode slewのslewは閏秒の設定で、長い時間をかけ少しずつ修正していく形式です。stepがデフォルトです。閏秒の修正が不自然?に一気に変わるのを防ぐための設定です。

そのため、デフォルトだったmakestep 1 3のままでも良いでしょう。

大事な点は2箇所だけ。

  1. pool 2.debian.pool.ntp.org iburstをコメントアウト
  2. SHMから読み取る設定(gpsdから取得する)

1は、外部のタイムサーバに接続してしまうとGPSの意味がなくなってしまうので今回はコメントアウトしています。

2は、共有メモリから時刻を読み取る設定です。gpsdと連携してデータを取得させていることになります。

# SHM(shared memory)から読み取る設定を追加
refclock SHM 0 refid GPS

オプションもたくさんあるのですが、色々と試した結果、USB接続のGPSモジュールであれば、シンプルな設定でOKでした。

シェアードメモリ(共有メモリー)= 複数プロセス間で共有可能なメモリ領域

ヒント

設定の反映はサービスの再起動でOKなのですが、chronydも一緒に指定しましょう。

sudo systemctl restart chrony chronyd

ラズパイを再起動してもOKです。

SHM(shared memory)から読み取る設定について

今回はUSB接続のデバイスということもあり、デフォルト値のままで試しています。

他にrefclock SHM 0 refid GPS precision 1e-1 offset 0.9999 delay 0.2な書き方があります。

ここの記述は難しいので、公式サイトで確認してください。

シリアル通信がUSB接続なので、GPIO接続のPPTSではありません。オフセットとディレイは無くても構わないと解釈しました。

socket指定は上手くいかなかった

socketを指定するやり方もあるようです。自分では良い結果になりませんでしたね。

refclock SOCK /var/run/chrony.gps0.sock refid GPS precision 1e-1 offset 0.9999

確認してみる

chronycコマンドで確認してみます。

chronyc sources

ここで表示されるID名GPSの左に*印が表示されれば良く、Reachにある数字377なら、すべてのパケットが届いた証拠になります。

ちなみに、-vオプションで詳しい説明が出ます。

加えて、他のPCなどでラズパイのchronyをタイムサーバ(NTPサーバ)に指定した場合、接続しているクライアントが出てきます。

先程のconfファイルにallow行を指定していなければ確認する必要はありません。

sudo chronyc -a clients

トラッキング情報を表示するコマンド。

sudo chronyc tracking
Reference ID    : 47505300 (GPS)
Stratum         : 1
Ref time (UTC)  : Thu May 25 03:26:07 2023
System time     : 0.000562014 seconds fast of NTP time
Last offset     : +0.000318895 seconds
RMS offset      : 0.001042068 seconds
Frequency       : 16.892 ppm fast
Residual freq   : +0.494 ppm
Skew            : 30.426 ppm
Root delay      : 0.200000003 seconds
Root dispersion : 0.101114176 seconds
Update interval : 16.0 seconds
Leap status     : Normal
Rレッド

どうやら、上手くいっているみたいですね。

タイムサーバ先として指定する方法

GPS+ラズパイをタイムサーバ先に指定する設定は、WindowsとMacで次のように指定します。

Windowsの場合

コントロールパネルにある日付と時刻の設定のインターネット時刻タブから、インターネット時刻サーバと同期するサーバを指定できます。

ここにRaspberry Pi のローカルアドレスを入力します。固定IPにしていないならホスト名でも大丈夫です。(例:testpi.localなど)

Macで同期

Macだと設定から一般の日付と時刻の設定にあるタイムサーバで変更します。同じくIPアドレスかホスト名を入れます。

Macの場合、実際に同期させるのはコマンドで行います。

sudo sntp -drsS アドレス

数回送信のうち1回くらい失敗がありましたが、Successとあり、最終的に誤差をアジャストしたと表示されます。refにある取得先がGPS、ラズパイのローカルIPアドレスも表示されています。

常時電源を入れているラズパイにUSBモジュールと仕込んでおけば、ローカルの時刻合わせ先に使えて実用的ですね。

はてな?

stratunが1になっているのは気になります。わざと10に設定したのですけどね。0(GPS)から受け取るので、1でも間違いではないと思うのですけれど。あれれ?

ラズパイ+モバイルバッテリーでGPSロガー

今回はGPSで受信するデータからUTC時刻だけを利用しました。GPSなのに緯度経度は使っていない!

モバイルバッテリーで数時間動くZero系なら、緯度経度を利用したロガー&トラッカーみたいに使えそうですね。スマホのアプリでも同じようなのがあるかも知れませんがセキュリティやプライバシーが怖い・・・。

自前でどこにも繋がっていないラズパイ+GPSは安心です。

緯度経度の精度は10mくらいズレるかな? 受信する場所にも依るでしょうし、まぁそんなもんかなという印象です。

リーズナブルな価格で楽しめるデバイスの1つだと感じました。皆さんも是非トライしてください。

今回使用したデバイス

Raspberry Pi 5は2024/01/11に国内技適の認証済み! (認証番号:020-230329)

Pi 5関連商品も続々と登場中

5V5Aの電源アダプターは、公式製品が気軽に買えるまで選択肢があまりありませんね。

Raspberry Pi 3B / 3B+ / 3A+
Raspberry Pi 4/ Pi 400
この記事の主な流れ