Raspberry Pi でGPSデータ取得

Raspberry Pi 3 B++でGPSデータを取得するソフトをc++で作成しました。
Pythonで実装されている方がいたので、そちらを参考にしました。
- [Raspberry Pi3のPythonでGPSを扱う](https://qiita.com/AmbientData/items/fff54c8ac8ec95aeeee6)

# センサ
使用したセンサは以下になります。またRaspberyPiとセンサはUSBシリアル変換モジュールでUSB接続しました。
- [ＧＰＳ受信機キット　１ＰＰＳ出力付き　「みちびき」３機受信対応](http://akizukidenshi.com/catalog/g/gK-09991/)
- [ＦＴ－２３２ＲＱ　ＵＳＢシリアル変換モジュール](http://akizukidenshi.com/catalog/g/gM-11007/)

# GPSデータ
このセンサからは、データはNMEA-0183というフォーマットで文字列で送信されます。
（例）  

```
$GPGSV,4,4,15,31,07,154,16,10,07,104,,193,05,179,*7D
$GPRMC,142330.000,A,3534.1763,N,13933.4925,E,0.09,210.39,101218,,,A*61
$GPVTG,210.39,T,,M,0.09,N,0.17,K,A*3B
$GPZDA,142330.000,10,12,2018,,*58
$GPGGA,142330.210,3534.1765,N,13933.4923,E,1,3,2.72,110.1,M,39.4,M,,*50
$GPGLL,3534.1765,N,13933.4923,E,142330.210,A,A*5E
```
1つのデータは`$`から始まって`\r\n`で終わります。`*`の後の数値はchecksumです。`$`の後の文字列がデータの種類を表しており、このセンサからは以下の種類のデータが送信されます。

- GPGGA
- GPGLL
- GPGSA
- GPGSV
- GPRMC
- GPVTG
- GPZDA

フォーマットの詳しい内容は、こちらの[サイト](https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm?tocpath=Logs%7CView%20Logs%7CGNSS%20Logs%7C_____45)を参考にしました。
※ フォーマットを説明したサイトはいくつかありますが、それぞれ内容が少しずつ異なっており、実際に取得したデータとフォーマットが一致したのは上記のサイトでした。

# ソフトウェア
内容を簡単に説明します。作成したソフトウェアは[こちら](https://github.com/hiro-han/sandbox/tree/master/Gps)にあるので、全てのコードはこちらを見てください。

100ms周期でセンサからデータを受信し、変換するプログラムです。
カレントディレクトリに`log.txt`を作成し受信したデータを出力します。
100msの間に同じ種類のデータを何度も受信した場合は最新のデータを出力します。

```c++:main.cpp
int main() {
  std::ofstream ofs("log.txt");

  SerialConnection serial;
  if (!serial.initialize("/dev/ttyUSB0", SerialConnection::kB9600)) {
    return 1;
  }
  sleep(1);

  NEMAParser::NEMAParser parser;
  NEMAParser::ErrorCode error;
  while (true) {
    usleep(100000.f); // 100ms
    ofs << "-----START-----" << std::endl;
    std::istringstream ss(serial.receive('\n'));
    std::string buffer;
    while (std::getline(ss, buffer, '\n') ) {
     error = parser.parse(buffer);
    }
    if (error != NEMAParser::ErrorCode::kNoError) {
      ofs << "Error Code = " << (int)error << std::endl;
    }
    parser.getGPGGA().output(ofs);
    parser.getGPGLL().output(ofs);
    parser.getGPGSA().output(ofs);
    parser.getGPGSV().output(ofs);
    parser.getGPRMC().output(ofs);
    parser.getGPVTG().output(ofs);
    parser.getGPZDA().output(ofs);
    ofs << "-----END-----" << std::endl;
  }
}
```
`serial.initialize()`の後、デバイスの初期化完了を待つために`sleep(1)`を入れています。

```c++:NEMAParser.cpp
...(略)...
std::vector<std::string> NEMAParser::split(const std::string &data) const {
  std::vector<std::string> splited_data;
  std::stringstream ss(data);
  std::string buffer;
  while (std::getline(ss, buffer, ',') ) {
    splited_data.push_back(buffer);
  }

  // split check sum
  std::string last = splited_data.back();
  splited_data.pop_back();
  ss.str("");
  ss.clear(std::stringstream::goodbit);
  ss << last;
  while (std::getline(ss, buffer, '*') ) {
    splited_data.push_back(buffer);
  }
  return splited_data;
}

ErrorCode NEMAParser::parseGPGGA(const std::vector<std::string> &data, GPGGA &gpgga) const {
  if (data.size() != gpgga_.data_num) {
    gpgga.reset();
    return ErrorCode::kGPGGAInvalid;
  }
  gpgga.time                                    = Common::convertToFloat(data.at(1));
  gpgga.latitude                                = Common::convertToFloat(data.at(2));
  gpgga.latitude_compass_direction              = data.at(3);
  ...(略)...
  return ErrorCode::kNoError;
}
...(略)...
```
データは`,`区切りなので、`,`でsplitします。最後のデータのみ`*`でsplitしてchecksumを取り出しています。
splitした後は1つずつ構造体に詰めています。

```c++:GpsSentences.h
...(略)...
struct GPGGA : Sentence {
  GPGGA() : Sentence("GPGGA", 16) {
    reset();
  }

  void output(std::ostream &stream) const {
    stream << "--------------------GPGGA START--------------------" << std::endl;
    stream << "time                                    = " << time                                    << std::endl;
    stream << "latitude                                = " << latitude                                << std::endl;
    stream << "latitude_compass_direction              = " << latitude_compass_direction              << std::endl;
    ...(略)...
    stream << "--------------------GPGGA END--------------------" << std::endl;
  }

  void reset() {
    time = 0.0f;
    latitude = 0.0f;
    latitude_compass_direction = "";
    ...(略)...
  }

  float time;
  float latitude;
  std::string latitude_compass_direction;
  ...(略)...
};
...(略)...
```
取得データの構造体を定義しています。

# 使い方
Gpsフォルダに移動し、以下を実行してください。

```
$ cmake .
$ make
$ ./Gps
```
初回処理では、センサから中途半端な文字列を受信してしまうため変換エラーがでます。

なお、環境によってはシリアルデバイスの接続でエラーが出ることがあります。
その場合は以下を試してください。`ttyUSB0`は環境によっては別名になっている可能性があります。

```
$ sudo chmod 777 /dev/ttyUSB0
```