Raspberry Pi3のPythonでGPSを扱う


Raspberry Pi3のPythonでGPSモジュールを扱ったメモです。Raspberry Pi3のOSバージョンは

~~Raspbian GNU/Linux 8.0 (jessie)~~
Raspbian GNU/Linux 9.4 (stretch)  (2018年10月5日修正)

です。
![Raspberry Pi3 + GPS](https://ambidata.io/wp/wp-content/uploads/2017/08/20170802gps01.jpg)

# GPSモジュール

GPSモジュールは秋月電子の「[GPS受信機キット 1PPS出力付き 『みちびき』対応](http://akizukidenshi.com/catalog/g/gK-09991/)」を使いました。

このGPSモジュールは、受信したGPSデーターをシリアルで送出します。Raspberry Pi3でシリアル通信するにはUSBポートを使う方法とGPIOピンを使う方法があります。USBポートを使う場合はUSBシリアル変換モジュールが必要になるので、今回はGPIOピンを使いました。
Raspberry Pi3とGPSモジュールとは次のように接続します。

|Raspberry Pi3|GPSモジュール|
|:-----------:|:----------|
|04(5v)       | 5V        |
|06(Ground)   | GND       |
|08(TXD0)     | RXD       |
|10(RXD0)     | TXD       |

GPSの1PPSピンは使わないので何もつなぎません。

# Raspberry Pi3のPythonでシリアル通信

Raspberry Pi3でシリアル通信をするには、raspi-configでシリアルを有効化し、シリアルをコンソールとして使わない設定をします。

## raspi-configでシリアルの有効化

次のようにraspi-configを起動し、シリアルを有効にします。

```bash
pi$ sudo raspi-config
```

「5 Interfacing Options」を選択し、さらに「 P6 Serial 」を選択します。「Would you like a login shell to be accessible over serial?」に対してYesを選択、Finishし、リブートします。



```bash
pi$ sudo reboot
```

/dev/serial0というデバイスが作られます。

```bash
pi$ ls /dev/se*
/dev/serial0  /dev/serial1
```

## /boot/cmdline.txtの修正

/boot/cmdline.txtを修正し、serial0をコンソールとして使わないように設定します。

```bash
pi$ cat /boot/cmdline.txt 
dwc_otg.lpm_enable=0 console=tty1 console=serial0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet splash plymouth.ignore-serial-consoles
```

エディターで「console=serial0,115200」を削除し、リブートします。

```bash
pi$ sudo reboot
```

## Pythonのシリアルモジュールをインストールする

Pythonのシリアルモジュールをインストールします。(2018年10月5日: python-serialからpyserialに変更)

```bash
pi$ pip install pyserial
```

pyenvを使っていない場合はpip3でインストールしてください。

これでRaspberry Pi3のPythonでシリアル通信ができるようになります。Raspberry Pi3にGPSモジュールを接続し、次のようにPythonを立ち上げて、確認すると、GPSデーターが読めているのが確認できます。

```bash
pi$ python3
>>> import serial
>>> s = serial.Serial('/dev/serial0', 9600, timeout=10)
>>> print(s.readline())
b'\x00\x00\xc0\x00\xfe\x00pK&&KLN\xcb14,302,,06,08,254,27,30,07,230,29*70\r\n'
>>> print(s.readline())
b'$GPGSV,4,4,14,23,05,150,19,14,04,054,*74\r\n'
>>>
```

1回目のreadline()は中途半端なデーターになっていますが、2回目以降のreadline()はGPSデーターが読めているようです。

# PythonのGPSライブラリー

GPSモジュールから送られてくるデーターは[NMEA-0183](https://ja.wikipedia.org/wiki/NMEA_0183)というフォーマットの文字列です。NMEA-0183のデーター形式については「[NMEA 0183 sentences データ解析](http://piyajk.com/archives/302)」というサイトが分かりやすいです。

GPSモジュールはGPS衛星からの信号を受信し、時刻、緯度、経度、海抜高度、測位に利用した衛星の数やID、それぞれの衛星の位置(方位角と仰角)などの情報をNMEA-0183フォーマットの文字列として送出します。

この文字列データーをプログラムで扱いやすいデーターに変換するPythonライブラリーがあります。Webを検索すると、[micropyGPS](https://github.com/inmcm/micropyGPS)と[pynmea2](https://github.com/Knio/pynmea2)が見つかりました。

micropyGPSはPython3.xとMicroPythonで動作するGPSデーターの解析ライブラリーで、GPSデーターを入力すると、それを解析してGPSオブジェクトにデーターを追加、更新していきます。GPSオブジェクトのデーターとして時刻、緯度、経度、測位に利用した衛星の数やIDなどの情報が得られます。ドキュメントもしっかりしています。

pynmea2はGPSデーターを1行ずつパースするだけのようで、ドキュメントも貧弱そうに見えます。

ということで、ライブラリーとしてはmicropyGPSを使うことにしました。使うためにはGithubからmicropyGPS.pyをダウンロードして、Pythonを起動するディレクトリーに置くだけです。

# Pythonプログラム

GPSデーターを読むPythonプログラムは次のようになりました。GPSモジュールからデーターを読み、
GPSオブジェクトにデーターを追加、更新する処理をスレッドにして動かし、そのデーターを3秒毎に出力しています。

MicroGPSオブジェクトを生成する時の引数として、タイムゾーンの時差(日本は+9時間)と、緯度経度の出力フォーマットを指定しています。出力フォーマットは次の形式が指定できるようです。

```
'ddm'   10進の度、分 (40° 26.767′ N)
'dms'   10進の度、分、秒 (40° 26′ 46″ N)
'dd'      10進の度 (40.446° N)
```

```python
import serial
import micropyGPS
import threading
import time

gps = micropyGPS.MicropyGPS(9, 'dd') # MicroGPSオブジェクトを生成する。
                                     # 引数はタイムゾーンの時差と出力フォーマット

def rungps(): # GPSモジュールを読み、GPSオブジェクトを更新する
    s = serial.Serial('/dev/serial0', 9600, timeout=10)
    s.readline() # 最初の1行は中途半端なデーターが読めることがあるので、捨てる
    while True:
        sentence = s.readline().decode('utf-8') # GPSデーターを読み、文字列に変換する
        if sentence[0] != '$': # 先頭が'$'でなければ捨てる
            continue
        for x in sentence: # 読んだ文字列を解析してGPSオブジェクトにデーターを追加、更新する
            gps.update(x)

gpsthread = threading.Thread(target=rungps, args=()) # 上の関数を実行するスレッドを生成
gpsthread.daemon = True
gpsthread.start() # スレッドを起動

while True:
    if gps.clean_sentences > 20: # ちゃんとしたデーターがある程度たまったら出力する
        h = gps.timestamp[0] if gps.timestamp[0] < 24 else gps.timestamp[0] - 24
        print('%2d:%02d:%04.1f' % (h, gps.timestamp[1], gps.timestamp[2]))
        print('緯度経度: %2.8f, %2.8f' % (gps.latitude[0], gps.longitude[0]))
        print('海抜: %f' % gps.altitude)
        print(gps.satellites_used)
        print('衛星番号: (仰角, 方位角, SN比)')
        for k, v in gps.satellite_data.items():
            print('%d: %s' % (k, v))
        print('')
    time.sleep(3.0)
```

プログラムを動かすと、次のような結果が出力されました。緯度経度の小数点以下は伏せてあります。

```
14:02:19.0
緯度経度: 35.********, 139.********
海抜: 51.900000
測位利用衛星: [17, 28, 6, 3, 193, 1, 22, 8]
衛星番号: (仰角, 方位角, SN比)
193: (59, 173, 40)
3: (71, 124, 24)
6: (13, 259, 27)
1: (49, 42, 16)
8: (15, 115, 21)
42: (48, 170, 34)
11: (40, 69, None)
14: (4, 47, None)
17: (42, 317, 15)
19: (20, 307, None)
22: (56, 66, 21)
23: (10, 145, None)
28: (63, 248, 38)
30: (3, 224, 16)
```

「衛星番号」の次からの行が捕捉した衛星の衛星番号と仰角、方位角、SN比です。衛星番号193は「準天頂衛星初号機みちびき」です。