先にざっくりまとめ
- LoRaWAN 1.0.3から標準定義された
DeviceTimeReqとDeviceTimeAnsを使うことによって、端末側の時刻同期が簡単に可能 - NTPのように往復時間(RTT)を都度計測するような複雑な仕組みではないが、プロトコルの仕組み上、Downlinkの通信遅延(レイテンシ)の影響を受けずに正確な時刻同期が可能
- 実装がNTPに比べて非常に楽
- 我々の用途としては十分過ぎる精度が出る
- ただし GPS準拠の時間 であるため、以下の考慮が必要
- うるう秒(Leap Seconds) の減算補正が必要(UTCが欲しい場合)
- msレベルで合わせるには、1/256秒単位の補数計算が必要
DeviceTimeReq / DeviceTimeAns とは?
LoRaWAN デバイスがネットワークサーバ(NS)に対して 現在時刻を問い合わせるための、LoRaWAN 1.0.3から標準定義されたMAC コマンド。
- ノード →(DeviceTimeReq)→ NS (Uplink)
- NS →(DeviceTimeAns)→ ノード (Downlink)
返される時刻は GPS時間(GPS Epoch 時刻)と、秒以下の補正値(Fractional Second) になる。
仕様上は±1秒以内の精度とされているが、実装を工夫すればさらに高精度にできる。
コマンド仕様
Uplink/Downlink 共に、Command ID (CID) は 0x0D を使用する。
■ DeviceTimeReq (CID: 0x0D)
- コマンド自体に payload はない
- 1 byte の MAC コマンド ID をただ投げるだけ
- これが標準で定義された予約コマンドになっているので、サポートされたLoRa Network Server(LNS)は
DeviceTimeAnsを自動的に返してくれる
- これが標準で定義された予約コマンドになっているので、サポートされたLoRa Network Server(LNS)は
0x0D
- ペイロードが空で非常に軽いので通信が通りやすく、相対的にバッテリーにも優しい
■ DeviceTimeAns (CID: 0x0D)
- ネットワークサーバから返るデータは CID(1byte) + ペイロード(5byte)
| Byte | 内容 | 説明 |
|---|---|---|
| 0-3 | Time (GPS Epoch) | 4-byte UInt32 (Little Endianに注意) |
| 4 | Fractional Second | 1-byte(1/256 秒単位) |
⚠️ 注意点
💡 GPS Epoch = 1980/01/06 00:00:00
1. うるう秒 (Leap Seconds) の考慮
DeviceTimeAns で返ってくる GPS 時刻は以下の仕様になっている。
- うるう秒を含まない
- 2025年現在、UTC とは 18 秒差
- ※この差分は将来的に増減する可能性があるため、厳密な運用の場合は設定値として外から持たせる設計が望ましい
そのため、ノード側で UTC(協定世界時)扱いたい場合は、以下のように補正を掛ける必要がある。
UTC = GPS - 18秒(現在のうるう秒)
2. 時刻のアンカーポイント
DeviceTimeAns で返ってくる時刻は一般的イメージと違って 「サーバが応答パケットを作った時刻」ではない。
「DeviceTimeReq を含んだ Uplink の送信が完了した瞬間」 の時刻が格納されている。
そのため、Downlinkが届くまでに数秒のラグがあっても、端末側では「送信完了割り込み」のタイミングさえ覚えておけば、計算によって遅延をキャンセルして正確に同期できる。
実装イメージ
返ってくる値はあくまで秒単位のものであるため、最大 0.999…秒ズレうる。
そこで Fractional Second (Byte 4) を使うことで、約4ms単位(1/256秒)までズレを修正することができる。
以下は実装のロジック例。
// 1. アップリンク送信完了割り込み(TxDone)で、その瞬間のローカル時刻を抑えておく
t_local_tx_done = get_current_local_time_ms();
~ 数秒後、Downlinkで DeviceTimeAns を受信 ~
// 2. DeviceTimeAnsのパース
// GPS時間は 1980/1/6 起点。Fractional Secondは 1/256秒単位。
double t_net_gps_sec = device_time_ans.Seconds + (device_time_ans.Frac / 256.0);
// 3. UTCへの変換(うるう秒補正:2025年時点は -18秒)
double t_net_utc_sec = t_net_gps_sec - 18.0;
// 4. オフセットの計算
// 「サーバーが計測した送信完了時刻」と「端末が記録した送信完了時刻」の差分をとる
double offset_sec = t_net_utc_sec - (t_local_tx_done / 1000.0);
// 5. 現在時刻の確定
// 現在のローカル時刻にオフセットを足せば、高精度な現在時刻(UTC)になる
current_utc_time = get_current_local_time_ms() / 1000.0 + offset_sec;
注意
- LoRaWAN v1.0.3以上でしか使えないが、それ以外にもLNSがサポートしているかどうかというのもある
- 少なくとも The Things Network や AWS IoT Core は対応しているので実用上はまず大丈夫だと思うが、万が一に備えてLNS側を要確認
参考資料
- LoRaWAN® Link Layer Specification v1.0.3
- https://resources.lora-alliance.org/technical-specifications/lorawan-specification-v1-0-3
- DeviceTimeReq / DeviceTimeAns は Section 5 (MAC Commands) あたりに定義されている
- Semtech LoRaMac-node (GitHub)
- https://github.com/Lora-net/LoRaMac-node
- Semtech社が提供するLoRaWANエンドデバイスのリファレンス実装
- 実際のMACコマンドの処理ロジック(LoRaMac.c や LoRaMacCommands.c 周辺)が参考になる