Raw data format of GNSS receivers (NovAtel, bynav, and unicore communications receivers)
Data exchange of navigation satellite receivers
NMEA-0183 format (text data) is the de facto standard and is widely used for data representation in GNSS (global navigation satellite system) satellite navigation.
For precision satellite positioning such as RTK (realtime kinematic) which can express satellite signals more accurately, RTCM (Radio Technical Commission for Maritime Services) format (binary data) and RINEX (receiver independent exchange) format (text data) format are used.
Raw data format in GNSS receivers
To help users get more details of their satellite signals, receiver manufacturers sometimes make it possible to output the raw satellite signal message data received and the intermediate data processed by the receiver. These raw data not only help us understand positioning, but also have potential for new applications.
Such raw data is proprietary to the receiver manufacturer. The raw data format is often different for each receiver manufacturer, but some are compatible with specific manufacturers. The bynav C1-FS receiver and unicore UM982 receiver have the raw data format and the commands compatible with NovAtel OEM729 receiver ones.
I use RTKLIB ver.2.4.3b34 for the output data processing of the GNSS receiver. I am very happy that RTKLIB’s NovAtel receiver (OEM7) settings are available for raw data processing of C1-FS receiver and UM982 receiver.
However, raw data output commands between different receivers are not always fully compatible. Professor Tomoji Takasu of Tokyo University of Marine Science and Technology, who is also the creator of RTKLIB, added additional raw data decoding function to the NovAtel receiver settings for using bynav receivers.
Compatibility of NovAtel raw data format
Therefore, the compatibility of raw data output commands between RTKLIB’s NovAtel receiver settings, NovAtel receivers, bynav receivers, and unicore receivers was tabulated using Microsoft Excel (novatel_binary.xlsx). Using Microsoft Excel’s filter button is convenient because you can, for example, extract only the commands that RTKLIB supports.
When handling the binary data of this NovAtel receiver, add b
to the end of this command name in addition to log
. For example, to use the rangecmp
command to output observed pseudorange data in compressed format, send the string log rangecmpb ontime 0.5
to the receiver. Here, ontime 0.5 means output observation data every 0.5 seconds.
Some of these commands are assigned a message ID. Here, if this message ID is the same between different receivers, they are counted as the same message. Even if the message ID is the same, the format may be different between different receivers.
NovAtel receivers have a large number of raw data display commands. Therefore, bynav receivers, unicore receivers, and all the commands supported by RTKLIB are listed, and the correspondence with the NovAtel receiver’s message ID is examined. Therefore, the NovAtel receiver raw data display commands listed here are part of a whole.
From this table, we can see that there are messages with the same message ID but different command names, and messages with the same command name but different message IDs (original messages).
The table also states that the Galileo satellite ephemeris output command galephemeris
(message ID 1122) can be interpreted by NovAtel receivers. The OEM7 Commands and Logs Reference Manual v22 (released on November 2022) no longer describes this command. However, since galephemeris
can be used even with the latest firmware (7.08.14) at the moment, it is indicated that it can be interpreted in this table.(added on 2023-01-24)
In fact, at the moment, the control command and raw data format of the unicore receiver are not published on the manufacturer’s site, and the materials found on the Internet are used. Therefore, it is inaccurate for unicore receivers (for example, there are two message ids for OBSVM
). I will try them with my unicore UM982 receiver.
NovAtel raw data display code
I wrote a Python code to display the message name of NovAtel raw data. Save the following code as a filename such as novdump.py
.
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
#
# novdump.py: NovAtel receiver raw message dump
#
# Copyright (c) 2022 Satoshi Takahashi, all rights reserved.
#
# Released under BSD 2-clause license.
import sys
class Nov:
def read_nov(self):
sync = [b'0x00' for i in range(3)]
ok = False
try:
while not ok:
b = sys.stdin.buffer.read(1)
if not b:
return False
sync = sync[1:3] + [b]
if sync == [b'\xaa', b'\x44', b'\x12']:
ok = True
head = sys.stdin.buffer.read(1+2+1+1+2+2+1+1+2+4+4+2+2+4)
if not head:
return False
self.parse_head(head)
payload = sys.stdin.buffer.read(self.msg_len)
if not payload:
return False
self.payload = payload
crc = sys.stdin.buffer.read(4)
if not crc:
return False
except KeyboardInterrupt:
sys.exit()
return True
def parse_head(self, head):
pos = 0
self.head_len = int.from_bytes(head[pos:pos+1], 'little')
pos += 1
self.msg_id = int.from_bytes(head[pos:pos+2], 'little')
pos += 2
self.msg_type = int.from_bytes(head[pos:pos+1], 'little')
pos += 1
self.port = int.from_bytes(head[pos:pos+1], 'little')
pos += 1
self.msg_len = int.from_bytes(head[pos:pos+2], 'little')
pos += 2
self.seq = int.from_bytes(head[pos:pos+2], 'little')
pos += 2
self.t_idle = int.from_bytes(head[pos:pos+1], 'little')
pos += 1
self.t_stat = int.from_bytes(head[pos:pos+1], 'little')
pos += 1
self.gpsw = int.from_bytes(head[pos:pos+2], 'little')
pos += 2
self.gpst = int.from_bytes(head[pos:pos+4], 'little') / 1e4
pos += 4
self.stat = int.from_bytes(head[pos:pos+4], 'little')
pos += 4
self.reserved = int.from_bytes(head[pos:pos+2], 'little')
pos += 2
self.ver = int.from_bytes(head[pos:pos+2], 'little')
pos += 2
self.response = int.from_bytes(head[pos:pos+4], 'little')
pos += 4
def msgid(self):
return self.msg_id
def msgname(self, msg_id=0):
if msg_id == 0:
msg_id = self.msg_id
msg_name = 'unknown'
if msg_id == 43:
msg_name ='RANGE'
elif msg_id == 41:
msg_name = 'RAWEPHEM'
elif msg_id == 8:
msg_name = 'IONUTC'
elif msg_id == 723:
msg_name = 'GLOEPHEMERIS'
elif msg_id == 1330:
msg_name = 'QZSSRAWSUBFRAME'
elif msg_id == 1347:
msg_name ='QZSSIONUTC'
elif msg_id == 1122:
msg_name ='GALEPHEMERIS'
elif msg_id == 1696:
msg_name ='BDSEPHEMERIS'
elif msg_id == 2123:
msg_name ='NAVICEPHEMERIS'
elif msg_id == 140:
msg_name = 'RANGECMP'
elif msg_id == 287:
msg_name ='RAWWAASFRAME'
elif msg_id == 973:
msg_name = 'RAWSBASFRAME'
elif msg_id == 1127:
msg_name = 'GALIONO'
elif msg_id == 1121:
msg_name = 'GALCLOCK'
elif msg_id == 1331:
msg_name = 'QZSSRAWEPHEM'
return msg_name
def msglen(self):
return self.msg_len
if __name__ == '__main__':
nov = Nov()
while nov.read_nov():
print(f'MT{nov.msgid():<4d} {nov.msgname():17s} {nov.msglen()} bytes')
# EOF
Raw data may contain secret messages from navigation satellite operators. I would like to analyze the raw data bit by bit.