Awesome PocketSDR (L6 band signal decode)

category: gnss

Introduction

PocketSDR has been updated to v.0.6. Since the decoding function of the quasi-zenith satellite L6 band signal was added, I tried to decode the L6E (MADOCA) message on my personal computer. PocketSDR is amazing.

Create a shared library

From this version, a shared library of low density parity check (LDPC) is required. PocketSDR ships with shared libraries for Windows and Linux, so this step is not necessary in Windows and Linux environments.

On macOS, you have to create the shared library yourself, but you can easily create this library because there is a Makefile in the lib/ldpc directory of PocketSDR. Simply change the output file extension from .so to .dylib and change the shared library creation option from -shared to -dynamiclib. The shared library for macOS that I created is as follows. Feel free to use it.

pocketsdr_maclib.zip
(updated on 2022-02-19 for PocketSDR version 0.7)

Unzip this ZIP file and you will see the folders darwin_arm and darwin_x86. The former is for M1 Mac and the latter is for Intel Mac. Place these folders in the PocketSDR lib directory.

Due to the security features of your macOS, you may not be able to run the downloaded file as is. In the Finder, “click with two fingers” and execute “Open” to give permission to execute these shared library files.

You would require running once after downloading the macOS shared library

Next, we make PocketSDR’s Python code work with these shared libraries. The method is the same as Awesome PocketSDR (pocket_trk), but the platform module is used to automatically determine the OS. Please rewrite the beginning of the following 3 files as follows.

sdr_fec.py

# load LIBFEC ([1]) ------------------------------------------------------------
dir = os.path.dirname(__file__)
import platform
environment = platform.platform()
if 'Windows' in environment:
    libfec = cdll.LoadLibrary(dir + '/../lib/win32/libfec.so')
elif 'Linux' in environment:
    libfec = cdll.LoadLibrary(dir + '/../lib/linux/libfec.so')
elif 'macOS' in environment and 'x86_64' in environment:
    libfec = cdll.LoadLibrary(dir + '/../lib/darwin_x86/libfec.dylib')
elif 'macOS' in environment and 'arm' in environment:
    libfec = cdll.LoadLibrary(dir + '/../lib/darwin_arm/libfec.dylib')

sdr_ldpc.py

# load library of LDPC-codes ([1],[2]) -----------------------------------------
dir = os.path.dirname(__file__)
import platform
environment = platform.platform()
if 'Windows' in environment:
    libldpc = cdll.LoadLibrary(dir + '/../lib/win32/libldpc.so')
elif 'Linux' in environment:
    libldpc = cdll.LoadLibrary(dir + '/../lib/linux/libldpc.so')
elif 'macOS' in environment and 'x86_64' in environment:
    libldpc = cdll.LoadLibrary(dir + '/../lib/darwin_x86/libldpc.dylib')
elif 'macOS' in environment and 'arm' in environment:
    libldpc = cdll.LoadLibrary(dir + '/../lib/darwin_arm/libldpc.dylib')

sdr_rtk.py

# load RTKLIB ([1]) ------------------------------------------------------------
dir = os.path.dirname(__file__)
import platform
environment = platform.platform()
if 'Windows' in environment:
    librtk = cdll.LoadLibrary(dir + '/../lib/win32/librtk.so')
elif 'Linux' in environment:
    librtk = cdll.LoadLibrary(dir + '/../lib/linux/librtk.so')
elif 'macOS' in environment and 'x86_64' in environment:
    librtk = cdll.LoadLibrary(dir + '/../lib/darwin_x86/librtk.dylib')
elif 'macOS' in environment and 'arm' in environment:
    librtk = cdll.LoadLibrary(dir + '/../lib/darwin_arm/librtk.dylib')

Decoding sample data

Open the sample folder of PocketSDR and get the ZIP format sample data from the URL written in the .link extension file. Unzip these files by using unzip command and put them in this folder. The sample data used this time is L6_20211226_082212_12MHz_IQ.bin.

First, I checked the L6D signal (CLAS: centimeter level augmentation service) that can be observed in this sample data. I executed pocket_rtk.py in the python folder. The meaning of the options is similar to that of pocket_acq.py.

