Skip to content

Commit

Permalink
Add basic support for SigMF input
Browse files Browse the repository at this point in the history
(cherry picked from commit f2b9bad)
  • Loading branch information
kerel-fs authored and daniestevez committed Feb 22, 2025
1 parent 22f7d0e commit 3d8c677
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 6 deletions.
88 changes: 86 additions & 2 deletions apps/gr_satellites
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#

import argparse
import json
import signal
import sys

Expand Down Expand Up @@ -85,6 +86,7 @@ def argument_parser():
p_sources.add_argument(
'--udp', action='store_true', help='Use UDP input')
p_sources.add_argument('--kiss_in', help='KISS input file')
p_sources.add_argument('--sigmf', help='SigMF input file')

p_input.add_argument('--samp_rate', type=float, help='Sample rate (Hz)')
p_input.add_argument(
Expand Down Expand Up @@ -133,9 +135,10 @@ def argument_parser():
def check_options(options, parser):
if (options.kiss_in is None
and options.wavfile is None
and options.samp_rate is None):
and options.samp_rate is None
and options.sigmf is None):
print('Need to specify --samp_rate unless --wavfile '
'or --kiss_in is used',
'or --kiss_in or --sigmf is used',
file=sys.stderr)
parser.print_usage(file=sys.stderr)
sys.exit(1)
Expand Down Expand Up @@ -227,6 +230,8 @@ class gr_satellites_top_block(gr.top_block):
return self.setup_udp_input()
elif self.options.kiss_in is not None:
return self.setup_kiss_input()
elif self.options.sigmf is not None:
return self.setup_sigmf_input()
else:
raise Exception('No input source set for flowgraph')

Expand Down Expand Up @@ -290,6 +295,85 @@ class gr_satellites_top_block(gr.top_block):
def setup_kiss_input(self):
self.input = datasources.kiss_file_source(self.options.kiss_in)

def setup_sigmf_input(self):
if (self.options.sigmf.endswith('sigmf-meta') or
self.options.sigmf.endswith('sigmf-data')):
base_filename = self.options.sigmf[:-len('.sigmf-data')]
dataset_filename = base_filename + '.sigmf-data'
metadata_filename = base_filename + '.sigmf-meta'
elif self.options.sigmf.endswith('sigmf'):
print(
'Failed to load SigMF archive (archives are not yet supported). '
'Consider extracting a recording and pass the corresponding SigMF metadata file.',
file=sys.stderr)
sys.exit(1)
else:
print('Invalid value passed to --sigmf, '
'expected file extension .sigmf-meta or .sigmf-data, '
f'got:\n{self.options.sigmf}', file=sys.stderr)
sys.exit(1)

try:
with open(metadata_filename, encoding='utf-8') as f:
try:
meta = json.load(f)
except json.decoder.JSONDecodeError as json_err:
print(f'Failed to parse SigMF Metadata in \'{metadata_filename}\', '
'JSON decoder error:', file=sys.stderr)
print(json_err, file=sys.stderr)
sys.exit(1)
except FileNotFoundError as fno_err:
print(f'Could not open SigMF Metadata in \'{metadata_filename}\':', file=sys.stderr)
print(fno_err, file=sys.stderr)
exit(1)

# Enforce valid SigMF metadata structure
for key in ['global', 'captures', 'annotations']:
if key not in meta:
print(f'ERROR: Missing top-level object \'{key}\' in SigMF metadata.', file=sys.stderr)
sys.exit(1)

fields_required = ['core:datatype', 'core:sample_rate']
for field in fields_required:
if field not in meta['global']:
print('SigMF metadata file is malformed: '
f'Missing field \'{field}\'', file=sys.stderr)
sys.exit(1)

datatype = meta['global']['core:datatype']
sample_rate = meta['global']['core:sample_rate']

datatypes_supported = ['ci16_le']

if datatype == 'ci16_le':
self.options.iq = True
self.options.rawint16 = dataset_filename
self.options.samp_rate = sample_rate
self.setup_rawint16_input()
else:
datatype_list = ', '.join(datatypes_supported)
print(f'Unsupported SigMF Dataset format (\'{datatype}\'). '
'Supported datatypes are: ' + datatype_list, file=sys.stderr)
sys.exit(1)

