USBのGPSレシーバーで現在地を取得

USBレシーバーが手元にあるのでラズパイにつけて現在地を取得してみます。
[BU-353S4](https://www.amazon.co.jp/GlobalSat-BU-353S4-GLOBALSAT-BU-353S4-SiRFstarIV%E6%90%AD%E8%BC%89-GPS%E3%83%AC%E3%82%B7%E3%83%BC%E3%83%90USB-%E6%9D%B1%E4%BA%AC%E9%80%9A%E5%95%86%E6%AD%A3%E8%A6%8F%E8%B2%A9%E5%A3%B2/dp/B00HZCQYLU)を使いました。

アナログな方法ですが、GPSレシーバーの抜き差し前後で下記コマンドを実行して対象のデバイスを特定します。

```
$ ls /dev/ttyUSB*
/dev/ttyUSB0  /dev/ttyUSB1  /dev/ttyUSB2  /dev/ttyUSB3  /dev/ttyUSB4
```

今回は`/dev/ttyUSB4`として認識されている事を前提に進めます。
#GPSの値を取得するスクリプト

```py:gps.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
import serial

#デバイスの指定
DEV = '/dev/ttyUSB4'

#緯度、経度の分数表記⇒度数表記への変換関数
def convert(x):
    result = (float(x[0:-7])+(float(x[-7:])/60))
    return result

#必要な値を文字列として返す関数
def get_str(x):
    result =""
    if (x[2] == "A") and (x[2] != ""):
        result += "state:"+"ok "
    elif (x[2] == "V") and (x[2] != ""):
        result += "state:"+"alert "
    else:
        result += "state:"+"no data "
    if x[7] != "":
        result += 'speed:'+ '%.1f' % (float(x[7])*1.852) + "km/h "
    else:
        result += "speed:"+"no data "
    if (x[4] == "N")  and ( x[4] != "" ):
        result += "north latitude:" + '%.4f' % convert(x[3]) +' '
    elif (x[4] == "S")  and ( x[4] != "" ):
        result += "south latitude:" + '%.4f' % convert(x[3]) +' '
    else:
        result += "latitude:"+"no data "
    if (x[6] == "E") and ( x[6] != ""):
        result += "east longitude:" + '%.4f' % convert(x[5]) +' '
    elif (x[6] == "W") and ( x[6] != ""):
        result += "west longitude" + '%.4f' % convert(x[5]) +' '
    else:
        result += "logitude:"+"no data "
    return result

#チェックサムの検証
def checksum_verify(c_sum,data):
    c_data = '0x00'
    for i in range(1,len(data)-3):
        c_data = hex(int(c_data,16) ^ int(hex(ord(data[i])),16))
    if int(c_sum,16) == int(c_data,16):
        return True
    else:
        return False


#メインループ
#GPRMCの値のみ処理、その値の要素の数が13未満でうまく取得出来ていない場合は処理しない
try:
    #シリアルの取得
    sr = serial.Serial(DEV, 4800)
    while 1:
        #改行コードは除外
        tmp = sr.readline().rstrip()
        #チェックサムの確認
        if checksum_verify(tmp[-2:],tmp):
            tmp2 = tmp.split(",")
            #GPRMCのみ処理
            if (tmp2[0] == '$GPRMC'):
                line = get_str(tmp2)
                print 'OK:'+tmp
                print line
                time.sleep(0.5)
        else:
            print 'NG:'+tmp
finally:
    print 'serial_close'
    sr.close()

```

###補足説明

GPRMCの各値は下記の通りです

```
tmp2[1] UTC時刻 日本標準時の-9時間
tmp2[2] ステータス Aは正常　Vは警告
tmp2[3] 緯度(分数表記)
tmp2[4] (N)北緯か(S)南緯か
tmp2[5] 経度(分数表記)
tmp2[6] (E)東経か(W)西経か　
tmp2[7]　移動速度 knot
tmp2[8] 真方位
tmp2[9] 協定世界時(UTC) ddmmyyの形式
tmp2[12] チェックサム
```

※分数表記⇒度数表記
　緯度、経度　3641.6498なら　36度41.6498分となるが41.6498を60で割りと度数と足せば度数表記

※移動速度
　knotは1.852倍でkm/h

※チェックサム
　`$`から`*`の間の文字をXOR計算して同じ値か確認
　`$GPRMC,034357.279,V,,,,,,,020117,,,N*42`
　上記であれば、
　`GPRMC,034357.279,V,,,,,,,020117,,,N`
　が計算対象となる


#デバイス参照時の固定方法
接続されているデバイスや環境によって`/dev/ttyUSB0`だったり`/dev/ttyUSB4`だったり都度変わってしまうというのは不便です。
自動起動のスクリプトで使用する事が出来ません。そういうときの対処です。

###デバイスIDの使用

差し込むUSBポートが決まっていれば下記の情報を確認し、使用する

```
$ ls -al /dev/serial/by-path
total 0
drwxr-xr-x 2 root root 140 Jan  2 01:30 .
drwxr-xr-x 4 root root  80 Jan  1 20:03 ..
lrwxrwxrwx 1 root root  13 Jan  2 01:30 platform-3f980000.usb-usb-0:1.3:1.0-port0 -> ../../ttyUSB4
lrwxrwxrwx 1 root root  13 Jan  1 20:03 platform-3f980000.usb-usb-0:1.4:1.0-port0 -> ../../ttyUSB0
lrwxrwxrwx 1 root root  13 Jan  1 20:03 platform-3f980000.usb-usb-0:1.4:1.1-port0 -> ../../ttyUSB1
lrwxrwxrwx 1 root root  13 Jan  1 20:03 platform-3f980000.usb-usb-0:1.4:1.2-port0 -> ../../ttyUSB2
lrwxrwxrwx 1 root root  13 Jan  1 20:03 platform-3f980000.usb-usb-0:1.4:1.3-port0 -> ../../ttyUSB3
```

対処のデバイスが`/dev/ttyUSB4`であれば、下記のように修正

```
#デバイスの指定
DEV = '/dev/ttyUSB4'
↓
DEV = '/dev/serial/by-path/platform-3f980000.usb-usb-0:1.3:1.0-port0'
```

###udevルールへの記載(USBポートの変更可能)
`udev`ルールについてはOracleの[udevルールについて](https://docs.oracle.com/cd/E39368_01/e48214/ch07s03.html)を参照。

下記コマンド実行し情報の確認(`/dev/ttyUSB4`が対象デバイスの時)
`udevadm info -a -p $(sudo udevadm info -q path -n /dev/ttyUSB4)`
表示された中から下記を確認

```
  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3':
    KERNELS=="1-1.3"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{devpath}=="1.3"
    ATTRS{idVendor}=="067b"
    ATTRS{speed}=="12"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="21"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="100mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="0"
    ATTRS{bcdDevice}=="0400"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{version}==" 1.10"
    ATTRS{urbnum}=="65169"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="Prolific Technology Inc. "
    ATTRS{removable}=="removable"
    ATTRS{idProduct}=="2303"
    ATTRS{bDeviceClass}=="00"
    ATTRS{product}=="USB-Serial Controller D"

```

下記の三点を抜き出し

```
ATTRS{idProduct}=="2303"
ATTRS{idVendor}=="067b"
ATTRS{product}=="USB-Serial Controller D"
```
`/etc/udev/rules.d`配下に`70-bu353s4.rules`などのファイルを作成し下記の情報を記載する。
※`/etc/udev/rules.d`配下に'70-'で始まるファイルがあって重複する場合は重複しない番号を指定

```70-bu353s4.rules
ATTRS{idProduct}=="2303",ATTRS{idVendor}=="067b",ATTRS{product}=="USB-Serial Controller D",SYMLINK+="bu353s4"
```

ファイルの作成後、GPSレシーバーの抜き差しを実施
これで`/dev/bu353s4`でGPSレシーバーに接続できるようになるので下記のように修正

```
#デバイスの指定
DEV = '/dev/ttyUSB4'
↓
DEV = '/dev/bu353s4'
```