GPS
IoT
SORACOM
LoRaWan

SORACOM Air for LoRaWAN で GPS トラッカー (Grove GPS 編)

審査員として参加した 道東×IoTハッカソン の空き時間で LoRaWAN を使った GPS トラッカーを作ってみたというお話しです。

アプリケーションは先日アップデートされた SORACOM Harvest の マップ機能 を活用しています

lorawan+gps/device

lorawan+gps/map

解説

ハードウェアと費用

用途 金額(円、税込み概算)
Arduino UNO R3 マイコン 3500
Grove ベースシールド V2 GPS 取り付け 1200
Grove GPS GPS 4200
LoRa 開発シールド (AL-050) LoRaWAN 通信モデム 8000
合計 16900

Grove ベースシールド V2

Arduino UNO R3 のピンヘッダを Grove コネクタシステムに変換するシールドです。(ピン互換なマイコンボードなら使えるハズです(未確認))

Analog, Digital, UART, I2C が Grove コネクタになっています。

Grove ベースシールド V2

今回 GPS モジュールは D6 に取り付けています。
仕様では UART ですが SoftwareSerial を利用することで digital ポートを UART として使えるようにしています。
通信速度が 9600bps 程度であれば SoftwareSerial で大丈夫です。

ちなみに D2 に取り付けると LoRa 開発シールドの LoRa モデムと干渉する関係でまともに動きません。ご注意ください。
(LoRa モデムの初期化に失敗します)

ソフトウェア (ファームウェア)

ペイロード設計

ペイロードのフォーマットは素直にバイトストリーム(バイナリ)にしました。

緯度 経度
float 4 byte float 4 byte

Arduino UNO R3 のバイトオーダーはリトルエンディアン です。

コード (スケッチ)

ペイロードのフォーマット設計が決まったらコードを書いていきます。

lora_gps.ino
#include <SoftwareSerial.h>
SoftwareSerial gpsport(6, 7);
#include "TinyGPS++.h"
TinyGPSPlus gps;
#include <lorawan_client.h>
LoRaWANClient client;

typedef union
{
 float number;
 uint8_t bytes[4];
} FLOATUNION_t;

void setup() {
  Serial.begin(9600);
  gpsport.begin(9600);
  if(! client.connect())
  {
    Serial.println(" failed to connect. Halt...");
    for(;;){};
  }
}

void loop()
{
  byte payload[8];
  FLOATUNION_t lat;
  FLOATUNION_t lng;
  uint8_t payload_offset; 

  gpsport.listen();
  while (gpsport.available() > 0)
    gps.encode(gpsport.read());
  if (gps.location.isValid())
  {
    payload_offset = 0;
    lat.number = gps.location.lat();
    Serial.println(lat.number, 6);
    for (int i = 0; i < 4; i++)
      payload[i + payload_offset] = lat.bytes[i];

    payload_offset = 4;
    lng.number = gps.location.lng();
    Serial.println(lng.number, 6);
    for (int i = 0; i < 4; i++)
      payload[i + payload_offset] = lng.bytes[i];

    for (int i = 0; i < sizeof(payload); i++)
      Serial.print(payload[i], HEX);
    Serial.println();

    client.sendBinary(payload, sizeof(payload), 2);
    delay(6000);
  } else {
    Serial.println("INVALID");
  }
}

解説

ポイントは下記の通りです。

  • GPS との UART 通信に SoftwareSerial を利用
  • GPS から出力される NMEA 形式 のデコードに TinyGPS++ を利用
  • float 型から byte 型への変換に union を利用

GPS との UART 通信に SoftwareSerial を利用

D6 ポートに接続した GPS モジュールと通信するための宣言です。
Grove ベースシールドの D6 は 仕様 を見ると D6 と D7 に接続されていますので、それをそれぞれ TX と RX に割り当てるように宣言しています。

#include <SoftwareSerial.h>
SoftwareSerial gpsport(6, 7);

setup() 内で通信速度を決定した後 loop() 内に実装しますが gpsport.read() の直前で gpsport.listen() を忘れないようにしてください。

loop()内
  gpsport.listen();
  while (gpsport.available() > 0)
    gps.encode(gpsport.read());

SoftwareSerial は同時に複数のポートを開くことができません。 実は LoRa 開発シールドも SoftwareSerial 使っているため、そこから制御を得るために read() 前に listen() を発行する必要があります。