python3 pocket_trk.py ../sample/L6_20211226_082212_12MHz_IQ.bin -prn 193-199 -f 12 -sig L6D
  TIME(s)   SIG  PRN STATE   LOCK(s)  C/N0 (dB-Hz)         COFF(ms)  DOP(Hz)    ADR(cyc) SYNC #NAV #ERR #LOL NER
     0.02   L6D  193  IDLE      0.00   0.0                0.0000000      0.0         0.0  ---    0    0    0   0
     0.05   L6D  194  LOCK      0.03  43.2 ||||||||       3.8254181   -104.9        -3.7  ---    0    0    0   0
     0.05   L6D  195  LOCK      0.03  40.9 |||||||        0.7774230   -250.6        -8.0  ---    0    0    0   0
     0.02   L6D  196  IDLE      0.00   0.0                0.0000000      0.0         0.0  ---    0    0    0   0
     0.02   L6D  197  IDLE      0.00   0.0                0.0000000      0.0         0.0  ---    0    0    0   0
     0.02   L6D  198  IDLE      0.00   0.0                0.0000000      0.0         0.0  ---    0    0    0   0
     0.05   L6D  199  LOCK      0.03  40.9 |||||||        2.0844222   -153.7        -4.4  ---    0    0    0   0

The L6D signals of QZS-2 (PRN 194), QZS-3 (PRN 199), and QZS-4 (PRN 195) could be observed. The accumulated delta range (ADR) is also shown, and we can expected to use this L6D signal for positioning. Here, signal tracking is performed for the QZS-2.

python3 pocket_trk.py ../sample/L6_20211226_082212_12MHz_IQ.bin -prn 194 -f 12 -sig L6D -p

Awesome PocketSDR - L6D signal decode

Next, I decode the L6E signal (MADOCA: multi-GNSS advanced demonstration tool for orbit and clock analysis). The PRN 204, which is the PRN number of QZS-2 L6D signal plus 10, corresponds to this L6E signal.

python3 pocket_trk.py ../sample/L6_20211226_082212_12MHz_IQ.bin -prn 204 -f 12 -sig L6E -p

Save this decoded data in the log file pocket.log.

python3 pocket_trk.py ../sample/L6_20211226_082212_12MHz_IQ.bin -prn 204 -f 12 -sig L6E -log pocket.log

L6E signal decoding

L6D signal and L6E signal messages are transmitted in a message called data part, and it is sent a single message per second.

The target here is the L6E signal, because the message is complete within one data part. For L6D signals, if subtype 1 that is broadcast only once every 30 seconds cannot be decoded, other messages cannot be decoded, and the message may span multiple data parts, so it is hard for me to decode the L6D signals.

This log file stores the signal tracking status and decryption data separated by commas. This L6E signal message is written in hexadecimal text format in column 6 of the line beginning with $L6FRM. I created a Python program to convert this log message into the L6E binary format.

pksdr2l6.py

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import sys

line = sys.stdin.readline().strip()
while (line):
    if line[0:6] != "$L6FRM":
        line = sys.stdin.readline().strip()
        continue
    t_data = line.split(',')[5]
    sys.stdout.buffer.write(bytes.fromhex(t_data))
    sys.stdout.flush()
    line = sys.stdin.readline().strip()

Then, we read this L6E message with the code qzsl62rtcm.py that extracts the payload from the L6 message and converts it into an RTCM 3 (Radio Technical Commission for Maritime Services version 3) message and the code showrtcm.py that displays the contents of the RTCM 3 message.

First, we decode this L6E message.

