Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion HAS_Converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def printHelp():
print("-t arg : Target stream to decode messages to")
print("-f opt : Format to convert HAS messages to. Options are [1:IGS, 2:RTCM3]")
print("-i opt : Input mode, specifying the type of input stream. Options are :",
"\n [1:SBF File, 2:BINEX File, 3:SBF Serial, 4:BINEX Serial, 5:SBF TCP, 6:BINEX TCP]")
"\n [1:SBF File, 2:BINEX File, 3:SBF Serial, 4:BINEX Serial, 5:SBF TCP, 6:BINEX TCP, 7:NOV-A File]")
print("-o opt : Output mode, specifying the type of output stream. Options are:",
"\n [1:TCP, 2:File, 3:PPPWiz File, 4:PPPWiz Stream]")
print("-p arg : Optional for TCP output. If not set, uses port 6947")
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Parameters for the `HAS_Converter` are as follows:
* `source`: The source. Can be a filename/path or portname.
* `target`: The output target. Can be a filename/path or an IP address for a TCP server.
* `out_format`: The format of the output. Options are [1:IGS, 2:RTCM3]
* `modeIn`: Optional. Determining the mode of input. If not set, looks for file endings. Options are [1:SBF File, 2:BINEX File, 3:SBF Serial, 4:BINEX Serial, 5:SBF TCP, 6:BINEX TCP]
* `modeIn`: Optional. Determining the mode of input. If not set, looks for file endings. Options are [1:SBF File, 2:BINEX File, 3:SBF Serial, 4:BINEX Serial, 5:SBF TCP, 6:BINEX TCP, 7:Novatel ASCII]
* `modeOut`: Optional. Determining the mode of output. If not set, decides based on all-numeric IP addresses (excl. dots)/localhost or not. Options are: [1:TCP, 2:File, 3:PPPWiz File, 4:PPPWiz Stream]
* `x`: Optional parameter. Used to indicate a maximum number of navigation messages to read. This includes all GNSS messages and is not limited to Galileo HAS messages.
* `port`: Optional parameter for TCP output. If not set, uses port 6947
Expand All @@ -64,7 +64,7 @@ This command sets up a decoder with exactly the same settings as shown in the pr
* -s arg : Source stream to decode messages from
* -t arg : Target stream to decode messages to
* -f opt : Format to convert HAS messages to. Options are [1:IGS, 2:RTCM3]
* -i opt : Input mode, specifying the type of input stream. Options are : [1:SBF File, 2:BINEX File, 3:SBF Serial, 4:BINEX Serial, 5:SBF TCP, 6:BINEX TCP]
* -i opt : Input mode, specifying the type of input stream. Options are : [1:SBF File, 2:BINEX File, 3:SBF Serial, 4:BINEX Serial, 5:SBF TCP, 6:BINEX TCP, 7:Novatel ASCII]
* -o opt : Output mode, specifying the type of output stream. Options are: [1:TCP, 2:File, 3:PPPWiz File, 4:PPPWiz Stream]
* -p arg : Optional for TCP output. If not set, uses port 6947
* -b arg : Optional for serial input, specifying the baudrate of the stream. If not set, uses 115200
Expand Down
Binary file added Tests/NovAsciiTestRecordings.zip
Binary file not shown.
4 changes: 4 additions & 0 deletions Tests/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ True location (converted from DMS) for FGI-Otaniemi Septentrio Antenna:
* trueLat = 60.182260;
* trueLong = 24.828537;
* trueHeight = 47.248;

NovAsciiTestRecordings.zip contains two test recordings that were collected at University of Vaasa campus with Novatel PwrPak7 receiver.
- gal_e6_raw_cnav_pages_2023-09-22_093700_UTC.txt with receiver firmware 7.08.10
- gal_e6_raw_cnav_pages_2023-10-04_071500_UTC.txt with receiver firmware 7.09.00
11 changes: 8 additions & 3 deletions galileo_has_decoder/conv.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