ちなみに LoRa 開発シールドのライブラリでも listen() しています。

TinyGPS++ の利用

TinyGPS++ のポイントは #encode() 関数です。うまくデコードできれば location#isValid()true になるので、あとは location オブジェクトの中を参照してください。

loop()内
  gpsport.listen();
  while (gpsport.available() > 0)
    gps.encode(gpsport.read());
  if (gps.location.isValid()) {
    float v = gps.location.lat();
  }

float 型から byte 型への変換

union を利用して 1 バイトずつ切り出せるようにします。

typedef union
{
 float number;
 uint8_t bytes[4];
} FLOATUNION_t;

以上の宣言をすると下記のように使うことができます。

FLOATUNION_t lat;
lat.number = 123.11; /* float 型を代入 */
Serial.println(lat.bytes[0], HEX); /* 変数内の1バイト目を取得 */

これを byte 型へ愚直に押し込めるといった方法になります。

    lat.number = gps.location.lat();
    for (int i = 0; i < 4; i++)
      payload[i] = lat.bytes[i];

    lng.number = gps.location.lng();
    for (int i = 0; i < 4; i++)
      payload[i + 4] = lng.bytes[i]; /* payload への代入は、位置をずらさないと上書きになっちゃうよ */

最後は client.sendBinary(payload, sizeof(payload)); として送信します。

SORACOM Air for LoRaWAN / Sigfox のバイナリパーサー機能

ペイロードが下記のようになっているので、それを SORACOM 上で展開するように バイナリパーサー機能 を設定します

緯度 経度
float 4 byte float 4 byte

これに対するフォーマットは下記の通りです。

lat::float:32:little-endian lng::float:32:little-endian
※Arduino UNO R3 のバイトオーダーはリトルエンディアンなので、末尾に little-endian を指定しています。というか、指定できるのかよ!

これで SORACOM からは下記の通りに出力されます。

{
  "lat": 35.1111111,
  "lng": 139.2222222
}

lorawan+gps/binaryparser

SORACOM Harvest へのデータ送信

先日リリースされた SORACOM Harvest の新機能 位置情報データのサポート によると

"lat (または latitude)“, "lon (または longitude, lng, long)” を含むデータを SORACOM Harvest に POST してださい。

ということですので、先ほどのバイナリパーサー機能のキーで位置情報がレンダリングされるわけです。

ソフトウェア実装における補足事項

ペイロード設計の背景

LoRaWAN は 1 回のデータ送信に 11 バイト使えます。このサイズに対して緯度、経度の情報を小数点込みで文字列として送る方法としては

  • 35.xx139.yy と小数点の精度を減らす
  • 1xxxx2yyyy と北緯35度もしくは東経139度からの相対値+小数点の精度を減らす

こんな方法が考えられますが、いづれにおいても「精度が悪い」「国内限定」といった制約があります。

GPS の小数点における精度

GPS の小数点以下の精度については 携帯のGPS精度の計算GPSログの桁数を考えてみました が詳しく解説されていますが、まとめると下表のようになります

小数点 精度
第 0 位 約 100 km
第 1 位 約 10 km
第 2 位 約 1 km
第 3 位 約 100 m
第 4 位 約 10 m
第 5 位 約 1 m
第 6 位 約 10 cm
第 7 位 約 1 cm

用途にもよりますが、 小数点第 5 位は欲しいものです。

ペイロードサイズ削減のアイデア

小数点部に対して必要な精度を整数化して送り、受け取り側で精度を戻すといった考え方もあります。
例えば 35.xxxxx であれば 100,000 を乗算して 3,5xx,xxx にした後に 100,000 の除算をするという方法です
今回の例では結局 4 バイト消費する long 型になるため採用を見送りましたが、計算結果が unsigned integer 型 (2バイト) に収まるのであれば有効な方法ですので、覚えておいて損は無いと思います。

ちなみに SORACOM Air for LoRaWAN (Sigfox) では、後述するバイナリパーサー機能上で四則演算ができるため、デバイスからは unsigned integer で送りつつ、バイナリパーサー機能で除算できます。
そのため、クラウド側で除算する必要が無いので便利かなと。

あとがき

空き時間にやったワリには、壮大な解説ブログになってしまいました。
ちゃんと審査はしたよ!

EoT