はじめに
micropygpsなどもっと便利なプログラムがありますが、自分なりのプログラムが欲しかったので作成しました。
少しだけ機能をつけて自分にカスタマイズしたので動かない環境が出てくると思います。
リポジトリ(Git)
環境
実行
- Raspberry Pi Pico 1/2
- Raspberry Pi 400(詳細は後述)
開発 - MicroPython v1.24.1 on 2024-11-29; Raspberry Pi Pico2 with
GPSモジュール - Air530Z = AT6558R
- M5stack GPSモジュール V1.1 = AT6668
動機
手持ちのモジュールに最適なモジュールが必要だったからです。
(GSVやGSAをほぼ完璧に取得したい)
GPSロガーとして使いたかったので、自分なりの解釈ができるようにつくりました。
特徴
- 経度から現在の現地時間を出すことができます。日付をまたぐもできます
- 2周波のGNSSモジュールに対応しています
- NMEAからできる限りのデータを取得できます(開発中 詳細以下)
- Fixしていないときでもエラーが発生せずに実行できます
- 自分自身の関数を作って実行できます
対応している解析センテンス
主要なセンテンスはカバーしています。
GPS受信機からのセンテンスが不足していても使用できます。
GGA:$GNGGA, $GPGGA, $BDGGA
GLL:$GNGLL, $GPGLL, $BDGLL
GSA:$GNGSA, $GPGSA, $BDGSA
GSV:$GPGSV, $BDGSV, $GQGSV, $GLGSV, $GAGSV, $GBGSV
RMC:$GNRMC, $GPRMC, $BDRMC
VTG:$GNVTG, $GPVTG, $BDVTG
GST:$GNGST, $GPGST, $BDGST
DHV:$GNDHV, $GPDHV, $BDDHV
ZDA:$GNZDA, $GPZDA, $BDZDA
TXT:$GNTXT, $GPTXT, $BDTXT
使用方法
リポジトリからpygps2.pyを取得し/libに配置
以下のようなプログラムでプログラムを実行できます!
使用方法(3.1以降)
GPSモジュールからデータを読み取ります。バッファは適度に調整してください。
raw = gps.read(8192)
それをちょっと特殊なデコード処理をつけます。tryのプログラムです。
raw = raw.replace(b'\r', b'').replace(b'\n', b'')
raw = raw.replace(b'/', b'')
data = raw.decode("utf-8", "ignore")
受信したデータに、改行がないと処理ができません。(面倒)
主にしたのサンプルのプログラムを利用してください。
次が肝です
pygps2.analyze(data, oldata={})
ここに、デコードしたdataをいれて解析することができます。
解析オプション
enable_type
オプションとして、必要なセンテンスのみを解析することができます。(この項目はなくてもOK)
リストとして入力してください。なにもない場合はすべて有効になります。
enable_type:
[0]: GGA
[1]: GLL
[2]: RMC
[3]: VTG
[4]: GST
[5]: DHV
[6]: ZDA
[7]: TXT
[8]: GSA
[9]: GSV
oldata(olddata)
olddataを間違えてoldataにしました てへぺろ
ここには前回解析したときの解析データを入力できるようにするものです。
必須の項目ですが、機能を使わない場合oldata={}で無効化できます
ただし、これを使うと以下のメリットとデメリットが出てきます。
メリット
- 少ないバッファで取得した、センテンスを積み重ねて解析できる
- 上記によって、すべてのセンテンスを使用しなくても良くなる
経緯度、時間など飲みが必要な場合に有効で低リソースで実行できる。
デメリット
- GSA GSVのセンテンスを正しく解析できない
→衛星データは実質使用できない - 少ないバッファだとしようできない
→一つのセンテンスすべてが読み取れる前提で実行するためバッファを64などにすると使用できない場合がある
メモリが余裕の場合は使用しないことを推奨します。
これらをみてこの機能を利用するか検討してください。
もし使用するなら、初回にanalyzeを実行する前にいかの関数でデフォルト値を設定してください。
analyzed_data = pygps2.init()
プログラム
while True:
raw = gps.read(8192)
if raw is not None:
try:
raw = raw.replace(b'\r', b'').replace(b'\n', b'')
raw = raw.replace(b'/', b'')
data = raw.decode("utf-8", "ignore")
del raw
except Exception as e:
print(f"error: {e}")
continue
analyzed_data = pygps2.analyze(data, oldata={})
使用方法(以前のバージョン)
- まず解析したいGPSモジュールから以下で文字列を取得します
バッファーは使用しているGPSモジュールが出力する量から判断してください。
過剰な値にするとメモリ不足になるので程々にしましょう。
わからない場合は8192からプログラム単体で調べてください。(すべてのセンテンスが含まれるか)
raw = gps.read(8192)
- エラー処理(推奨)
上記のような大きいバッファーがあっても不完全なセンテンスを受信することがあります。そこで、このあとデコードする事ができるような形にととのえます。(tryは保険ですがあったほうが無難です。)
改行の/r/nでエラーが起きるので一度全て消して、追加します。以下のプログラムでそれが実現されます。
continueでいいです
try:
raw = raw.replace(b'\r', b'').replace(b'\n', b'')
raw = raw.replace(b'/', b'')
data = raw.decode("utf-8", "ignore")
except Exception as e:
print(f"error: {e}")
print(raw)
continue
- 解析準備
センテンスの最初にある$でセンテンスごとに切り、リスト化します。以下のようなデータになります。(空白しかないようなデータが有っても大丈夫です。)
sentences = data.split('$')
['', 'GNRMC,084312.00,V,,,,,,,170325,,,N,V*17', 'GNVTG,,,,,,,,,N*2E', 'GNGGA,084312.00,,,,,0,00,25.5,,,,,,*46', 'GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,1*01', 'GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,2*02', 'GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,3*03', 'GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,4*04', 'GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,5*05', 'GPGSV,2,1,08,01,11,086,,03,28,046,,04,41,093,18,09,41,139,24,1*68', 'GPGSV,2,2,08,14,12,199,,17,71,157,,19,76,300,,22,26,218,,1*6D', 'GLGSV,2,1,05,65,37,038,,66,67,123,,79,08,245,,81,60,007,,1*7E', 'GLGSV,2,2,05,88,36,093,19,1*4A', 'GAGSV,1,1,00,7*73', 'BDGSV,2,1,06,21,69,149,,22,40,050,,34,68,327,,35,06,120,,1*7D', 'BDGSV,2,2,06,40,80,221,,44,50,111,,1*7A', 'BDGSV,1,1,00,3*77', 'GQGSV,1,1,04,02,38,195,,03,88,001,,04,48,175,,07,43,202,,1*6C', 'GNGLL,,,,,084312.00,V,N*58', 'GNGST,084312.00,,,,,,,*6B', 'GNZDA,084312.00,17,03,2025,00,00*74', 'GNDHV,084312.00,,,,,,,,,,M*10', 'PCAS50,084312.00,,-2147483648.2147482647,0.000,,8,21,25.5,*0E', 'GPTXT,01,01,02,MS=3,9,0025210F,22,6,00300000,22,6,00816003*19', 'GPTXT,01,01,01,ANTENNA OPEN*25', 'GPTXT,01,01,02,JS=0,,,,0,,,*45']
次にさっきの空白などを消します
sentences = ['$' + sentence for sentence in sentences if sentence]
['$GNRMC,084513.00,V,,,,,,,170325,,,N,V*10', '$GNVTG,,,,,,,,,N*2E', '$GNGGA,084513.00,,,,,0,00,25.5,,,,,,*41', '$GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,1*01', '$GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,2*02', '$GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,3*03', '$GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,4*04', '$GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,5*05', '$GPGSV,2,1,08,01,10,086,,03,27,046,,04,41,091,20,09,42,138,22,1*6B', '$GPGSV,2,2,08,14,11,199,,17,70,157,,19,76,297,,22,25,217,,1*6C', '$GLGSV,2,1,05,65,36,038,,66,68,120,,79,08,244,,81,60,009,,1*7C', '$GLGSV,2,2,05,88,35,094,27,1*43', '$GAGSV,1,1,00,7*73', '$BDGSV,2,1,05,21,70,147,,22,40,049,,34,69,327,,40,80,220,,1*7E', '$BDGSV,2,2,05,44,50,112,,1*47', '$BDGSV,1,1,00,3*77', '$GQGSV,1,1,04,02,38,195,,03,88,002,,04,49,175,,07,43,202,,1*6E', '$GNGLL,,,,,084513.00,V,N*5F', '$GNGST,084513.00,,,,,,,*6C', '$GNZDA,084513.00,17,03,2025,00,00*73', '$GNDHV,084513.00,,,,,,,,,,M*17', '$PCAS50,084513.00,,-2147483648.2147482647,0.000,,8,21,25.5,*09', '$GPTXT,01,01,02,MS=3,9,0025210F,22,6,00300000,22,6,00816003*19', '$GPTXT,01,01,01,ANTENNA OPEN*25', '$GPTXT,01,01,02,JS=0,,,,0,,,*45']
最後に、改行の/r/nを追加して準備完了です。
data = '\r\n'.join(sentences) + '\r\n'
- 解析
parsed_data = pygps2.parse_nmea_sentences(data)
analyzed_data = pygps2.analyze_nmea_data(parsed_data)
これで終わりです。analyzed_dataには解析されたデータがあるはずです。
具体的な使用方法(Raspberry Pi / Pico)
Raspberry Pi
import serial
import time
gps = serial.Serial('/dev/serial0', 115200, timeout=0.01)
import pygps2
import time
while True:
raw = gps.read(6096)#適度に調整
if raw is not None:
try:
raw = raw.replace(b'\r', b'').replace(b'\n', b'')
raw = raw.replace(b'/', b'')
data = raw.decode("utf-8", "ignore")
except Exception as e:
print(f"error: {e}")
print(raw)
continue
sentences = data.split('$')
sentences = ['$' + sentence for sentence in sentences if sentence]
data = '\r\n'.join(sentences) + '\r\n'
parsed_data = pygps2.parse_nmea_sentences(data)
analyzed_data = pygps2.analyze_nmea_data(parsed_data)
print(analyzed_data)
(後述のところ)
ただ実行しているとエラーが出ます。Read Errorみたいのが出ます。sudoで実行するとでないです
パーミッションが原因のようですが、面倒になりやってません。(実行はできる)
あと、タイミングのせいで5秒に一回ぐらいの出力になります。
Raspberry Pi Pico 1/2
import pygps2
from machine import UART, Pin
import time
at6668 = UART(0, baudrate=115200, tx=Pin(0), rx=Pin(1))
while True:
start = time.ticks_cpu()
raw = at6668.read(8192)
if raw is not None:
try:
raw = raw.replace(b'\r', b'').replace(b'\n', b'') # 改行を削除
raw = raw.replace(b'/', b'') # 不要なスラッシュを削除
data = raw.decode("utf-8", "ignore")
except Exception as e:
print(f"error: {e}")
continue
sentences = data.split('$')
sentences = ['$' + sentence for sentence in sentences if sentence]
data = '\r\n'.join(sentences) + '\r\n'
parsed_data = pygps2.parse_nmea_sentences(data)
analyzed_data = pygps2.analyze_nmea_data(parsed_data)
#print(data)
time.sleep(0.01)
機能の有効化
- GPGSV,GNGSVに含まれるQZSSを検出し、typeを"QZS"にする
DETECT_CONVERT_QZS=True
- GSVセンテンスの13番目からバンド情報を取得し、出力する
13番のフィールドがない場合はFalseに設定する
IN_BAND_DATA_INTO_GSV=True
- 同一のGNGSAに複数の衛星システムがある場合に、衛星システムを識別子を取得する
OBTAIN_IDENTIFLER_FROM_GSA=True
最後に
よければお気に入りして帰ってください
参考文献
ライブラリ作成の際参考にさせていただきました。ありがとうございます
解析プログラムの拡張
自分で解析関数を作成して独自の解析プログラムが作りやすいようにしています。
- pygps2.pyに解析関数を作成します
- pygps2.pyのpatternsに解析したいセンテンスを設定します
ここでは出力されるセンテンスをどの解析プログラムにいれるかを設定するところです。
使用するモジュールにあった設定をすることができます
patterns = {
'GGA': re.compile(r'\$GNGGA,.*?\*..|\$GPGGA,.*?\*..|\$BDGGA,.*?\*..'),
'GLL': re.compile(r'\$GNGLL,.*?\*..|\$GPGLL,.*?\*..|\$BDGLL,.*?\*..'),
'GSA': re.compile(r'\$GNGSA,.*?\*..|\$GPGSA,.*?\*..|\$BDGSA,.*?\*..'),
'GSV': re.compile(r'\$GPGSV,.*?\*..|\$BDGSV,.*?\*..|\$GQGSV,.*?\*..|\$GLGSV,.*?\*..|\$GAGSV,.*?\*..'),
'RMC': re.compile(r'\$GNRMC,.*?\*..|\$GPRMC,.*?\*..|\$BDRMC,.*?\*..'),
'VTG': re.compile(r'\$GNVTG,.*?\*..|\$GPVTG,.*?\*..|\$BDVTG,.*?\*..'),
'GST': re.compile(r'\$GNGST,.*?\*..|\$GPGST,.*?\*..|\$BDGST,.*?\*..')
}
ここに自分の解析する名前を辞書形式で追加してください。以下のような形です
例
'DHV': re.compile(r'\$GNDHV,.*?\*..|\$GPDHV,.*?\*..|\$BDDHV,.*?\*..')
, (カンマ)を忘れないこと!!
- 次に解析実行をする関数であるanalyze_nmea_data関数内に移動し以下のようなプログラムを追記します
例
analyzed_data['DHV'] = [parse_gst(sentence) for sentence in parsed_data['GST']] if parsed_data['DHV'] else [parse_gst('')]
これで独自の解析関数が登録、実行できるようになりました。
-
関数の作成方法
センテンスは1行ずつ関数に入れられます。
def parse_dhv(sentence):
fields = sentence.split(',')#カンマで区切り、リスト型にしたほうが処理しやすい
data = {
'timestamp': fields[1] if len(fields) > 1 and fields[1] else '000000.0',
}
return data #基本的に戻り値は辞書で返していいです。リストでもOK
質問等は遠慮なくしてください。
GithubのIssuesでも立てていいですよ