# Enforce valid capture segment object (if one or more exist)
if len(meta['captures']) >= 1:
for idx, capture_segment in enumerate(meta['captures']):
if 'core:sample_start' not in capture_segment:
print('SigMF metadata file is malformed: '
f'Field \'core:sample_start\' is missing in Capture segment {idx}.', file=sys.stderr)
sys.exit(1)

# Set start_time if available & unambiguous
if len(meta['captures']) == 1:
capture_segment = meta['captures'][0]
if 'core:datetime' in capture_segment:
if capture_segment['core:sample_start'] == 0:
self.options.start_time = capture_segment['core:datetime']
else:
print('WARNING: \'datetime\' field in capture segment is ignored, because '
'Capture segment field \'sample_start\' is not equal to zero.')


def main():
parser = argument_parser()
Expand Down
3 changes: 3 additions & 0 deletions docs/gr_satellites.1
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Use UDP input
.BR \-\-kiss_in\ \fIKISS_IN\fR
KISS input file
.TP
.BR \-\-sigmf\ \fISIGMF\fR
SigMF input file
.TP
.BR \-\-samp_rate\ \fISAMP_RATE\fR
Sample rate (Hz)
.TP
Expand Down
13 changes: 9 additions & 4 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ basic information about the arguments it allows.
usage: gr_satellites satellite [-h] [--version] [--list_satellites]
[--ignore_unknown_args]
[--satcfg]
(--wavfile WAVFILE | --rawfile RAWFILE | --rawint16 RAWINT16 | --audio [DEVICE] | --udp | --kiss_in KISS_IN)
[--samp_rate SAMP_RATE] [--udp_ip UDP_IP]
(--wavfile WAVFILE | --rawfile RAWFILE | --rawint16 RAWINT16 | --audio [DEVICE] | --udp | --kiss_in KISS_IN | --sigmf SIGMF)
[--samp_rate SAMP_RATE]
[--udp_port UDP_PORT] [--iq] [--udp_raw]
[--input_gain INPUT_GAIN]
[--start_time START_TIME] [--throttle]
Expand Down Expand Up @@ -190,6 +190,10 @@ the input source by using exactly one of the following options:
This can be useful to view the telemetry stored in files previously decoded
with gr-satellites or other software.

* ``--sigmf`` can be used to read a SigMF Recording. The value can be either the
``.sigmf-data`` or the ``.sigmf-meta`` file. The corresponding SigMF Dataset /
SigMF Metadata file is automatically found.

Getting help
""""""""""""

Expand All @@ -204,8 +208,8 @@ For example, this shows all the options allowed by the FUNcube-1 decoder:
$ gr_satellites FUNcube-1 --help
usage: gr_satellites satellite [-h] [--version] [--list_satellites]
(--wavfile WAVFILE | --rawfile RAWFILE | --rawint16 RAWINT16 | --audio [DEVICE] | --udp | --kiss_in KISS_IN)
[--samp_rate SAMP_RATE] [--udp_ip UDP_IP]
(--wavfile WAVFILE | --rawfile RAWFILE | --rawint16 RAWINT16 | --audio [DEVICE] | --udp | --kiss_in KISS_IN | --sigmf SIGMF)
[--samp_rate SAMP_RATE]
[--udp_port UDP_PORT] [--iq]
[--input_gain INPUT_GAIN]
[--start_time START_TIME] [--throttle]
Expand Down Expand Up @@ -239,6 +243,7 @@ For example, this shows all the options allowed by the FUNcube-1 decoder:
--audio [DEVICE] Soundcard device input
--udp Use UDP input
--kiss_in KISS_IN KISS input file
--sigmf SIGMF SigMF input file
--samp_rate SAMP_RATE
Sample rate (Hz)
--udp_ip UDP_IP UDP input listen IP [default='::']
Expand Down

0 comments on commit 3d8c677

Please sign in to comment.