cat pocket.log | ./pksdr2l6.py | qzsl62rtcm.py > /dev/null
204 Hitachi-Ota:0* 2021-12-26 08:21:58 1059(24)
204 Hitachi-Ota:0* 2021-12-26 08:22:00 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:00 1059(2)
204 Hitachi-Ota:0* 2021-12-26 08:22:02 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:02 1065(19)
204 Hitachi-Ota:0* 2021-12-26 08:22:04 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:04
204 Hitachi-Ota:0* 2021-12-26 08:22:06 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:06
204 Hitachi-Ota:0* 2021-12-26 08:22:08 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:08
204 Hitachi-Ota:0* 2021-12-26 08:22:10 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:10
204 Hitachi-Ota:0* 2021-12-26 08:22:12 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:12 1057(8) 1061(8)
204 Hitachi-Ota:0* 2021-12-26 08:22:14 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:14 1057(8) 1061(8)
204 Hitachi-Ota:0* 2021-12-26 08:22:16 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:16 1057(8) 1061(8)
204 Hitachi-Ota:0* 2021-12-26 08:22:18 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:18
204 Hitachi-Ota:0* 2021-12-26 08:22:20 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:20 1063(8) 1067(8)
204 Hitachi-Ota:0* 2021-12-26 08:22:22 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:22 1063(8) 1067(8)
204 Hitachi-Ota:0* 2021-12-26 08:22:24 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:24 1063(1) 1067(1)
204 Hitachi-Ota:0* 2021-12-26 08:22:26 1062(24) 1068(17)
204 Hitachi-Ota:0* 2021-12-26 08:22:26

It shows the contents of one data part per line. Each line contains the PRN number, control station (Hitachi-ota or Kobe) and system number (0 or 1), alert flag status (asterisk shows alert on status), UTC date and time, RTCM message number and number of satellites to be augmented. One data part sometimes contains two RTCM messages. Then we decode this RTCM 3 message.

cat pocket.log | ./pksdr2l6.py | qzsl62rtcm.py 2> /dev/null | showrtcm.py

Some of the execution results are as follows. I placed all RTCM 3 messages that you can observe in the last 30 seconds in pocket.rtcm3.txt.

RTCM 1059 G SSR code bias  G01 G02 G03 G05 G06 G07 G08 G09 G10 G12 G13 G15 G16 G17 G19 G20 G21 G22 G24 G25 G26 G27 G29 G30 (nsat=24 iod=4 cont.)
RTCM 1062 G SSR hr clock   G01 G02 G03 G05 G06 G07 G08 G09 G10 G12 G13 G15 G16 G17 G19 G20 G21 G24 G25 G26 G29 G30 G31 G32 (nsat=24 iod=4)
RTCM 1068 R SSR hr clock   R01 R02 R03 R04 R05 R07 R08 R12 R13 R14 R15 R17 R18 R19 R20 R21 R22 (nsat=17 iod=5)
RTCM 1059 G SSR code bias  G31 G32 (nsat=2 iod=4)
RTCM 1062 G SSR hr clock   G01 G02 G03 G05 G06 G07 G08 G09 G10 G12 G13 G15 G16 G17 G19 G20 G21 G24 G25 G26 G29 G30 G31 G32 (nsat=24 iod=4)
RTCM 1068 R SSR hr clock   R01 R02 R03 R04 R05 R07 R08 R12 R13 R14 R15 R17 R18 R19 R20 R21 R22 (nsat=17 iod=5)
RTCM 1065 R SSR code bias  R01 R02 R03 R04 R05 R07 R08 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R22 (nsat=19 iod=5)

I have successfully decoded the RTCM format augmentation information!

MADOCA will augment three satellite systems: G (GPS), R (Glonass), and J (QZS-1). SSR (space state representation) is a method of transmitting correction information by dividing the error into factors. MADOCA augments the satellite internal clock and satellite orbit. We can see the high rate clock correction message frequently. MADOCA in L6E signals places a message in a data part, so it may transmit a continue to different data parts with the same message number. For example, the continuation of the contents of the first line is the contents of the fourth line. nsat represents the number of satellites to be augmented, and iod (issue of data) represents the SSR message issue number.

If we add “clock correction information” to this RTCM message, we can use the MADOCA augmentation.

There is an L6 signal header 1a cf fc 1d in hexadecimal at the beginning of the line of the message decoding screen after error correction of PocketSDR, it is obvious that the messages were correctly decoded.

Not long ago, the only receivers that could extract L6 signal messages were, for example, Javad DELTA-G3T with the quasi-zenith satellite option and the L6 signal reception option, which was very expensive. This is amazing.

Conclusion

The PocketSDR code has been upgraded to be able to decode L6 signals. From the sample data, I confirmed the RTCM messages.


Related article(s):