VER DATE AUTHOR
1.0 09/12/2021 Oliver Horst / FGI
Modified 2023-09-29 Jaakko Yliaho / Uwasa to add NOV_Reader
'''

from galileo_has_decoder.sbf_reading import SBF_Reader
from galileo_has_decoder.binex_reading import Binex_Reader
from galileo_has_decoder.tcp_sbf_reading import TCP_SBF_Reader
from galileo_has_decoder.tcp_binex_reading import TCP_Binex_Reader
from galileo_has_decoder.serial_reading import Serial_SBF_Reader, Serial_Binex_Reader
from galileo_has_decoder.nov_reading import NOV_Reader
from galileo_has_decoder.tcp_server import TCP_Server
from galileo_has_decoder.ssr_converter import SSR_Converter
from galileo_has_decoder.file_write import File_Writer, PPP_Wiz_Writer
Expand Down Expand Up @@ -66,6 +68,9 @@ def __init__(self, source, target, outFormat, modeIn=None, modeOut=None, port=No
elif modeIn == 6:
inp = "BINEX TCP stream on " + str(source)
self.reader = TCP_Binex_Reader(source)
elif modeIn == 7:
inp = "Novatel GALCNAVRAWPAGE ASCII file"
self.reader = NOV_Reader(source, skip=float(skip))
#Target Initialization
if modeOut == None:
if target.replace(".", "").isnumeric() or target == 'localhost':
Expand Down Expand Up @@ -110,7 +115,7 @@ def convertAll(self, compact=True, HRclk=False, lowerUDI=True, verbose=0):
#Convert all messages available from the source
if verbose != 0 and self.converter != None:
self.converter.setVerbose(verbose)
if self.modeIn == 1 or self.modeIn == 2 or self.modeIn == 5 or self.modeIn == 6:
if self.modeIn == 1 or self.modeIn == 2 or self.modeIn == 5 or self.modeIn == 6 or self.modeIn == 7:
self.reader.read(converter=self.converter, output=self.output, compact=compact, HRclk=HRclk, verbose=verbose)
elif self.modeIn == 3 or self.modeIn == 4:
self.reader.read(converter=self.converter, output=self.output, compact=compact, HRclk=HRclk, verbose=verbose)
Expand All @@ -120,7 +125,7 @@ def convertX(self, x, compact=True, HRclk=False, lowerUDI=True, verbose=0):
if verbose != 0 and self.converter != None:
self.converter.setVerbose(verbose)
#Convert X messages from the source
if self.modeIn == 1 or self.modeIn == 2 or self.modeIn == 5 or self.modeIn == 6:
if self.modeIn == 1 or self.modeIn == 2 or self.modeIn == 5 or self.modeIn == 6 or self.modeIn == 7:
self.reader.read(converter=self.converter, output=self.output, x=x, compact=compact, HRclk=HRclk, verbose=verbose)
elif self.modeIn == 3 or self.modeIn == 4:
self.reader.read(converter=self.converter, output=self.output, x=x, compact=compact, HRclk=HRclk, verbose=verbose)
Expand All @@ -130,7 +135,7 @@ def convertUntil(self, s, compact=True, HRclk=False, lowerUDI=True, verbose=0):
if verbose != 0 and self.converter != None:
self.converter.setVerbose(verbose)
#Convert messages from the source for s seconds
if self.modeIn == 1 or self.modeIn == 2:
if self.modeIn == 1 or self.modeIn == 2 or self.modeIn == 7:
raise Mode_Error("ERROR: Timed constraint not available for file reading.")
elif self.modeIn >= 3 and self.modeIn <= 6:
self.reader.read(converter=self.converter, output=self.output, mode="t", x=s, compact=compact, HRclk=HRclk, verbose=verbose)
Expand Down
184 changes: 184 additions & 0 deletions galileo_has_decoder/nov_reading.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env python

'''
Novatel GALCNAVRAWPAGE file reading class

