災危通報の解析
メッセージフォーマット
災危通報のメッセージは、0xB5 0x62
から始まるUBXプロトコルのバイナリ形式で出力され、必ず以下の形で始まります。
b5 -> UBX Preamble sync character 1
62 -> UBX Preamble sync character 2
02 -> Message Class (RBX)
13 -> Message ID (SFRBX)
2c 00 -> Payload Length (Little Endian, 44 bytes)
05 -> GNSS ID (5=QZSS)
NMEAセンテンスは \r\n
の改行コードで終わりますが、UBXプロトコルのバイナリ形式メッセージはヘッダの Payload Length
を読んでメッセージの終端を得る必要があります。
上記のヘッダに続き、メッセージごとのデータおよびチェックサムが入っています。
07 -> Satellite ID (7+182=189=QZS03)
01 -> Signal ID (1=L1S)
00 -> Frequency ID (Only used for GLONASS)
09 -> The number of data words contained in this message (8+9*4=44)
45 -> Tracking channel number
02 -> Message version (0x02)
00 -> Reserved 0
55f5ad9a170580110000008e00000000000000000000000010000000b1aa5aebff9483b2 -> 災危通報データ
e2 -> ck_a (Checksum)
cd -> ck_b (Checksum)
UBXプロトコルに関する詳細については、以下のu-blox社のドキュメントを参照してください。
M10 firmware 5.10 interface description (PDF, 2.28MiB)
災危通報メッセージは衛星航法データの出力 UBX-RXM-SFRBX
を有効にすると出力されます。
GR-M10-C/Sでは、出荷時に有効にする設定をBBR(Battery Backup RAM)に書き込んでいるため設定作業は不要です。
内蔵バックアップ電池の取り外し、消耗による交換をした場合は再設定が必要です。
出荷時にバックアップ電池を取り付けていないGR-M10-B/S-B45、GR-M10-RPでは、初期状態で衛星航法データの出力 UBX-RXM-SFRBX
が無効になっています。
以下のサンプルコードでは、すべてのGR-M10シリーズ受信機に対応させるために UBX-RXM-SFRBX
を有効にするコマンドを実行時の最初に送信しています。
センテンスへの変換
UBXプロトコルのバイナリ形式で出力される災危通報メッセージを ユーザインタフェース仕様書(災害・危機管理通報サービス, IS-QZSS-DCR-010)の 4.3.1. Sentence format
で示されている $QZQSM
から始まるNMEA形式のセンテンスに変換します。
Python3
以下はPython3で災危通報のセンテンスを出力するサンプルコードです。
import sys
import argparse
import operator
from functools import reduce
import serial
import time
VAL_SET_RAM_UBX_RXM_SFRBX_UART1_ON = bytes([0xB5, 0x62, 0x06, 0x8A, 0x09, 0x00, 0x01, 0x01, 0x00, 0x00, 0x32, 0x02, 0x91, 0x20, 0x01, 0x81, 0x30])
satellite_id = {
184: '56',
185: '57',
189: '61',
183: '55',
186: '58',
}
def nmea_checksum(sentence):
data = sentence.strip("$").split('*', 1)[0]
cksum = reduce(operator.xor, (ord(s) for s in data), 0)
return cksum
def ubx_checksum(message):
ck_a = 0
ck_b = 0
i = 0
while i < len(message):
ck_a = (ck_a + message[i]) & 0xff
ck_b = (ck_b + ck_a) & 0xff
i += 1
return ck_a, ck_b
def ubx2qzqsm(line):
if line[:7] == b'\xB5\x62\x02\x13\x2C\x00\x05': # UBX-RXM-SFRBX, 44 bytes, QZSS
satId = satellite_id[line[7] + 182] # PRN -> Satellite ID
data = b''
for i in range(9):
data += bytes((line[14+3+i*4], line[14+2+i*4], line[14+1+i*4], line[14+0+i*4]))
if data[1] >> 2 == 43 or data[1] >> 2 == 44: # Message Type 43=JMA-DC Report, 44=Other
dcr_message = (data[:31] + bytes((data[31] & 0xC0,))).hex()[:-1] # 256-4=252 bit
sentence = '$QZQSM,' + satId + ',' + dcr_message + '*'
return sentence + format(nmea_checksum(sentence), 'x')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Print QZQSM NMEA format sentence')
parser.add_argument('port', help='serial port. ex: /dev/ttyUSB0')
parser.add_argument('baudrate', help='baudrate. ex: 115200')
parser.add_argument('-n', '--nmea', help='print other standard NMEA sentence', action='store_true')
args = parser.parse_args()
with serial.Serial(args.port, args.baudrate) as ser:
print('initializing...')
ser.write(VAL_SET_RAM_UBX_RXM_SFRBX_UART1_ON) # UBX-RXM-SFRBX Output ON
time.sleep(1)
print('start!')
while True:
line = b''
nmea_flag = False
ubx_flag = False
count = 0
payload_length = 0
while True:
if ubx_flag:
if count > 4 and payload_length == 0:
payload_length = int.from_bytes(line[4:5], "little")
if payload_length > 0 and count == payload_length + 8: # header 6 bytes + checksum 2 bytes
break
b = ser.read()
if b == b'$' and not ubx_flag:
nmea_flag = True
if b == b'\x62' and line == b'\xB5':
ubx_flag = True
if b == b'\n':
if line.endswith(b'\r'):
line += b
break
else:
line += b
else:
line += b
count += 1
if args.nmea and nmea_flag:
sentence = line.decode().strip('\r\n')
ck = nmea_checksum(sentence)
if format(ck, 'x') == sentence.split('*', 1)[1]:
print(sentence)
if ubx_flag:
ck_a, ck_b = ubx_checksum(line[2:payload_length+6])
if line[-2] == ck_a and line[-1] == ck_b:
sentence = ubx2qzqsm(line)
if sentence:
print(sentence)
実行
PySerialをインストールしていない場合はpipでインストールしてください。
pip install pyserial
以下のように、シリアルポートとボーレートを指定すると $QZQSM
から始まるセンテンスを出力します。
python read.py /dev/tty.usbserial1410 115200
$QZQSM,57,9aadf5b1118002c3f2587f8b101962082c41a588acb1181623500012b979380*20
GR-M10-B/S-B45、GR-M10-RPでは初期状態でボーレートが 9600bps
または 38400bps
(2024年6月以降出荷分) になっているため、以下のように実行してください。
python read.py /dev/tty.usbserial1410 9600
または
python read.py /dev/tty.usbserial1410 38400
115200bpsで受信する場合は、設定が必要です。
-n
オプションでNMEA形式の他のセンテンスも出力します。
詳しくは、 -h
オプションで表示されるヘルプを参照してください。
usage: read.py [-h] [-n] port baudrate
Print QZQSM NMEA format sentence
positional arguments:
port serial port. ex: /dev/ttyUSB0
baudrate baudrate. ex: 115200
options:
-h, --help show this help message and exit
-n, --nmea print other standard NMEA sentence
Golang
以下はGo言語で災危通報のセンテンスを出力するサンプルコードです。
package main
import (
"io"
"flag"
"fmt"
"strings"
"bytes"
"log"
"time"
"github.com/tarm/serial"
)
var satelliteID = map[byte]string{
183: "55", // QZS01
184: "56", // QZS02
185: "57", // QZS04
186: "58", // QZS1R
189: "61", // QZS03
}
// VAL_SET_RAM_UBX_RXM_SFRBX_UART1_ON
var valSetRamUbxRxmsfrbxUart1On = []byte{0xB5, 0x62, 0x06, 0x8A, 0x09, 0x00, 0x01, 0x01, 0x00, 0x00, 0x32, 0x02, 0x91, 0x20, 0x01, 0x81, 0x30}
func calculateNmeaChecksum(sentence string) string {
data := sentence[1:]
var cksum uint8
for i := 0; i < len(data); i++ {
cksum ^= data[i]
}
return fmt.Sprintf("%02X", cksum)
}
func calculateUbxChecksum(message []byte) []byte {
var ck_a byte
var ck_b byte
for i:= 0; i < len(message); i++ {
ck_a = (ck_a + message[i]) & 0xff
ck_b = (ck_b + ck_a) & 0xff
}
return []byte{ck_a, ck_b}
}
func main() {
port := flag.String("port", "/dev/ttyUSB0", "serial port. ex: /dev/ttyUSB0")
baud := flag.Int("baud", 9600, "baudrate. ex: 9600")
nmea := flag.Bool("n", false, "print other standard NMEA sentence")
flag.Parse()
config := &serial.Config{Name: *port, Baud: *baud}
s, err := serial.OpenPort(config)
if err != nil {
panic(err)
}
defer s.Close()
_, err = s.Write(valSetRamUbxRxmsfrbxUart1On)
if err != nil {
log.Fatalf("s.Write: %v", err)
}
fmt.Println("Sent VAL_SET_RAM_UBX_RXM_SFRBX_UART1_ON")
time.Sleep(100 * time.Millisecond)
ubxHeader := []byte{0xB5, 0x62}
buf := make([]byte, 1024)
index := 0
messageLength := 0
readingMessage := false
for {
b := make([]byte, 1)
_, err := s.Read(b)
if err != nil {
if err == io.EOF {
continue
} else {
panic(err)
}
}
if !readingMessage && index == 0 && b[0] == ubxHeader[0] {
readingMessage = true
buf[index] = b[0]
index++
continue
} else if readingMessage && index == 1 && b[0] == ubxHeader[1] {
buf[index] = b[0]
index++
continue
}
if readingMessage {
buf[index] = b[0]
index++
if index == 6 {
messageLength = int(buf[4]) | int(buf[5])<<8
}
if index == messageLength+8 {
readingMessage = false
message := buf[:index]
ck := calculateUbxChecksum(message[2 : messageLength+6])
if message[len(message)-2] == ck[0] && message[len(message)-1] == ck[1] {
// UBX-RXM-SFRBX, 44 bytes, QZSS
if bytes.Equal(message[2:7], []byte{0x02, 0x13, 0x2C, 0x00, 0x05}) {
// PRN -> Satellite ID
satID := satelliteID[message[7]+182]
data := make([]byte, 0, 36)
for i := 0; i < 9; i++ {
data = append(data, message[14+3+i*4], message[14+2+i*4], message[14+1+i*4], message[14+0+i*4])
}
// Message Type 43=JMA-DC Report, 44=Other Organization
if data[1]>>2 == 43 || data[1]>>2 == 44 {
dcrMessage := fmt.Sprintf("%X%02X", data[:31], data[31]&0xC0)
sentence := fmt.Sprintf("$QZQSM,%s,%s", satID, dcrMessage[:len(dcrMessage)-1])
cksum := calculateNmeaChecksum(sentence)
fmt.Printf("%s*%s\r\n", sentence, cksum)
}
}
}
index = 0
messageLength = 0
}
} else {
if b[0] == '$' && *nmea {
nmeaSentence := string(b)
for {
_, err := s.Read(b)
if err != nil {
if err == io.EOF {
continue
} else {
panic(err)
}
}
nmeaSentence += string(b)
if b[0] == '\n' {
break
}
}
ckIndex := strings.Index(nmeaSentence, "*")
if ckIndex != -1 {
ck := calculateNmeaChecksum(nmeaSentence[:ckIndex])
if ck == nmeaSentence[ckIndex+1:ckIndex+3] {
fmt.Printf("%s", nmeaSentence)
}
}
}
}
}
}
実行
必要なライブラリをインストールしてください。
go mod init read
go get github.com/tarm/serial
以下のように、シリアルポートとボーレートを指定すると $QZQSM
から始まるセンテンスを出力します。
go run read.go -port /dev/tty.usbserial1410 -baud 115200
または、コンパイルして実行してください。
go build
./read -port /dev/tty.usbserial1410 -baud 115200
GR-M10-B/S-B45、GR-M10-RPでは初期状態でボーレートが 9600bps
または 38400bps
(2024年6月以降出荷分) になっているため、以下のように実行してください。
go run read.go -port /dev/tty.usbserial1410 -baud 9600
または
go run read.go -port /dev/tty.usbserial1410 -baud 38400
115200bpsで受信する場合は、設定が必要です。
-n
オプションでNMEA形式の他のセンテンスも出力します。
詳しくは、 -h
オプションで表示されるヘルプを参照してください。
Usage of ./read:
-baud int
baudrate. ex: 9600 (default 9600)
-n print other standard NMEA sentence
-port string
serial port. ex: /dev/ttyUSB0 (default "/dev/ttyUSB0")
センテンスの解析
以下のライブラリを使用することで、センテンスを文字情報に変換することができます。
nbtk/azarashi: QZSS DCR Decoder
インストール
pip install azarashi
使用方法
sentenceに上記サンプルコードで得た $QZQSM
から始まるセンテンスを入れてください。
import azarashi
sentence = '$QZQSM,57,9aadf5b1118002c3f2587f8b101962082c41a588acb1181623500012b979380*20'
report = azarashi.decode(sentence, msg_type='spresense')
print(report)
防災気象情報(海上)(発表)(通常)
海上警報が発表されました。
発表時刻: 11月12日17時35分
警報等情報要素: 海上濃霧警報
サハリン東方海上
警報等情報要素: 海上濃霧警報
サハリン西方海上
警報等情報要素: 海上濃霧警報
網走沖
警報等情報要素: 海上濃霧警報
宗谷海峡
警報等情報要素: 海上濃霧警報
北海道西方海上
警報等情報要素: 海上濃霧警報
北海道東方海上
警報等情報要素: 海上濃霧警報
釧路沖
警報等情報要素: 海上濃霧警報
日高沖
過去の災危通報データ
過去の配信データは以下に保存しています。ライブラリや自作プログラムの出力テストにご利用ください。