VER DATE AUTHOR
1.0.x 2023-09-29 Jaakko Yliaho / Uwasa
'''

import struct
import math
import binascii
from galileo_has_decoder.utils import bits2Bytes, gpst2time
from galileo_has_decoder.has_classes import HAS_Storage
from galileo_has_decoder.ssr_converter import SSR_Converter
from galileo_has_decoder.tcp_server import TCP_Server

class FileError(Exception):
#Base File Error class
pass

class NOV_Reader:
file = None
has_storage = None
def __init__(self, path, msgnum=0, skip=0):
self.file = open(path, "r")
lines = self.file.readlines()
self.lines = lines[int(skip):]
self.numberOfRawPages = len(self.lines)
self.has_storage = HAS_Storage()
self.msgnum = msgnum
self.pppWiz = False

# Calculate CRC for single byte
@staticmethod
def crc(byte):
for j in range(8):
if ((byte & 1) & 0xFF):
byte = ((byte >> 1)) ^ 0xEDB88320
else:
byte = (byte >> 1)
return byte

# Calculate CRC for bytestream
@staticmethod
def CalculateBlockCRC32(bytestream):
result = 0x00000000
for byte in bytestream:
temp1 = (result >> 8) & 0x00FFFFFF
temp2 = NOV_Reader.crc((result ^ byte ) & 0xFF)
result = temp1 ^ temp2
return result

def read(self, path=None, converter=None, output=None, mode='m', x=None, compact=True, HRclk=False, lowerUDI=True, verbose=0):
if mode != 'm':
raise Exception("File Reading does only support message-number constraints")
if x != None: self.msgnum = x

if converter is not None:
if converter.pppWiz:
self.output = output
self.pppWiz = True

cnavs = 0
hasnum=0
currentLineNumber=0

for logMessage in self.lines:
logMessageFields=logMessage.split(";")
if len(logMessageFields) != 2:
if verbose >= 5:
print("NOV-A Reader: Invalid line format on line " + str(currentLineNumber+1))
currentLineNumber+=1
continue

headerFields=logMessageFields[0].split(",")
if len(headerFields) != 10:
if verbose >= 5:
print("NOV-A Reader: Invalid line format, wrong number of header fields, on line " + str(currentLineNumber+1))
currentLineNumber+=1
continue

dataCrcFields=logMessageFields[1].split("*")
if len(dataCrcFields) != 2:
if verbose >= 5:
print("NOV-A Reader: Invalid line format, CRC missing, on line " + str(currentLineNumber+1))
currentLineNumber+=1
continue

dataFields=dataCrcFields[0].split(",")
crcField=str.rstrip(dataCrcFields[1])

headerMessage=headerFields[0]
if headerMessage != "#GALCNAVRAWPAGEA" and "#GALCNAVRAWPAGEA" in headerMessage:
headerMessage="#GALCNAVRAWPAGEA"

if headerMessage != "#GALCNAVRAWPAGEA":
if verbose >= 6:
print("NOV-A Reader, line "+str(currentLineNumber)+", not a raw C/NAV page: "+str(headerMessage))
currentLineNumber+=1
continue

# Compute and check log message CRC
# C/NAV raw data CRC has already been checked by the receiver.
# Only C/NAV messages that pass CRC validation are outputted by logging
messageToCheck=headerMessage[1:]+','+','.join(headerFields[1:])+';'+','.join(dataFields)
calculatedCrc=NOV_Reader.CalculateBlockCRC32(bytes(messageToCheck, 'utf-8'))
if (binascii.unhexlify(crcField) != int.to_bytes(calculatedCrc, 4, 'big')):
if verbose >= 5:
print("NOV-A Reader: Invalid CRC on line " + str(currentLineNumber+1))
currentLineNumber+=1
continue

if len(dataFields) != 5 and len(dataFields) != 4:
if verbose >= 5:
print("NOV-A Reader: Invalid line format, wrong number of data fields, on line " + str(currentLineNumber+1))
currentLineNumber+=1
continue

headerPort=headerFields[1]
headerSequence=headerFields[2]
headerIdleTime=headerFields[3]
headerTimeStatus=headerFields[4]
headerWeek=headerFields[5]
headerSeconds=headerFields[6]
headerReceiverStatus=headerFields[7]
headerReserved=headerFields[8]
headerReceiverSWVersion=headerFields[9]

dataSignalChannel=dataFields[0]
dataPRN=dataFields[1]
dataMessageID=dataFields[2]
dataPageID=""
dataRawCNAV=""

# SW version 17022(7.08.14) implemented new ASCII logging format with four data fields before raw C/NAV data
# Before version 17022 it was only three data fields before C/NAV raw data
# Check that logged software version matches the number of data fields logged
if int(headerReceiverSWVersion) < 17022:
if len(dataFields) != 4:
if verbose >= 5:
print("NOV-A Reader: Invalid line format, wrong number of data fields, on line " + str(currentLineNumber+1))
currentLineNumber+=1
continue

dataPageID=""
dataRawCNAV=dataFields[3]
else:
dataPageID=dataFields[3]
dataRawCNAV=dataFields[4]

# All the checks done. This is an OK raw C/NAV page, increase the counter
cnavs+=1

rawCNAVBinaryString=''.join(format(x, '08b') for x in binascii.unhexlify(dataRawCNAV))
# Remove the two extra bits at the end that are added for the ascii-hex representation of 464 bits instead of the C/NAV 462
rawCNAVBinaryString=rawCNAVBinaryString[0:-2]

if verbose >= 6:
print("NOV-A Reader, line "+str(currentLineNumber+1)+", header: "+str(headerFields)+", data: "+str(dataFields)+", crc: "+crcField+", bin: "+rawCNAVBinaryString)

if self.has_storage.feedMessage(rawCNAVBinaryString, float(headerSeconds), verbose=verbose):
hasnum+=1
if converter != None:
decoded_msg = self.has_storage.lastMessage
tow = self.has_storage.lastMessage_tow
if verbose >= 6:
print("NOV-A Reader, decoded_msg "+str(decoded_msg)+", tow: "+str(tow)+", tow2: "+str(float(headerSeconds)))
converted = converter.convertMessage(decoded_msg, compact=compact, HRclk=HRclk, tow=tow, lowerUDI=lowerUDI, verbose=verbose)
if output != None and converted != None:
for msg_conv in converted:
msg_bytes = bits2Bytes(msg_conv)
gpst2time(int(headerWeek), float(headerSeconds))
if self.pppWiz:
output.write(msg_bytes, 2, 1, gpst2time(int(headerWeek), float(headerSeconds)))
else:
output.write(msg_bytes)

currentLineNumber+=1
continue

if verbose>=1:
print("Out of "+str(currentLineNumber)+" messages, "+str(cnavs)+" were C/Nav messages. "+str(hasnum)
+" HAS messages have successfully been decoded and converted.")
4 changes: 3 additions & 1 deletion galileo_has_decoder/readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

VER DATE AUTHOR
1.0 09/12/2021 Oliver Horst / FGI
Modified 2023-09-29 Jaakko Yliaho / Uwasa to add NOV_Reader
'''

from galileo_has_decoder.sbf_reading import SBF_Reader
from galileo_has_decoder.binex_reading import Binex_Reader
from galileo_has_decoder.tcp_sbf_reading import TCP_SBF_Reader
from galileo_has_decoder.tcp_binex_reading import TCP_Binex_Reader
from galileo_has_decoder.serial_reading import Serial_SBF_Reader, Serial_Binex_Reader
from galileo_has_decoder.serial_reading import Serial_SBF_Reader, Serial_Binex_Reader
from galileo_has_decoder.nov_reading import NOV_Reader