├── lib ├── pmt_utils.cc ├── tag_keys.h ├── reader_utils.h ├── pmt_utils.h ├── writer_utils.h ├── qa_sigmf.cc ├── qa_sigmf.h ├── reader_utils.cc ├── test_sigmf.cc ├── usrp_gps_message_source_impl.h ├── writer_utils.cc ├── sigmf_utils.cc ├── annotation_sink_impl.h ├── CMakeLists.txt ├── source_impl.h └── meta_namespace.cc ├── python ├── bindings │ ├── README.md │ ├── docstrings │ │ ├── README.md │ │ ├── time_mode_pydoc_template.h │ │ ├── usrp_gps_message_source_pydoc_template.h │ │ ├── annotation_sink_pydoc_template.h │ │ ├── source_pydoc_template.h │ │ ├── sink_pydoc_template.h │ │ └── nmea_parser_pydoc_template.h │ ├── failed_conversions.txt │ ├── CMakeLists.txt │ ├── time_mode_python.cc │ ├── python_bindings.cc │ ├── usrp_gps_message_source_python.cc │ ├── bind_oot_file.py │ ├── source_python.cc │ ├── annotation_sink_python.cc │ ├── header_utils.py │ ├── sink_python.cc │ └── nmea_parser_python.cc ├── test_utils.py ├── __init__.py ├── apps_test_helper.py ├── qa_hash.py ├── build_utils_codes.py ├── CMakeLists.txt ├── qa_archive.py ├── qa_nmea_parser.py ├── test_blocks.py └── qa_crop.py ├── examples ├── README ├── sigmf-source.grc └── sigmf-sink.grc ├── setup.cfg ├── docs ├── doxygen │ ├── other │ │ ├── group_defs.dox │ │ └── main_page.dox │ ├── doxyxml │ │ ├── generated │ │ │ ├── __init__.py │ │ │ └── index.py │ │ ├── text.py │ │ ├── __init__.py │ │ └── base.py │ ├── pydoc_macros.h │ └── CMakeLists.txt ├── README.sigmf └── CMakeLists.txt ├── gr-sigmf.pc.in ├── cmake ├── Modules │ ├── targetConfig.cmake.in │ ├── FindRapidJson.cmake │ ├── sigmfConfig.cmake │ ├── FindCppUnit.cmake │ ├── LibFindMacros.cmake │ └── CMakeParseArgumentsCopy.cmake └── cmake_uninstall.cmake.in ├── .gitignore ├── MANIFEST.md ├── include └── sigmf │ ├── version.h.in │ ├── time_mode.h │ ├── CMakeLists.txt │ ├── api.h │ ├── sigmf_utils.h │ ├── usrp_gps_message_source.h │ ├── source.h │ ├── annotation_sink.h │ ├── nmea_parser.h │ └── sink.h ├── grc ├── sigmf_usrp_gps_message_source.block.yml ├── CMakeLists.txt ├── sigmf_annotation_sink.block.yml └── sigmf_source.block.yml ├── swig ├── gr_sigmf_swig.i └── CMakeLists.txt ├── scripts └── get-rapidjson.sh ├── apps ├── app_utils.h ├── CMakeLists.txt ├── sigmf-archive ├── sigmf_play.cc └── sigmf-hash ├── .clang-format ├── CHANGELOG.md ├── README.md └── CMakeLists.txt /lib/pmt_utils.cc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/bindings/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/bindings/docstrings/README.md: -------------------------------------------------------------------------------- 1 | This directory stores templates for docstrings that are scraped from the include header files for each block -------------------------------------------------------------------------------- /examples/README: -------------------------------------------------------------------------------- 1 | It is considered good practice to add examples in here to demonstrate the 2 | functionality of your OOT module. Python scripts, GRC flow graphs or other 3 | code can go here. 4 | 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = F403,E711,E712,E999,E722,W504 3 | exclude = 4 | build, 5 | docs, 6 | examples, 7 | python/__init__.py, 8 | python/build_utils* 9 | 10 | [tool:pytest] 11 | python_files=qa_*.py 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/doxygen/other/group_defs.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | * \defgroup block GNU Radio SIGMF C++ Signal Processing Blocks 3 | * \brief All C++ blocks that can be used from the SIGMF GNU Radio 4 | * module are listed here or in the subcategories below. 5 | * 6 | */ 7 | 8 | -------------------------------------------------------------------------------- /python/bindings/failed_conversions.txt: -------------------------------------------------------------------------------- 1 | ./include/sigmf/sink.hNo c++ parser found. Please install castxml. 2 | ./include/sigmf/sink.hError occurred while running CASTXML: status:1 3 | ./include/sigmf/nmea_parser.hError occurred while running CASTXML: status:1 4 | -------------------------------------------------------------------------------- /docs/doxygen/doxyxml/generated/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains generated files produced by generateDS.py. 3 | 4 | These do the real work of parsing the doxygen xml files but the 5 | resultant classes are not very friendly to navigate so the rest of the 6 | doxyxml module processes them further. 7 | """ 8 | -------------------------------------------------------------------------------- /python/test_utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | def sig_source_c(samp_rate, freq, amp, N): 5 | t = map(lambda x: float(x) / samp_rate, range(N)) 6 | y = map(lambda x: amp * math.cos(2. * math.pi * freq * x) + 7 | 1j * amp * math.sin(2. * math.pi * freq * x), t) 8 | return list(y) 9 | -------------------------------------------------------------------------------- /gr-sigmf.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | includedir=@includedir@ 5 | 6 | Name: gr-sigmf 7 | Description: A set of GNURadio blocks implementing the SigMF standard 8 | Requires: 9 | Version: @LIBVER@ 10 | Libs: -L${libdir} -lgnuradio-sigmf 11 | Cflags: -I${includedir} 12 | -------------------------------------------------------------------------------- /docs/doxygen/other/main_page.dox: -------------------------------------------------------------------------------- 1 | /*! \mainpage 2 | 3 | Welcome to the GNU Radio SIGMF Block 4 | 5 | This is the intro page for the Doxygen manual generated for the SIGMF 6 | block (docs/doxygen/other/main_page.dox). Edit it to add more detailed 7 | documentation about the new GNU Radio modules contained in this 8 | project. 9 | 10 | */ 11 | -------------------------------------------------------------------------------- /cmake/Modules/targetConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Free Software Foundation, Inc. 2 | # 3 | # This file is part of GNU Radio 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | 8 | include(CMakeFindDependencyMacro) 9 | 10 | set(target_deps "@TARGET_DEPENDENCIES@") 11 | foreach(dep IN LISTS target_deps) 12 | find_dependency(${dep}) 13 | endforeach() 14 | include("${CMAKE_CURRENT_LIST_DIR}/@TARGET@Targets.cmake") 15 | -------------------------------------------------------------------------------- /docs/README.sigmf: -------------------------------------------------------------------------------- 1 | This is the sigmf-write-a-block package meant as a guide to building 2 | out-of-tree packages. To use the sigmf blocks, the Python namespaces 3 | is in 'sigmf', which is imported as: 4 | 5 | import sigmf 6 | 7 | See the Doxygen documentation for details about the blocks available 8 | in this package. A quick listing of the details can be found in Python 9 | after importing by using: 10 | 11 | help(sigmf) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake build dir 2 | build/ 3 | 4 | # SigMF files 5 | *.sigmf-data 6 | *.sigmf-meta 7 | 8 | # Visual Studio Code config directory 9 | .vscode/ 10 | 11 | # Python 12 | *.pyc 13 | 14 | .pytest_cache/ 15 | 16 | .cache 17 | 18 | # Example files 19 | /examples/*.py 20 | 21 | # Eclipse config files 22 | .cproject 23 | .project 24 | .pydevproject 25 | .settings/ 26 | 27 | # Vim 28 | *.swp 29 | 30 | # unittest junk 31 | .unittests/ -------------------------------------------------------------------------------- /lib/tag_keys.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace gr { 5 | namespace sigmf { 6 | 7 | static const pmt::pmt_t TIME_KEY = pmt::string_to_symbol("rx_time"); 8 | static const pmt::pmt_t RATE_KEY = pmt::string_to_symbol("rx_rate"); 9 | static const pmt::pmt_t FREQ_KEY = pmt::string_to_symbol("rx_freq"); 10 | static const pmt::pmt_t PACKET_LEN_KEY = pmt::string_to_symbol("packet_len"); 11 | 12 | } // namespace sigmf 13 | } // namespace gr -------------------------------------------------------------------------------- /MANIFEST.md: -------------------------------------------------------------------------------- 1 | title: gr-sigmf 2 | brief: Blocks for interfacing with SigMF (The Signal Metadata Format) 3 | tags: 4 | - sigmf 5 | - metadata 6 | - streams 7 | author: 8 | - Paul Wicks 9 | - Cate Miller 10 | - Scott Torborg 11 | - Kyle Logue 12 | - Terry Ferrett 13 | copyright_owner: 14 | - SkySafe Inc 15 | license: GPLv3 16 | repo: https://github.com/skysafe/gr-sigmf 17 | --- 18 | -------------------------------------------------------------------------------- /include/sigmf/version.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define GR_SIGMF_VERSION_MAJOR @VERSION_INFO_MAJOR_VERSION@ 4 | #define GR_SIGMF_VERSION_API_COMPAT @VERSION_INFO_API_COMPAT@ 5 | #define GR_SIGMF_VERSION_MINOR @VERSION_INFO_MINOR_VERSION@ 6 | #define GR_SIGMF_VERSION_MAINT @VERSION_INFO_MAINT_VERSION@ 7 | #define GR_SIGMF_VERSION_MAINT_STRING "@VERSION_INFO_MAINT_VERSION@" 8 | #define GR_SIGMF_VERSION_STRING "@VERSION_INFO_MAJOR_VERSION@.@VERSION_INFO_API_COMPAT@.@VERSION_INFO_MINOR_VERSION@.@VERSION_INFO_MAINT_VERSION@" -------------------------------------------------------------------------------- /python/bindings/docstrings/time_mode_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | #include "pydoc_macros.h" 10 | #define D(...) DOC(gr,sigmf, __VA_ARGS__ ) 11 | /* 12 | This file contains placeholders for docstrings for the Python bindings. 13 | Do not edit! These were automatically extracted during the binding process 14 | and will be overwritten during the build process 15 | */ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /include/sigmf/time_mode.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_SIGMF_TIME_MODE_H 2 | #define INCLUDED_SIGMF_TIME_MODE_H 3 | 4 | #include 5 | 6 | namespace gr { 7 | namespace sigmf { 8 | 9 | // NOTE: We don't actually care about the underlying type 10 | // of this enum, but if left unspecified, this triggers a bug 11 | // in versions of gcc < 6 (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=43407) 12 | enum class sigmf_time_mode: int SIGMF_API{ 13 | absolute, 14 | relative 15 | }; 16 | } 17 | } 18 | 19 | #endif /* INCLUDED_SIGMF_TIME_MODE_H */ -------------------------------------------------------------------------------- /grc/sigmf_usrp_gps_message_source.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: sigmf_usrp_gps_message_source 4 | label: USRP GPS Message Source 5 | category: '[SigMF]' 6 | 7 | parameters: 8 | - id: uhd_args 9 | label: UHD Args 10 | dtype: string 11 | - id: poll_interval 12 | label: Poll Interval (s) 13 | dtype: real 14 | default: '1.0' 15 | 16 | outputs: 17 | - domain: message 18 | id: out 19 | 20 | templates: 21 | imports: import gr_sigmf 22 | make: gr_sigmf.usrp_gps_message_source(${uhd_args}, ${poll_interval}) 23 | 24 | file_format: 1 25 | -------------------------------------------------------------------------------- /lib/reader_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_SIGMF_READER_UTILS_H 2 | #define INCLUDED_SIGMF_READER_UTILS_H 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * Common utilities used by readers of sigmf files 9 | */ 10 | namespace gr { 11 | namespace sigmf { 12 | namespace reader_utils { 13 | boost::posix_time::ptime iso_string_to_ptime(const std::string &str); 14 | pmt::pmt_t ptime_to_uhd_time(const boost::posix_time::ptime &time); 15 | 16 | } 17 | } // namespace sigmf 18 | } // namespace gr 19 | 20 | #endif /* INCLUDED_SIGMF_READER_UTILS_H */ -------------------------------------------------------------------------------- /include/sigmf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011,2012 Free Software Foundation, Inc. 2 | # 3 | # This file was generated by gr_modtool, a tool from the GNU Radio framework 4 | # This file is a part of gr-sigmf 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | ######################################################################## 10 | # Install public header files 11 | ######################################################################## 12 | install(FILES 13 | api.h 14 | sink.h 15 | source.h 16 | annotation_sink.h 17 | usrp_gps_message_source.h DESTINATION include/sigmf 18 | ) 19 | -------------------------------------------------------------------------------- /python/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2008,2009 Free Software Foundation, Inc. 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | # The presence of this file turns this directory into a Python package 8 | 9 | ''' 10 | This is the GNU Radio SIGMF module. Place your Python package 11 | description here (python/__init__.py). 12 | ''' 13 | import os 14 | 15 | from .sigmf_python import * 16 | 17 | # import any pure python here 18 | # 19 | 20 | annotation_mode_clear = annotation_mode.clear 21 | annotation_mode_keep = annotation_mode.keep 22 | 23 | sigmf_time_mode_absolute = sigmf_time_mode.absolute 24 | sigmf_time_mode_relative = sigmf_time_mode.relative 25 | -------------------------------------------------------------------------------- /lib/pmt_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_SIGMF_PMT_UTILS_H 2 | #define INCLUDED_SIGMF_PMT_UTILS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gr { 9 | namespace sigmf { 10 | namespace pmt_utils { 11 | inline std::pair 12 | extract_uhd_time(pmt::pmt_t uhd_time) 13 | { 14 | uint64_t seconds = pmt::to_uint64(pmt::tuple_ref(uhd_time, 0)); 15 | double frac_seconds = pmt::to_double(pmt::tuple_ref(uhd_time, 1)); 16 | return std::make_pair(seconds, frac_seconds); 17 | } 18 | } // namespace pmt_utils 19 | } // namespace sigmf 20 | } // namespace gr 21 | #endif /* INCLUDED_SIGMF_PMT_UTILS_H */ -------------------------------------------------------------------------------- /cmake/Modules/FindRapidJson.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find RapidJson 2 | # Once done, this will define 3 | # 4 | # RAPIDJSON_FOUND - system has RapidJson 5 | # RAPIDJSON_INCLUDE_DIRS - the RapidJson include directories 6 | 7 | include(LibFindMacros) 8 | 9 | # Use pkg-config to get hints about paths 10 | libfind_pkg_check_modules(RapidJson_PKGCONF RapidJSON) 11 | 12 | # Main include dir 13 | find_path(RapidJson_INCLUDE_DIR 14 | NAMES rapidjson/rapidjson.h 15 | PATHS ${RAPIDJSON_PKGCONF_INCLUDE_DIRS} 16 | ) 17 | 18 | # Set the include dir variables and the libraries and let libfind_process do the rest. 19 | # NOTE: Singular variables for this library, plural for libraries this this lib depends on. 20 | set(RapidJson_PROCESS_INCLUDES RapidJson_INCLUDE_DIR) 21 | libfind_process(RapidJson) 22 | -------------------------------------------------------------------------------- /swig/gr_sigmf_swig.i: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | 3 | #define SIGMF_API 4 | 5 | %include "gnuradio.i" // the common stuff 6 | %include "stdint.i" 7 | 8 | //load generated python docstrings 9 | %include "sigmf_swig_doc.i" 10 | 11 | %{ 12 | #include "sigmf/sink.h" 13 | #include "sigmf/source.h" 14 | #include "sigmf/annotation_sink.h" 15 | #include "sigmf/time_mode.h" 16 | #include "sigmf/nmea_parser.h" 17 | #include "sigmf/usrp_gps_message_source.h" 18 | %} 19 | 20 | %include "sigmf/time_mode.h" 21 | %include "sigmf/nmea_parser.h" 22 | %include "sigmf/sink.h" 23 | GR_SWIG_BLOCK_MAGIC2(sigmf, sink); 24 | %include "sigmf/source.h" 25 | GR_SWIG_BLOCK_MAGIC2(sigmf, source); 26 | %include "sigmf/annotation_sink.h" 27 | GR_SWIG_BLOCK_MAGIC2(sigmf, annotation_sink); 28 | %include "sigmf/usrp_gps_message_source.h" 29 | GR_SWIG_BLOCK_MAGIC2(sigmf, usrp_gps_message_source); 30 | -------------------------------------------------------------------------------- /lib/writer_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_SIGMF_WRITER_UTILS_H 2 | #define INCLUDED_SIGMF_WRITER_UTILS_H 3 | 4 | #include 5 | #include 6 | #include "sigmf/meta_namespace.h" 7 | 8 | /** 9 | * Internal functions shared between blocks that 10 | * need to write data 11 | */ 12 | namespace gr { 13 | namespace sigmf { 14 | namespace writer_utils { 15 | 16 | /** 17 | * Write given metadata data set to file. 18 | * Assumes file is already open and does not 19 | * close the file when finished 20 | */ 21 | void write_meta_to_fp(FILE *fp, 22 | const meta_namespace &global, 23 | std::vector &captures, 24 | std::vector &annotations); 25 | } 26 | } // namespace sigmf 27 | } // namespace gr 28 | #endif /* INCLUDED_SIGMF_WRITER_UTILS_H */ -------------------------------------------------------------------------------- /docs/doxygen/pydoc_macros.h: -------------------------------------------------------------------------------- 1 | #ifndef PYDOC_MACROS_H 2 | #define PYDOC_MACROS_H 3 | 4 | #define __EXPAND(x) x 5 | #define __COUNT(_1, _2, _3, _4, _5, _6, _7, COUNT, ...) COUNT 6 | #define __VA_SIZE(...) __EXPAND(__COUNT(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1)) 7 | #define __CAT1(a, b) a##b 8 | #define __CAT2(a, b) __CAT1(a, b) 9 | #define __DOC1(n1) __doc_##n1 10 | #define __DOC2(n1, n2) __doc_##n1##_##n2 11 | #define __DOC3(n1, n2, n3) __doc_##n1##_##n2##_##n3 12 | #define __DOC4(n1, n2, n3, n4) __doc_##n1##_##n2##_##n3##_##n4 13 | #define __DOC5(n1, n2, n3, n4, n5) __doc_##n1##_##n2##_##n3##_##n4##_##n5 14 | #define __DOC6(n1, n2, n3, n4, n5, n6) __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6 15 | #define __DOC7(n1, n2, n3, n4, n5, n6, n7) \ 16 | __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6##_##n7 17 | #define DOC(...) __EXPAND(__EXPAND(__CAT2(__DOC, __VA_SIZE(__VA_ARGS__)))(__VA_ARGS__)) 18 | 19 | #endif // PYDOC_MACROS_H -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Free Software Foundation, Inc. 2 | # 3 | # This file was generated by gr_modtool, a tool from the GNU Radio framework 4 | # This file is a part of gr-sigmf 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | ######################################################################## 10 | # Setup dependencies 11 | ######################################################################## 12 | find_package(Doxygen) 13 | 14 | ######################################################################## 15 | # Begin conditional configuration 16 | ######################################################################## 17 | if(ENABLE_DOXYGEN) 18 | 19 | ######################################################################## 20 | # Add subdirectories 21 | ######################################################################## 22 | add_subdirectory(doxygen) 23 | 24 | endif(ENABLE_DOXYGEN) 25 | -------------------------------------------------------------------------------- /python/bindings/docstrings/usrp_gps_message_source_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | #include "pydoc_macros.h" 10 | #define D(...) DOC(gr,sigmf, __VA_ARGS__ ) 11 | /* 12 | This file contains placeholders for docstrings for the Python bindings. 13 | Do not edit! These were automatically extracted during the binding process 14 | and will be overwritten during the build process 15 | */ 16 | 17 | 18 | 19 | static const char *__doc_gr_sigmf_usrp_gps_message_source = R"doc()doc"; 20 | 21 | 22 | static const char *__doc_gr_sigmf_usrp_gps_message_source_usrp_gps_message_source = R"doc()doc"; 23 | 24 | 25 | static const char *__doc_gr_sigmf_usrp_gps_message_source_make_0 = R"doc()doc"; 26 | 27 | 28 | static const char *__doc_gr_sigmf_usrp_gps_message_source_make_1 = R"doc()doc"; 29 | 30 | 31 | -------------------------------------------------------------------------------- /cmake/Modules/sigmfConfig.cmake: -------------------------------------------------------------------------------- 1 | if(NOT PKG_CONFIG_FOUND) 2 | INCLUDE(FindPkgConfig) 3 | endif() 4 | PKG_CHECK_MODULES(PC_SIGMF sigmf) 5 | 6 | FIND_PATH( 7 | SIGMF_INCLUDE_DIRS 8 | NAMES sigmf/api.h 9 | HINTS $ENV{SIGMF_DIR}/include 10 | ${PC_SIGMF_INCLUDEDIR} 11 | PATHS ${CMAKE_INSTALL_PREFIX}/include 12 | /usr/local/include 13 | /usr/include 14 | ) 15 | 16 | FIND_LIBRARY( 17 | SIGMF_LIBRARIES 18 | NAMES gnuradio-sigmf 19 | HINTS $ENV{SIGMF_DIR}/lib 20 | ${PC_SIGMF_LIBDIR} 21 | PATHS ${CMAKE_INSTALL_PREFIX}/lib 22 | ${CMAKE_INSTALL_PREFIX}/lib64 23 | /usr/local/lib 24 | /usr/local/lib64 25 | /usr/lib 26 | /usr/lib64 27 | ) 28 | 29 | include("${CMAKE_CURRENT_LIST_DIR}/sigmfTarget.cmake") 30 | 31 | INCLUDE(FindPackageHandleStandardArgs) 32 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SIGMF DEFAULT_MSG SIGMF_LIBRARIES SIGMF_INCLUDE_DIRS) 33 | MARK_AS_ADVANCED(SIGMF_LIBRARIES SIGMF_INCLUDE_DIRS) 34 | -------------------------------------------------------------------------------- /scripts/get-rapidjson.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == "-h" ] ; then 4 | echo "Download and install the rapidjson dependency from github. Installs to /usr/include by default. Usage: `basename $0` [-h] [install-dir] " 5 | exit 0 6 | fi 7 | 8 | if [ -z "$1" ] ; then 9 | INSTALL_DIR=/usr/include 10 | else 11 | INSTALL_DIR=$1 12 | fi 13 | 14 | wget https://github.com/Tencent/rapidjson/archive/v1.1.0.zip 15 | 16 | if [[ "$?" != 0 ]]; then 17 | echo "Failed to download rapidjson from github!" 18 | exit -1 19 | fi 20 | 21 | 22 | unzip v1.1.0.zip 'rapidjson-1.1.0/include/rapidjson/*' -d $INSTALL_DIR/rapidjson 23 | 24 | if [[ "$?" != 0 ]]; then 25 | echo "Failed to install rapidjson headers to $INSTALL_DIR" 26 | 27 | else 28 | # unzip preserves directory structure, so we'll just move them to where we want them 29 | mv $INSTALL_DIR/rapidjson/rapidjson-1.1.0/include/rapidjson/* $INSTALL_DIR/rapidjson/ 30 | rm -r $INSTALL_DIR/rapidjson/rapidjson-1.1.0 31 | 32 | fi 33 | 34 | rm v1.1.0.zip 35 | -------------------------------------------------------------------------------- /grc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Free Software Foundation, Inc. 2 | # 3 | # This file is part of GNU Radio 4 | # 5 | # GNU Radio is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 3, or (at your option) 8 | # any later version. 9 | # 10 | # GNU Radio is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with GNU Radio; see the file COPYING. If not, write to 17 | # the Free Software Foundation, Inc., 51 Franklin Street, 18 | # Boston, MA 02110-1301, USA. 19 | 20 | install(FILES 21 | sigmf_sink.block.yml 22 | sigmf_source.block.yml 23 | sigmf_annotation_sink.block.yml 24 | sigmf_usrp_gps_message_source.block.yml DESTINATION share/gnuradio/grc/blocks 25 | ) 26 | -------------------------------------------------------------------------------- /python/bindings/docstrings/annotation_sink_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | #include "pydoc_macros.h" 10 | #define D(...) DOC(gr,sigmf, __VA_ARGS__ ) 11 | /* 12 | This file contains placeholders for docstrings for the Python bindings. 13 | Do not edit! These were automatically extracted during the binding process 14 | and will be overwritten during the build process 15 | */ 16 | 17 | 18 | 19 | static const char *__doc_gr_sigmf_annotation_mode = R"doc()doc"; 20 | 21 | 22 | static const char *__doc_gr_sigmf_annotation_mode_annotation_mode = R"doc()doc"; 23 | 24 | 25 | static const char *__doc_gr_sigmf_annotation_mode_keep = R"doc()doc"; 26 | 27 | 28 | static const char *__doc_gr_sigmf_annotation_mode_clear = R"doc()doc"; 29 | 30 | 31 | static const char *__doc_gr_sigmf_annotation_sink = R"doc()doc"; 32 | 33 | 34 | static const char *__doc_gr_sigmf_annotation_sink_annotation_sink = R"doc()doc"; 35 | 36 | 37 | static const char *__doc_gr_sigmf_annotation_sink_make = R"doc()doc"; 38 | 39 | 40 | -------------------------------------------------------------------------------- /include/sigmf/api.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * GNU Radio is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 3, or (at your option) 9 | * any later version. 10 | * 11 | * GNU Radio is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with GNU Radio; see the file COPYING. If not, write to 18 | * the Free Software Foundation, Inc., 51 Franklin Street, 19 | * Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #ifndef INCLUDED_SIGMF_API_H 23 | #define INCLUDED_SIGMF_API_H 24 | 25 | #include 26 | 27 | #ifdef gnuradio_sigmf_EXPORTS 28 | #define SIGMF_API __GR_ATTR_EXPORT 29 | #else 30 | #define SIGMF_API __GR_ATTR_IMPORT 31 | #endif 32 | 33 | #endif /* INCLUDED_SIGMF_API_H */ 34 | -------------------------------------------------------------------------------- /python/bindings/docstrings/source_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | #include "pydoc_macros.h" 10 | #define D(...) DOC(gr,sigmf, __VA_ARGS__ ) 11 | /* 12 | This file contains placeholders for docstrings for the Python bindings. 13 | Do not edit! These were automatically extracted during the binding process 14 | and will be overwritten during the build process 15 | */ 16 | 17 | 18 | 19 | static const char *__doc_gr_sigmf_source = R"doc()doc"; 20 | 21 | 22 | static const char *__doc_gr_sigmf_source_source_0 = R"doc()doc"; 23 | 24 | 25 | static const char *__doc_gr_sigmf_source_source_1 = R"doc()doc"; 26 | 27 | 28 | static const char *__doc_gr_sigmf_source_make = R"doc()doc"; 29 | 30 | 31 | static const char *__doc_gr_sigmf_source_make_no_datatype = R"doc()doc"; 32 | 33 | 34 | static const char *__doc_gr_sigmf_source_set_begin_tag = R"doc()doc"; 35 | 36 | 37 | static const char *__doc_gr_sigmf_source_global_meta = R"doc()doc"; 38 | 39 | 40 | static const char *__doc_gr_sigmf_source_capture_segments = R"doc()doc"; 41 | 42 | 43 | -------------------------------------------------------------------------------- /grc/sigmf_annotation_sink.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: sigmf_annotation_sink 4 | label: Annotation Sink 5 | category: '[SigMF]' 6 | 7 | parameters: 8 | - id: filename 9 | label: SigMF File 10 | dtype: file_open 11 | - id: annotation_mode 12 | label: Annotation Mode 13 | dtype: enum 14 | options: [keep, clear] 15 | option_labels: [Keep Existing, Clear Existing] 16 | - id: filter_key 17 | label: Filter Key 18 | dtype: string 19 | hide: ${ ('part' if annotation_mode == 'clear' else 'all') } 20 | - id: time_mode 21 | label: Time Handling Mode 22 | dtype: enum 23 | options: [gr_sigmf.sigmf_time_mode_absolute, gr_sigmf.sigmf_time_mode_relative] 24 | option_labels: [Absolute, Relative] 25 | 26 | inputs: 27 | - domain: message 28 | id: annotations 29 | 30 | templates: 31 | imports: import gr_sigmf 32 | make: |- 33 | % if annotation_mode == 'clear': 34 | gr_sigmf.annotation_sink(${filename}, gr_sigmf.annotation_mode.${annotation_mode(filter_key)}, ${time_mode}) 35 | % else: 36 | gr_sigmf.annotation_sink(${filename}, gr_sigmf.annotation_mode.${annotation_mode()}, ${time_mode}) 37 | % endif 38 | 39 | file_format: 1 40 | -------------------------------------------------------------------------------- /cmake/Modules/FindCppUnit.cmake: -------------------------------------------------------------------------------- 1 | # http://www.cmake.org/pipermail/cmake/2006-October/011446.html 2 | # Modified to use pkg config and use standard var names 3 | 4 | # 5 | # Find the CppUnit includes and library 6 | # 7 | # This module defines 8 | # CPPUNIT_INCLUDE_DIR, where to find tiff.h, etc. 9 | # CPPUNIT_LIBRARIES, the libraries to link against to use CppUnit. 10 | # CPPUNIT_FOUND, If false, do not try to use CppUnit. 11 | 12 | INCLUDE(FindPkgConfig) 13 | PKG_CHECK_MODULES(PC_CPPUNIT "cppunit") 14 | 15 | FIND_PATH(CPPUNIT_INCLUDE_DIRS 16 | NAMES cppunit/TestCase.h 17 | HINTS ${PC_CPPUNIT_INCLUDE_DIR} 18 | ${CMAKE_INSTALL_PREFIX}/include 19 | PATHS 20 | /usr/local/include 21 | /usr/include 22 | ) 23 | 24 | FIND_LIBRARY(CPPUNIT_LIBRARIES 25 | NAMES cppunit 26 | HINTS ${PC_CPPUNIT_LIBDIR} 27 | ${CMAKE_INSTALL_PREFIX}/lib 28 | ${CMAKE_INSTALL_PREFIX}/lib64 29 | PATHS 30 | ${CPPUNIT_INCLUDE_DIRS}/../lib 31 | /usr/local/lib 32 | /usr/lib 33 | ) 34 | 35 | LIST(APPEND CPPUNIT_LIBRARIES ${CMAKE_DL_LIBS}) 36 | 37 | INCLUDE(FindPackageHandleStandardArgs) 38 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(CPPUNIT DEFAULT_MSG CPPUNIT_LIBRARIES CPPUNIT_INCLUDE_DIRS) 39 | MARK_AS_ADVANCED(CPPUNIT_LIBRARIES CPPUNIT_INCLUDE_DIRS) 40 | -------------------------------------------------------------------------------- /lib/qa_sigmf.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * GNU Radio is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 3, or (at your option) 9 | * any later version. 10 | * 11 | * GNU Radio is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with GNU Radio; see the file COPYING. If not, write to 18 | * the Free Software Foundation, Inc., 51 Franklin Street, 19 | * Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | /* 23 | * This class gathers together all the test cases for the gr-filter 24 | * directory into a single test suite. As you create new test cases, 25 | * add them here. 26 | */ 27 | 28 | #include "qa_sigmf.h" 29 | 30 | CppUnit::TestSuite * 31 | qa_sigmf::suite() 32 | { 33 | CppUnit::TestSuite *s = new CppUnit::TestSuite("sigmf"); 34 | 35 | return s; 36 | } 37 | -------------------------------------------------------------------------------- /python/apps_test_helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import Popen, PIPE 3 | 4 | from gnuradio import gr, blocks, analog 5 | 6 | try: 7 | import gr_sigmf as sigmf 8 | except ImportError: 9 | import sys 10 | dirname, filename = os.path.split(os.path.abspath(__file__)) 11 | sys.path.append(os.path.join(dirname, "bindings")) 12 | import gr_sigmf as sigmf 13 | 14 | 15 | SIGMF_METADATA_EXT = ".sigmf-meta" 16 | SIGMF_DATASET_EXT = ".sigmf-data" 17 | 18 | 19 | class AppRunner: 20 | def __init__(self, testdir, app): 21 | self.testdir = testdir 22 | self.app = app 23 | 24 | def run(self, argstr): 25 | cmdargs = [self.app] 26 | cmdargs.extend(argstr.split()) 27 | print("running cmdargs: %r" % cmdargs) 28 | proc = Popen(cmdargs, stdout=PIPE, stderr=PIPE) 29 | return proc 30 | 31 | 32 | def run_flowgraph(filename): 33 | 34 | samp_rate = 32000 35 | 36 | head = blocks.head(gr.sizeof_float * 1, samp_rate) 37 | source = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 1000, 1, 0) 38 | sigmf_sink = sigmf.sink("rf32_le", filename) 39 | 40 | tb = gr.top_block() 41 | tb.connect(source, head) 42 | tb.connect(head, sigmf_sink) 43 | tb.run() 44 | tb.wait() 45 | -------------------------------------------------------------------------------- /lib/qa_sigmf.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2012 Free Software Foundation, Inc. 4 | * 5 | * This file is part of GNU Radio 6 | * 7 | * GNU Radio is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 3, or (at your option) 10 | * any later version. 11 | * 12 | * GNU Radio is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with GNU Radio; see the file COPYING. If not, write to 19 | * the Free Software Foundation, Inc., 51 Franklin Street, 20 | * Boston, MA 02110-1301, USA. 21 | */ 22 | 23 | #ifndef _QA_SIGMF_H_ 24 | #define _QA_SIGMF_H_ 25 | 26 | #include 27 | #include 28 | 29 | //! collect all the tests for the gr-filter directory 30 | 31 | class __GR_ATTR_EXPORT qa_sigmf { 32 | public: 33 | //! return suite of tests for all of gr-filter directory 34 | static CppUnit::TestSuite *suite(); 35 | }; 36 | 37 | #endif /* _QA_SIGMF_H_ */ 38 | -------------------------------------------------------------------------------- /python/qa_hash.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import os 3 | import shutil 4 | import json 5 | 6 | from gnuradio import gr_unittest 7 | 8 | import apps_test_helper 9 | 10 | 11 | class qa_hash(gr_unittest.TestCase): 12 | 13 | def setUp(self): 14 | 15 | # Create a temporary directory 16 | self.test_dir = tempfile.mkdtemp() 17 | 18 | def tearDown(self): 19 | 20 | # Remove the directory after the test 21 | shutil.rmtree(self.test_dir) 22 | 23 | def test_hash(self): 24 | 25 | runner = apps_test_helper.AppRunner(self.test_dir, "sigmf-hash") 26 | 27 | filename = os.path.join(self.test_dir, "temp") 28 | data_file = filename + apps_test_helper.SIGMF_DATASET_EXT 29 | meta_file = filename + apps_test_helper.SIGMF_METADATA_EXT 30 | 31 | apps_test_helper.run_flowgraph(data_file) 32 | 33 | # update 34 | proc = runner.run("update " + data_file) 35 | out, err = proc.communicate() 36 | 37 | # check 38 | proc = runner.run("check " + data_file) 39 | out, err = proc.communicate() 40 | assert out == b"Hash match\n" 41 | 42 | meta = open(meta_file, "r") 43 | data = json.loads(meta.read()) 44 | 45 | assert 'core:sha512' in data['global'] 46 | 47 | 48 | if __name__ == '__main__': 49 | gr_unittest.run(qa_hash) 50 | -------------------------------------------------------------------------------- /grc/sigmf_source.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: sigmf_source 4 | label: SigMF Source 5 | category: '[SigMF]' 6 | 7 | parameters: 8 | - id: filename 9 | label: File 10 | dtype: file_open 11 | - id: type 12 | label: Output Type 13 | dtype: enum 14 | options: [fc32, sc32, sc16, sc8, f64, f32, s64, s32, s16, s8] 15 | option_labels: [Complex Float 32, Complex Integer 32, Complex Integer 16, Complex 16 | Integer 8, Float 64, Float 32, Integer 64, Integer 32, Integer 16, Integer 17 | 8] 18 | option_attributes: 19 | sigmf_type: [cf32, ci32, ci16, ci8, rf64, rf32, ri64, ri32, ri16, ri8] 20 | hide: part 21 | - id: repeat 22 | label: Repeat 23 | dtype: enum 24 | default: 'False' 25 | options: ['True', 'False'] 26 | option_labels: ['Yes', 'No'] 27 | 28 | inputs: 29 | - domain: message 30 | id: command 31 | optional: true 32 | 33 | outputs: 34 | - domain: stream 35 | dtype: ${ type } 36 | - domain: message 37 | id: meta 38 | optional: true 39 | 40 | templates: 41 | imports: |- 42 | import gr_sigmf 43 | import sys 44 | make: gr_sigmf.source(${filename}, "${type.sigmf_type}" + ("_le" if sys.byteorder 45 | == "little" else "_be"), ${repeat}) 46 | 47 | documentation: |- 48 | Stream data from a SigMF recording. 49 | 50 | file_format: 1 51 | -------------------------------------------------------------------------------- /docs/doxygen/doxyxml/text.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2010 Free Software Foundation, Inc. 3 | # 4 | # This file was generated by gr_modtool, a tool from the GNU Radio framework 5 | # This file is a part of gr-sigmf 6 | # 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | # 9 | # 10 | """ 11 | Utilities for extracting text from generated classes. 12 | """ 13 | 14 | def is_string(txt): 15 | if isinstance(txt, str): 16 | return True 17 | try: 18 | if isinstance(txt, str): 19 | return True 20 | except NameError: 21 | pass 22 | return False 23 | 24 | def description(obj): 25 | if obj is None: 26 | return None 27 | return description_bit(obj).strip() 28 | 29 | def description_bit(obj): 30 | if hasattr(obj, 'content'): 31 | contents = [description_bit(item) for item in obj.content] 32 | result = ''.join(contents) 33 | elif hasattr(obj, 'content_'): 34 | contents = [description_bit(item) for item in obj.content_] 35 | result = ''.join(contents) 36 | elif hasattr(obj, 'value'): 37 | result = description_bit(obj.value) 38 | elif is_string(obj): 39 | return obj 40 | else: 41 | raise Exception('Expecting a string or something with content, content_ or value attribute') 42 | # If this bit is a paragraph then add one some line breaks. 43 | if hasattr(obj, 'name') and obj.name == 'para': 44 | result += "\n\n" 45 | return result 46 | -------------------------------------------------------------------------------- /python/bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Free Software Foundation, Inc. 2 | # 3 | # This file is part of GNU Radio 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | 8 | ######################################################################## 9 | # Check if there is C++ code at all 10 | ######################################################################## 11 | if(NOT sigmf_sources) 12 | MESSAGE(STATUS "No C++ sources... skipping python bindings") 13 | return() 14 | endif(NOT sigmf_sources) 15 | 16 | ######################################################################## 17 | # Check for pygccxml 18 | ######################################################################## 19 | GR_PYTHON_CHECK_MODULE_RAW( 20 | "pygccxml" 21 | "import pygccxml" 22 | PYGCCXML_FOUND 23 | ) 24 | 25 | include(GrPybind) 26 | 27 | ######################################################################## 28 | # Python Bindings 29 | ######################################################################## 30 | 31 | list(APPEND sigmf_python_files 32 | time_mode_python.cc 33 | nmea_parser_python.cc 34 | sink_python.cc 35 | source_python.cc 36 | annotation_sink_python.cc 37 | usrp_gps_message_source_python.cc 38 | python_bindings.cc) 39 | 40 | GR_PYBIND_MAKE_OOT(sigmf 41 | ../.. 42 | gr::sigmf 43 | "${sigmf_python_files}") 44 | 45 | install(TARGETS sigmf_python DESTINATION ${GR_PYTHON_DIR}/gr_sigmf COMPONENT pythonapi) 46 | -------------------------------------------------------------------------------- /lib/reader_utils.cc: -------------------------------------------------------------------------------- 1 | #include "reader_utils.h" 2 | #include 3 | #include 4 | 5 | namespace posix = boost::posix_time; 6 | 7 | namespace gr { 8 | namespace sigmf { 9 | namespace reader_utils { 10 | posix::ptime 11 | iso_string_to_ptime(const std::string &str) 12 | { 13 | boost::posix_time::ptime time; 14 | std::stringstream ss(str); 15 | boost::local_time::local_time_input_facet *ifc = 16 | new boost::local_time::local_time_input_facet(); 17 | ifc->set_iso_extended_format(); 18 | ss.imbue(std::locale(ss.getloc(), ifc)); 19 | boost::local_time::local_date_time zonetime(boost::local_time::not_a_date_time); 20 | if(ss >> zonetime) { 21 | time = zonetime.utc_time(); 22 | } 23 | return time; 24 | } 25 | 26 | pmt::pmt_t 27 | ptime_to_uhd_time(const boost::posix_time::ptime &time) 28 | { 29 | uint64_t seconds = static_cast(posix::to_time_t(time)); 30 | auto tod = time.time_of_day(); 31 | auto tick_seconds = tod.total_seconds() * tod.ticks_per_second(); 32 | auto frac_ticks = tod.ticks() - tick_seconds; 33 | double frac_seconds = static_cast(frac_ticks) / tod.ticks_per_second(); 34 | return pmt::make_tuple(pmt::mp(seconds), pmt::mp(frac_seconds)); 35 | } 36 | } // namespace reader_utils 37 | } // namespace sigmf 38 | } // namespace gr -------------------------------------------------------------------------------- /apps/app_utils.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2017 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | 24 | std::string 25 | uhd_format_to_sigmf_format(const std::string &format) 26 | { 27 | std::string ending; 28 | if (boost::endian::order::native == boost::endian::order::little) { 29 | ending = "_le"; 30 | } else { 31 | ending = "_be"; 32 | } 33 | if(format == "fc64") { 34 | return "cf64" + ending; 35 | } else if(format == "fc32") { 36 | return "cf32" + ending; 37 | } else if(format == "sc16") { 38 | return "ci16" + ending; 39 | } else if(format == "sc8") { 40 | return "ci8" + ending; 41 | } 42 | return format; 43 | } 44 | -------------------------------------------------------------------------------- /cmake/cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | # http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F 2 | 3 | IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 4 | MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") 5 | ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 6 | 7 | FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) 8 | STRING(REGEX REPLACE "\n" ";" files "${files}") 9 | FOREACH(file ${files}) 10 | MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") 11 | IF(EXISTS "$ENV{DESTDIR}${file}") 12 | EXEC_PROGRAM( 13 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 14 | OUTPUT_VARIABLE rm_out 15 | RETURN_VALUE rm_retval 16 | ) 17 | IF(NOT "${rm_retval}" STREQUAL 0) 18 | MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") 19 | ENDIF(NOT "${rm_retval}" STREQUAL 0) 20 | ELSEIF(IS_SYMLINK "$ENV{DESTDIR}${file}") 21 | EXEC_PROGRAM( 22 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 23 | OUTPUT_VARIABLE rm_out 24 | RETURN_VALUE rm_retval 25 | ) 26 | IF(NOT "${rm_retval}" STREQUAL 0) 27 | MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") 28 | ENDIF(NOT "${rm_retval}" STREQUAL 0) 29 | ELSE(EXISTS "$ENV{DESTDIR}${file}") 30 | MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") 31 | ENDIF(EXISTS "$ENV{DESTDIR}${file}") 32 | ENDFOREACH(file) 33 | -------------------------------------------------------------------------------- /python/bindings/time_mode_python.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | 10 | /***********************************************************************************/ 11 | /* This file is automatically generated using bindtool and can be manually edited */ 12 | /* The following lines can be configured to regenerate this file during cmake */ 13 | /* If manual edits are made, the following tags should be modified accordingly. */ 14 | /* BINDTOOL_GEN_AUTOMATIC(0) */ 15 | /* BINDTOOL_USE_PYGCCXML(0) */ 16 | /* BINDTOOL_HEADER_FILE(time_mode.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(32d630ce5dd1485da350ac677846917e) */ 18 | /***********************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace py = pybind11; 25 | 26 | #include 27 | 28 | void bind_time_mode(py::module& m) 29 | { 30 | py::enum_<::gr::sigmf::sigmf_time_mode>(m,"sigmf_time_mode") 31 | .value("absolute", ::gr::sigmf::sigmf_time_mode::absolute) // 0 32 | .value("relative", ::gr::sigmf::sigmf_time_mode::relative) // 1 33 | .export_values() 34 | ; 35 | 36 | py::implicitly_convertible(); 37 | } 38 | -------------------------------------------------------------------------------- /lib/test_sigmf.cc: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2012 Free Software Foundation, Inc. 4 | * 5 | * This file is part of GNU Radio 6 | * 7 | * GNU Radio is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 3, or (at your option) 10 | * any later version. 11 | * 12 | * GNU Radio is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with GNU Radio; see the file COPYING. If not, write to 19 | * the Free Software Foundation, Inc., 51 Franklin Street, 20 | * Boston, MA 02110-1301, USA. 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | #include "config.h" 25 | #endif 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include "qa_sigmf.h" 34 | 35 | int 36 | main(int argc, char **argv) 37 | { 38 | CppUnit::TextTestRunner runner; 39 | std::ofstream xmlfile(get_unittest_path("sigmf.xml").c_str()); 40 | CppUnit::XmlOutputter *xmlout = new CppUnit::XmlOutputter(&runner.result(), xmlfile); 41 | 42 | runner.addTest(qa_sigmf::suite()); 43 | runner.setOutputter(xmlout); 44 | 45 | bool was_successful = runner.run("", false); 46 | 47 | return was_successful ? 0 : 1; 48 | } 49 | -------------------------------------------------------------------------------- /python/build_utils_codes.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2004 Free Software Foundation, Inc. 3 | # 4 | # This file is part of GNU Radio 5 | # 6 | # GNU Radio is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 3, or (at your option) 9 | # any later version. 10 | # 11 | # GNU Radio is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with GNU Radio; see the file COPYING. If not, write to 18 | # the Free Software Foundation, Inc., 51 Franklin Street, 19 | # Boston, MA 02110-1301, USA. 20 | # 21 | 22 | def i_code (code3): 23 | return code3[0] 24 | 25 | def o_code (code3): 26 | if len (code3) >= 2: 27 | return code3[1] 28 | else: 29 | return code3[0] 30 | 31 | def tap_code (code3): 32 | if len (code3) >= 3: 33 | return code3[2] 34 | else: 35 | return code3[0] 36 | 37 | def i_type (code3): 38 | return char_to_type[i_code (code3)] 39 | 40 | def o_type (code3): 41 | return char_to_type[o_code (code3)] 42 | 43 | def tap_type (code3): 44 | return char_to_type[tap_code (code3)] 45 | 46 | 47 | char_to_type = {} 48 | char_to_type['s'] = 'short' 49 | char_to_type['i'] = 'int' 50 | char_to_type['f'] = 'float' 51 | char_to_type['c'] = 'gr_complex' 52 | char_to_type['b'] = 'unsigned char' 53 | -------------------------------------------------------------------------------- /docs/doxygen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Free Software Foundation, Inc. 2 | # 3 | # This file was generated by gr_modtool, a tool from the GNU Radio framework 4 | # This file is a part of gr-sigmf 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | ######################################################################## 10 | # Create the doxygen configuration file 11 | ######################################################################## 12 | file(TO_NATIVE_PATH ${CMAKE_SOURCE_DIR} top_srcdir) 13 | file(TO_NATIVE_PATH ${CMAKE_BINARY_DIR} top_builddir) 14 | file(TO_NATIVE_PATH ${CMAKE_SOURCE_DIR} abs_top_srcdir) 15 | file(TO_NATIVE_PATH ${CMAKE_BINARY_DIR} abs_top_builddir) 16 | 17 | set(HAVE_DOT ${DOXYGEN_DOT_FOUND}) 18 | set(enable_html_docs YES) 19 | set(enable_latex_docs NO) 20 | set(enable_mathjax NO) 21 | set(enable_xml_docs YES) 22 | 23 | configure_file( 24 | ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in 25 | ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 26 | @ONLY) 27 | 28 | set(BUILT_DIRS ${CMAKE_CURRENT_BINARY_DIR}/xml ${CMAKE_CURRENT_BINARY_DIR}/html) 29 | 30 | ######################################################################## 31 | # Make and install doxygen docs 32 | ######################################################################## 33 | add_custom_command( 34 | OUTPUT ${BUILT_DIRS} 35 | COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 36 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 37 | COMMENT "Generating documentation with doxygen" 38 | ) 39 | 40 | add_custom_target(doxygen_target ALL DEPENDS ${BUILT_DIRS}) 41 | 42 | install(DIRECTORY ${BUILT_DIRS} DESTINATION ${GR_PKG_DOC_DIR}) 43 | -------------------------------------------------------------------------------- /python/bindings/docstrings/sink_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | #include "pydoc_macros.h" 10 | #define D(...) DOC(gr,sigmf, __VA_ARGS__ ) 11 | /* 12 | This file contains placeholders for docstrings for the Python bindings. 13 | Do not edit! These were automatically extracted during the binding process 14 | and will be overwritten during the build process 15 | */ 16 | 17 | 18 | 19 | static const char *__doc_gr_sigmf_sink = R"doc()doc"; 20 | 21 | 22 | static const char *__doc_gr_sigmf_sink_sink_0 = R"doc()doc"; 23 | 24 | 25 | static const char *__doc_gr_sigmf_sink_sink_1 = R"doc()doc"; 26 | 27 | 28 | static const char *__doc_gr_sigmf_sink_make = R"doc()doc"; 29 | 30 | 31 | static const char *__doc_gr_sigmf_sink_get_data_path = R"doc()doc"; 32 | 33 | 34 | static const char *__doc_gr_sigmf_sink_get_meta_path = R"doc()doc"; 35 | 36 | 37 | static const char *__doc_gr_sigmf_sink_set_global_meta_0 = R"doc()doc"; 38 | 39 | 40 | static const char *__doc_gr_sigmf_sink_set_global_meta_1 = R"doc()doc"; 41 | 42 | 43 | static const char *__doc_gr_sigmf_sink_set_global_meta_2 = R"doc()doc"; 44 | 45 | 46 | static const char *__doc_gr_sigmf_sink_set_global_meta_3 = R"doc()doc"; 47 | 48 | 49 | static const char *__doc_gr_sigmf_sink_set_global_meta_4 = R"doc()doc"; 50 | 51 | 52 | static const char *__doc_gr_sigmf_sink_set_global_meta_5 = R"doc()doc"; 53 | 54 | 55 | static const char *__doc_gr_sigmf_sink_set_annotation_meta = R"doc()doc"; 56 | 57 | 58 | static const char *__doc_gr_sigmf_sink_set_capture_meta = R"doc()doc"; 59 | 60 | 61 | static const char *__doc_gr_sigmf_sink_open = R"doc()doc"; 62 | 63 | 64 | static const char *__doc_gr_sigmf_sink_close = R"doc()doc"; 65 | 66 | 67 | -------------------------------------------------------------------------------- /python/bindings/docstrings/nmea_parser_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | #include "pydoc_macros.h" 10 | #define D(...) DOC(gr,sigmf, __VA_ARGS__ ) 11 | /* 12 | This file contains placeholders for docstrings for the Python bindings. 13 | Do not edit! These were automatically extracted during the binding process 14 | and will be overwritten during the build process 15 | */ 16 | 17 | 18 | 19 | static const char *__doc_gr_sigmf_nmea_message = R"doc()doc"; 20 | 21 | 22 | static const char *__doc_gr_sigmf_nmea_message_nmea_message_0 = R"doc()doc"; 23 | 24 | 25 | static const char *__doc_gr_sigmf_nmea_message_nmea_message_1 = R"doc()doc"; 26 | 27 | 28 | static const char *__doc_gr_sigmf_gprmc_message = R"doc()doc"; 29 | 30 | 31 | static const char *__doc_gr_sigmf_gprmc_message_gprmc_message_0 = R"doc()doc"; 32 | 33 | 34 | static const char *__doc_gr_sigmf_gprmc_message_gprmc_message_1 = R"doc()doc"; 35 | 36 | 37 | static const char *__doc_gr_sigmf_gprmc_message_parse = R"doc()doc"; 38 | 39 | 40 | static const char *__doc_gr_sigmf_gpgga_message = R"doc()doc"; 41 | 42 | 43 | static const char *__doc_gr_sigmf_gpgga_message_gpgga_message_0 = R"doc()doc"; 44 | 45 | 46 | static const char *__doc_gr_sigmf_gpgga_message_gpgga_message_1 = R"doc()doc"; 47 | 48 | 49 | static const char *__doc_gr_sigmf_gpgga_message_parse = R"doc()doc"; 50 | 51 | 52 | static const char *__doc_gr_sigmf_nmea_split = R"doc()doc"; 53 | 54 | 55 | static const char *__doc_gr_sigmf_nmea_extract = R"doc()doc"; 56 | 57 | 58 | static const char *__doc_gr_sigmf_nmea_parse_degrees = R"doc()doc"; 59 | 60 | 61 | static const char *__doc_gr_sigmf_nmea_parse_magnetic_variation = R"doc()doc"; 62 | 63 | 64 | static const char *__doc_gr_sigmf_nmea_parse_datetime = R"doc()doc"; 65 | 66 | 67 | -------------------------------------------------------------------------------- /lib/usrp_gps_message_source_impl.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifndef INCLUDED_SIGMF_USRP_GPS_MESSAGE_SOURCE_IMPL_H 22 | #define INCLUDED_SIGMF_USRP_GPS_MESSAGE_SOURCE_IMPL_H 23 | 24 | #include 25 | #include 26 | 27 | namespace gr { 28 | namespace sigmf { 29 | 30 | class usrp_gps_message_source_impl : public usrp_gps_message_source 31 | { 32 | private: 33 | bool d_finished; 34 | double d_poll_interval; 35 | size_t d_mboard; 36 | ::uhd::usrp::multi_usrp::sptr d_usrp; 37 | gr::thread::thread d_poll_thread; 38 | 39 | void poll_thread(); 40 | 41 | public: 42 | usrp_gps_message_source_impl(const ::uhd::device_addr_t &uhd_args, double poll_interval); 43 | usrp_gps_message_source_impl(::uhd::usrp::multi_usrp::sptr usrp_ptr, double poll_interval); 44 | ~usrp_gps_message_source_impl(); 45 | 46 | bool start(); 47 | bool stop(); 48 | 49 | void poll_now(); 50 | }; 51 | 52 | } // namespace sigmf 53 | } // namespace gr 54 | 55 | #endif /* INCLUDED_SIGMF_USRP_GPS_MESSAGE_SOURCE_IMPL_H */ 56 | 57 | -------------------------------------------------------------------------------- /python/bindings/python_bindings.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | 10 | #include 11 | 12 | #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 13 | #include 14 | 15 | namespace py = pybind11; 16 | 17 | // Headers for binding functions 18 | /**************************************/ 19 | // The following comment block is used for 20 | // gr_modtool to insert function prototypes 21 | // Please do not delete 22 | /**************************************/ 23 | // BINDING_FUNCTION_PROTOTYPES( 24 | void bind_time_mode(py::module& m); 25 | void bind_nmea_parser(py::module& m); 26 | void bind_sink(py::module& m); 27 | void bind_source(py::module& m); 28 | void bind_annotation_sink(py::module& m); 29 | void bind_usrp_gps_message_source(py::module& m); 30 | // ) END BINDING_FUNCTION_PROTOTYPES 31 | 32 | 33 | // We need this hack because import_array() returns NULL 34 | // for newer Python versions. 35 | // This function is also necessary because it ensures access to the C API 36 | // and removes a warning. 37 | void* init_numpy() 38 | { 39 | import_array(); 40 | return NULL; 41 | } 42 | 43 | PYBIND11_MODULE(sigmf_python, m) 44 | { 45 | // Initialize the numpy C API 46 | // (otherwise we will see segmentation faults) 47 | init_numpy(); 48 | 49 | // Allow access to base block methods 50 | py::module::import("gnuradio.gr"); 51 | 52 | /**************************************/ 53 | // The following comment block is used for 54 | // gr_modtool to insert binding function calls 55 | // Please do not delete 56 | /**************************************/ 57 | // BINDING_FUNCTION_CALLS( 58 | bind_time_mode(m); 59 | bind_nmea_parser(m); 60 | bind_sink(m); 61 | bind_source(m); 62 | bind_annotation_sink(m); 63 | bind_usrp_gps_message_source(m); 64 | // ) END BINDING_FUNCTION_CALLS 65 | } 66 | -------------------------------------------------------------------------------- /python/bindings/usrp_gps_message_source_python.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | 10 | /***********************************************************************************/ 11 | /* This file is automatically generated using bindtool and can be manually edited */ 12 | /* The following lines can be configured to regenerate this file during cmake */ 13 | /* If manual edits are made, the following tags should be modified accordingly. */ 14 | /* BINDTOOL_GEN_AUTOMATIC(0) */ 15 | /* BINDTOOL_USE_PYGCCXML(0) */ 16 | /* BINDTOOL_HEADER_FILE(usrp_gps_message_source.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(0f820784683d00e616f20e19ea7a627e) */ 18 | /***********************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace py = pybind11; 25 | 26 | #include 27 | // pydoc.h is automatically generated in the build directory 28 | #include 29 | 30 | void bind_usrp_gps_message_source(py::module& m) 31 | { 32 | 33 | using usrp_gps_message_source = ::gr::sigmf::usrp_gps_message_source; 34 | 35 | py::class_>(m, "usrp_gps_message_source", D(usrp_gps_message_source)) 37 | 38 | // Use a lambda here so that we can explicitly convert UHD args to a device_addr_t, 39 | // and thus resolve the overloaded make() method. 40 | .def(py::init([](const std::string uhd_args, double poll_interval) { 41 | ::uhd::device_addr_t addr(uhd_args); 42 | return usrp_gps_message_source::make(addr, poll_interval); 43 | })) 44 | ; 45 | } 46 | -------------------------------------------------------------------------------- /docs/doxygen/doxyxml/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2010 Free Software Foundation, Inc. 3 | # 4 | # This file was generated by gr_modtool, a tool from the GNU Radio framework 5 | # This file is a part of gr-sigmf 6 | # 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | # 9 | # 10 | """ 11 | Python interface to contents of doxygen xml documentation. 12 | 13 | Example use: 14 | See the contents of the example folder for the C++ and 15 | doxygen-generated xml used in this example. 16 | 17 | >>> # Parse the doxygen docs. 18 | >>> import os 19 | >>> this_dir = os.path.dirname(globals()['__file__']) 20 | >>> xml_path = this_dir + "/example/xml/" 21 | >>> di = DoxyIndex(xml_path) 22 | 23 | Get a list of all top-level objects. 24 | 25 | >>> print([mem.name() for mem in di.members()]) 26 | [u'Aadvark', u'aadvarky_enough', u'main'] 27 | 28 | Get all functions. 29 | 30 | >>> print([mem.name() for mem in di.in_category(DoxyFunction)]) 31 | [u'aadvarky_enough', u'main'] 32 | 33 | Check if an object is present. 34 | 35 | >>> di.has_member(u'Aadvark') 36 | True 37 | >>> di.has_member(u'Fish') 38 | False 39 | 40 | Get an item by name and check its properties. 41 | 42 | >>> aad = di.get_member(u'Aadvark') 43 | >>> print(aad.brief_description) 44 | Models the mammal Aadvark. 45 | >>> print(aad.detailed_description) 46 | Sadly the model is incomplete and cannot capture all aspects of an aadvark yet. 47 | 48 | This line is uninformative and is only to test line breaks in the comments. 49 | >>> [mem.name() for mem in aad.members()] 50 | [u'aadvarkness', u'print', u'Aadvark', u'get_aadvarkness'] 51 | >>> aad.get_member(u'print').brief_description 52 | u'Outputs the vital aadvark statistics.' 53 | 54 | """ 55 | 56 | from .doxyindex import DoxyIndex, DoxyFunction, DoxyParam, DoxyClass, DoxyFile, DoxyNamespace, DoxyGroup, DoxyFriend, DoxyOther 57 | 58 | def _test(): 59 | import os 60 | this_dir = os.path.dirname(globals()['__file__']) 61 | xml_path = this_dir + "/example/xml/" 62 | di = DoxyIndex(xml_path) 63 | # Get the Aadvark class 64 | aad = di.get_member('Aadvark') 65 | aad.brief_description 66 | import doctest 67 | return doctest.testmod() 68 | 69 | if __name__ == "__main__": 70 | _test() 71 | 72 | -------------------------------------------------------------------------------- /docs/doxygen/doxyxml/generated/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Generated Mon Feb 9 19:08:05 2009 by generateDS.py. 5 | """ 6 | 7 | from xml.dom import minidom 8 | 9 | import os 10 | import sys 11 | from . import compound 12 | 13 | from . import indexsuper as supermod 14 | 15 | class DoxygenTypeSub(supermod.DoxygenType): 16 | def __init__(self, version=None, compound=None): 17 | supermod.DoxygenType.__init__(self, version, compound) 18 | 19 | def find_compounds_and_members(self, details): 20 | """ 21 | Returns a list of all compounds and their members which match details 22 | """ 23 | 24 | results = [] 25 | for compound in self.compound: 26 | members = compound.find_members(details) 27 | if members: 28 | results.append([compound, members]) 29 | else: 30 | if details.match(compound): 31 | results.append([compound, []]) 32 | 33 | return results 34 | 35 | supermod.DoxygenType.subclass = DoxygenTypeSub 36 | # end class DoxygenTypeSub 37 | 38 | 39 | class CompoundTypeSub(supermod.CompoundType): 40 | def __init__(self, kind=None, refid=None, name='', member=None): 41 | supermod.CompoundType.__init__(self, kind, refid, name, member) 42 | 43 | def find_members(self, details): 44 | """ 45 | Returns a list of all members which match details 46 | """ 47 | 48 | results = [] 49 | 50 | for member in self.member: 51 | if details.match(member): 52 | results.append(member) 53 | 54 | return results 55 | 56 | supermod.CompoundType.subclass = CompoundTypeSub 57 | # end class CompoundTypeSub 58 | 59 | 60 | class MemberTypeSub(supermod.MemberType): 61 | 62 | def __init__(self, kind=None, refid=None, name=''): 63 | supermod.MemberType.__init__(self, kind, refid, name) 64 | 65 | supermod.MemberType.subclass = MemberTypeSub 66 | # end class MemberTypeSub 67 | 68 | 69 | def parse(inFilename): 70 | 71 | doc = minidom.parse(inFilename) 72 | rootNode = doc.documentElement 73 | rootObj = supermod.DoxygenType.factory() 74 | rootObj.build(rootNode) 75 | 76 | return rootObj 77 | 78 | -------------------------------------------------------------------------------- /lib/writer_utils.cc: -------------------------------------------------------------------------------- 1 | #include "writer_utils.h" 2 | 3 | #define RAPIDJSON_HAS_STDSTRING 1 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace gr { 11 | namespace sigmf { 12 | namespace writer_utils { 13 | void 14 | write_meta_to_fp(FILE *fp, 15 | const meta_namespace &global, 16 | std::vector &captures, // TODO: These should 17 | // probably be const 18 | std::vector &annotations) 19 | { 20 | char write_buf[65536]; 21 | rapidjson::FileWriteStream file_stream(fp, write_buf, sizeof(write_buf)); 22 | 23 | rapidjson::PrettyWriter writer(file_stream); 24 | writer.StartObject(); 25 | 26 | writer.String("global"); 27 | global.serialize(writer); 28 | 29 | writer.String("captures"); 30 | writer.StartArray(); 31 | for(std::vector::iterator it = captures.begin(); it != captures.end(); it++) { 32 | (*it).serialize(writer); 33 | } 34 | writer.EndArray(); 35 | 36 | 37 | // sort annotations 38 | std::sort(annotations.begin(), annotations.end(), 39 | [](const meta_namespace &a, const meta_namespace &b) { 40 | // TODO: This may need to become a more complex sort if the spec 41 | // changes based on https://github.com/gnuradio/SigMF/issues/90 42 | return pmt::to_uint64(a.get("core:sample_start")) < 43 | pmt::to_uint64(b.get("core:sample_start")); 44 | }); 45 | 46 | writer.String("annotations"); 47 | writer.StartArray(); 48 | for(std::vector::iterator it = annotations.begin(); 49 | it != annotations.end(); it++) { 50 | (*it).serialize(writer); 51 | } 52 | writer.EndArray(); 53 | 54 | writer.EndObject(); 55 | } 56 | } // namespace writer_utils 57 | } // namespace sigmf 58 | } // namespace gr -------------------------------------------------------------------------------- /python/bindings/bind_oot_file.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | import argparse 3 | import os 4 | from gnuradio.bindtool import BindingGenerator 5 | import pathlib 6 | import sys 7 | 8 | parser = argparse.ArgumentParser(description='Bind a GR Out of Tree Block') 9 | parser.add_argument('--module', type=str, 10 | help='Name of gr module containing file to bind (e.g. fft digital analog)') 11 | 12 | parser.add_argument('--output_dir', default='/tmp', 13 | help='Output directory of generated bindings') 14 | parser.add_argument('--prefix', help='Prefix of Installed GNU Radio') 15 | parser.add_argument('--src', help='Directory of gnuradio source tree', 16 | default=os.path.dirname(os.path.abspath(__file__))+'/../../..') 17 | 18 | parser.add_argument( 19 | '--filename', help="File to be parsed") 20 | 21 | parser.add_argument( 22 | '--defines', help='Set additional defines for precompiler',default=(), nargs='*') 23 | parser.add_argument( 24 | '--include', help='Additional Include Dirs, separated', default=(), nargs='*') 25 | 26 | parser.add_argument( 27 | '--status', help='Location of output file for general status (used during cmake)', default=None 28 | ) 29 | parser.add_argument( 30 | '--flag_automatic', default='0' 31 | ) 32 | parser.add_argument( 33 | '--flag_pygccxml', default='0' 34 | ) 35 | 36 | args = parser.parse_args() 37 | 38 | prefix = args.prefix 39 | output_dir = args.output_dir 40 | defines = tuple(','.join(args.defines).split(',')) 41 | includes = ','.join(args.include) 42 | name = args.module 43 | 44 | namespace = ['gr', name] 45 | prefix_include_root = name 46 | 47 | 48 | with warnings.catch_warnings(): 49 | warnings.filterwarnings("ignore", category=DeprecationWarning) 50 | 51 | bg = BindingGenerator(prefix, namespace, 52 | prefix_include_root, output_dir, define_symbols=defines, addl_includes=includes, 53 | catch_exceptions=False, write_json_output=False, status_output=args.status, 54 | flag_automatic=True if args.flag_automatic.lower() in [ 55 | '1', 'true'] else False, 56 | flag_pygccxml=True if args.flag_pygccxml.lower() in ['1', 'true'] else False) 57 | bg.gen_file_binding(args.filename) 58 | -------------------------------------------------------------------------------- /lib/sigmf_utils.cc: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2017 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include "sigmf/sigmf_utils.h" 22 | #include 23 | #include 24 | #include 25 | 26 | namespace fs = boost::filesystem; 27 | 28 | namespace gr { 29 | namespace sigmf { 30 | fs::path 31 | to_data_path(const std::string &filename) 32 | { 33 | fs::path data_path(filename); 34 | data_path.replace_extension(".sigmf-data"); 35 | return data_path; 36 | } 37 | 38 | fs::path 39 | meta_path_from_data(fs::path data_path) 40 | { 41 | fs::path meta_path(data_path); 42 | meta_path.replace_extension(".sigmf-meta"); 43 | return meta_path; 44 | } 45 | 46 | format_detail_t 47 | parse_format_str(const std::string &format_str) 48 | { 49 | 50 | boost::regex format_regex("(r|c)((f|i|u)(8|16|32))(_(le|be))?"); 51 | boost::smatch result; 52 | 53 | if(boost::regex_match(format_str, result, format_regex)) { 54 | format_detail_t detail; 55 | detail.is_complex = result[1] == "c"; 56 | detail.type_str = result[2]; 57 | detail.width = boost::lexical_cast(result[4]); 58 | if(result[6].matched) { 59 | detail.endianness = result[6] == "le" ? LITTLE : BIG; 60 | } 61 | return detail; 62 | } else { 63 | throw std::runtime_error("bad format str"); 64 | } 65 | } 66 | } // namespace sigmf 67 | } // namespace gr 68 | -------------------------------------------------------------------------------- /lib/annotation_sink_impl.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018 Paul Wicks 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifndef INCLUDED_SIGMF_ANNOTATION_SINK_IMPL_H 22 | #define INCLUDED_SIGMF_ANNOTATION_SINK_IMPL_H 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | namespace gr { 29 | namespace sigmf { 30 | 31 | pmt::pmt_t SAMPLE_START_KEY = pmt::mp("core:sample_start"); 32 | pmt::pmt_t SAMPLE_COUNT_KEY = pmt::mp("core:sample_count"); 33 | pmt::pmt_t ANNO_TIME_KEY = pmt::mp("time"); 34 | pmt::pmt_t ANNO_DURATION_KEY = pmt::mp("duration"); 35 | 36 | class annotation_sink_impl : public annotation_sink { 37 | private: 38 | annotation_filter_strategy d_filter_strategy; 39 | std::regex d_filter_key_regex; 40 | 41 | boost::filesystem::path d_data_path; 42 | boost::filesystem::path d_meta_path; 43 | FILE *d_meta_fp; 44 | 45 | meta_namespace d_global; 46 | std::vector d_captures; 47 | std::vector d_annotations; 48 | 49 | double d_sample_rate; 50 | 51 | sigmf_time_mode d_time_mode; 52 | 53 | // Start time as uhd time tuple 54 | pmt::pmt_t d_start_time; 55 | 56 | std::regex glob_to_regex(const std::string &filter_glob); 57 | 58 | void add_annotation(pmt::pmt_t annotation_msg); 59 | 60 | void write_metadata(); 61 | bool open(); 62 | void load_metadata(); 63 | 64 | public: 65 | annotation_sink_impl(std::string filename, annotation_mode mode, sigmf_time_mode time_mode); 66 | ~annotation_sink_impl() = default; 67 | 68 | bool stop(); 69 | }; 70 | 71 | } // namespace sigmf 72 | } // namespace gr 73 | 74 | #endif /* INCLUDED_SIGMF_ANNOTATION_SINK_IMPL_H */ 75 | -------------------------------------------------------------------------------- /python/bindings/source_python.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | 10 | /***********************************************************************************/ 11 | /* This file is automatically generated using bindtool and can be manually edited */ 12 | /* The following lines can be configured to regenerate this file during cmake */ 13 | /* If manual edits are made, the following tags should be modified accordingly. */ 14 | /* BINDTOOL_GEN_AUTOMATIC(0) */ 15 | /* BINDTOOL_USE_PYGCCXML(0) */ 16 | /* BINDTOOL_HEADER_FILE(source.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(692a3a1e91b9075e6720c5fc39f47711) */ 18 | /***********************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace py = pybind11; 25 | 26 | #include 27 | // pydoc.h is automatically generated in the build directory 28 | #include 29 | 30 | void bind_source(py::module& m) 31 | { 32 | 33 | using source = ::gr::sigmf::source; 34 | 35 | 36 | py::class_>(m, "source", D(source)) 38 | 39 | .def(py::init(&source::make), 40 | py::arg("filename"), 41 | py::arg("output_datatype"), 42 | py::arg("repeat") = false, 43 | D(source,make) 44 | ) 45 | 46 | 47 | 48 | 49 | 50 | 51 | .def_static("make_no_datatype",&source::make_no_datatype, 52 | py::arg("filename"), 53 | py::arg("repeat") = false, 54 | D(source,make_no_datatype) 55 | ) 56 | 57 | 58 | 59 | .def("set_begin_tag",&source::set_begin_tag, 60 | py::arg("val"), 61 | D(source,set_begin_tag) 62 | ) 63 | 64 | 65 | 66 | .def("global_meta",&source::global_meta, 67 | D(source,global_meta) 68 | ) 69 | 70 | 71 | 72 | .def("capture_segments",&source::capture_segments, 73 | D(source,capture_segments) 74 | ) 75 | 76 | ; 77 | 78 | 79 | 80 | 81 | } 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Free Software Foundation, Inc. 2 | # 3 | # This file is part of GNU Radio 4 | # 5 | # GNU Radio is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 3, or (at your option) 8 | # any later version. 9 | # 10 | # GNU Radio is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with GNU Radio; see the file COPYING. If not, write to 17 | # the Free Software Foundation, Inc., 51 Franklin Street, 18 | # Boston, MA 02110-1301, USA. 19 | 20 | ######################################################################## 21 | # Include python install macros 22 | ######################################################################## 23 | include(GrPython) 24 | if(NOT PYTHONINTERP_FOUND) 25 | return() 26 | endif() 27 | 28 | add_subdirectory(bindings) 29 | 30 | ######################################################################## 31 | # Install python sources 32 | ######################################################################## 33 | GR_PYTHON_INSTALL( 34 | FILES 35 | __init__.py 36 | DESTINATION ${GR_PYTHON_DIR}/gr_sigmf 37 | ) 38 | 39 | ######################################################################## 40 | # Handle the unit tests 41 | ######################################################################## 42 | include(GrTest) 43 | 44 | set(GR_TEST_TARGET_DEPS gnuradio-sigmf) 45 | 46 | GR_ADD_TEST(qa_annotation_sink ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_annotation_sink.py) 47 | GR_ADD_TEST(qa_archive ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_archive.py) 48 | GR_ADD_TEST(qa_hash ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_hash.py) 49 | GR_ADD_TEST(qa_sink ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_sink.py) 50 | GR_ADD_TEST(qa_source ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_source.py) 51 | GR_ADD_TEST(qa_source_to_sink ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_source_to_sink.py) 52 | GR_ADD_TEST(qa_type_converter ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_type_converter.py) 53 | GR_ADD_TEST(qa_crop ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_crop.py) 54 | GR_ADD_TEST(qa_nmea_parser ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_nmea_parser.py) 55 | -------------------------------------------------------------------------------- /python/bindings/annotation_sink_python.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | 10 | /***********************************************************************************/ 11 | /* This file is automatically generated using bindtool and can be manually edited */ 12 | /* The following lines can be configured to regenerate this file during cmake */ 13 | /* If manual edits are made, the following tags should be modified accordingly. */ 14 | /* BINDTOOL_GEN_AUTOMATIC(0) */ 15 | /* BINDTOOL_USE_PYGCCXML(0) */ 16 | /* BINDTOOL_HEADER_FILE(annotation_sink.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(81a2aaaf74bc612b3ec9aa67457595b2) */ 18 | /***********************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace py = pybind11; 25 | 26 | #include 27 | // pydoc.h is automatically generated in the build directory 28 | #include 29 | 30 | void bind_annotation_sink(py::module& m) 31 | { 32 | 33 | using annotation_mode = ::gr::sigmf::annotation_mode; 34 | using annotation_sink = ::gr::sigmf::annotation_sink; 35 | 36 | 37 | py::class_>(m, "annotation_mode") 39 | .def(py::init(), py::arg("arg0")) 40 | .def_static("keep", &annotation_mode::keep) 41 | .def_static("clear",&annotation_mode::clear, py::arg("filter")) 42 | ; 43 | 44 | py::class_>(m, "annotation_sink", D(annotation_sink)) 46 | 47 | .def(py::init(&annotation_sink::make), 48 | py::arg("filename"), 49 | py::arg("mode"), 50 | py::arg("time_mode") = ::gr::sigmf::sigmf_time_mode::relative, 51 | D(annotation_sink,make) 52 | ) 53 | ; 54 | 55 | py::enum_<::gr::sigmf::annotation_filter_strategy>(m,"annotation_filter_strategy") 56 | .value("clear_existing", ::gr::sigmf::annotation_filter_strategy::clear_existing) // 0 57 | .value("keep_existing", ::gr::sigmf::annotation_filter_strategy::keep_existing) // 1 58 | .export_values() 59 | ; 60 | 61 | py::implicitly_convertible(); 62 | } 63 | -------------------------------------------------------------------------------- /swig/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Free Software Foundation, Inc. 2 | # 3 | # This file is part of GNU Radio 4 | # 5 | # GNU Radio is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 3, or (at your option) 8 | # any later version. 9 | # 10 | # GNU Radio is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with GNU Radio; see the file COPYING. If not, write to 17 | # the Free Software Foundation, Inc., 51 Franklin Street, 18 | # Boston, MA 02110-1301, USA. 19 | 20 | ######################################################################## 21 | # Setup swig generation 22 | ######################################################################## 23 | if(NOT sigmf_sources) 24 | MESSAGE(STATUS "No C++ sources... skipping swig/") 25 | return() 26 | endif(NOT sigmf_sources) 27 | 28 | ######################################################################## 29 | # Include swig generation macros 30 | ######################################################################## 31 | find_package(SWIG) 32 | find_package(PythonLibs 3) 33 | if(NOT SWIG_FOUND OR NOT PYTHONLIBS_FOUND) 34 | return() 35 | endif() 36 | include(GrSwig) 37 | include(GrPython) 38 | 39 | ######################################################################## 40 | # Setup swig generation 41 | ######################################################################## 42 | set(GR_SWIG_INCLUDE_DIRS $) 43 | set(GR_SWIG_TARGET_DEPS gnuradio::runtime_swig) 44 | 45 | set(GR_SWIG_LIBRARIES gnuradio-sigmf) 46 | set(GR_SWIG_DOC_FILE ${CMAKE_CURRENT_BINARY_DIR}/sigmf_swig_doc.i) 47 | set(GR_SWIG_DOC_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../include) 48 | 49 | GR_SWIG_MAKE(gr_sigmf_swig gr_sigmf_swig.i) 50 | 51 | ######################################################################## 52 | # Install the build swig module 53 | ######################################################################## 54 | GR_SWIG_INSTALL( 55 | TARGETS gr_sigmf_swig 56 | DESTINATION ${GR_PYTHON_DIR}/gr_sigmf 57 | ) 58 | 59 | ######################################################################## 60 | # Install swig .i files for development 61 | ######################################################################## 62 | install( 63 | FILES 64 | gr_sigmf_swig.i 65 | ${CMAKE_CURRENT_BINARY_DIR}/sigmf_swig_doc.i 66 | DESTINATION ${GR_INCLUDE_DIR}/sigmf/swig 67 | ) 68 | -------------------------------------------------------------------------------- /include/sigmf/sigmf_utils.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2017 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | namespace gr { 26 | namespace sigmf { 27 | 28 | /*! 29 | * \brief convert a filename to the path of a .sigmf-data file 30 | * @param filename the filename 31 | * @return the path as a boost::filesystem::path object 32 | * 33 | * For example, filename is `/foo/bar/baz` then the returned path would 34 | * be `/foo/bar/bas.sigmf-data`. Any existing extension will be replaced 35 | */ 36 | boost::filesystem::path to_data_path(const std::string &filename) SIGMF_API; 37 | 38 | /*! 39 | * \brief convert a data path to the path of a .sigmf-meta file 40 | * @param data_path path to data 41 | * @return the path as a boost::filesystem::path object 42 | */ 43 | boost::filesystem::path meta_path_from_data(boost::filesystem::path data_path) SIGMF_API; 44 | 45 | //! An enum describing endianness 46 | enum endian_t { 47 | //! Little endian 48 | LITTLE = 0, 49 | //! Big endian 50 | BIG = 1 51 | }; 52 | 53 | //! struct representing a parsed SigMF format 54 | struct SIGMF_API format_detail_t { 55 | //! true if the format is a complex type, false otherwise 56 | bool is_complex; 57 | //! base type string, so no r or c and no _le or _be 58 | std::string type_str; 59 | //! size of the format in bits 60 | size_t width; 61 | //! endinness of the format 62 | endian_t endianness; 63 | }; 64 | 65 | /*! 66 | * \brief Parse the dataset format 67 | * defined by SigMF into a format_detail_t struct. 68 | * @param format_str format as a sttring 69 | * @exception std::runtime_error invalid format string 70 | * @return the parsed struct 71 | */ 72 | format_detail_t parse_format_str(const std::string &format_str) SIGMF_API; 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /include/sigmf/usrp_gps_message_source.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | 22 | #ifndef INCLUDED_SIGMF_USRP_GPS_MESSAGE_SOURCE_H 23 | #define INCLUDED_SIGMF_USRP_GPS_MESSAGE_SOURCE_H 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | namespace gr { 30 | namespace sigmf { 31 | 32 | /*! 33 | * \brief Emit PMT messages with GPS sensor data from a USRP. 34 | * \ingroup sigmf 35 | * 36 | */ 37 | class SIGMF_API usrp_gps_message_source : virtual public gr::block 38 | { 39 | public: 40 | typedef std::shared_ptr sptr; 41 | 42 | /*! 43 | * \brief Return a shared_ptr to a new instance of sigmf::usrp_gps_message_source. 44 | * @param uhd_args the args specifiying the usrp to use 45 | * @param poll_interval the polling interval in seconds 46 | * To avoid accidental use of raw pointers, sigmf::usrp_gps_message_source's 47 | * constructor is in a private implementation 48 | * class. sigmf::usrp_gps_message_source::make is the public interface for 49 | * creating new instances. 50 | */ 51 | static sptr make(const ::uhd::device_addr_t &uhd_args, double poll_interval); 52 | 53 | /*! 54 | * \brief Return a shared_ptr to a new instance of sigmf::usrp_gps_message_source. 55 | * @param usrp_ptr a pointer to an already open usrp 56 | * @param poll_interval the polling interval in seconds 57 | * To avoid accidental use of raw pointers, sigmf::usrp_gps_message_source's 58 | * constructor is in a private implementation 59 | * class. sigmf::usrp_gps_message_source::make is the public interface for 60 | * creating new instances. 61 | */ 62 | static sptr make(::uhd::usrp::multi_usrp::sptr usrp_ptr, double poll_interval); 63 | }; 64 | 65 | } // namespace sigmf 66 | } // namespace gr 67 | 68 | #endif /* INCLUDED_SIGMF_USRP_GPS_MESSAGE_SOURCE_H */ 69 | 70 | -------------------------------------------------------------------------------- /include/sigmf/source.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2017 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifndef INCLUDED_SIGMF_SOURCE_H 22 | #define INCLUDED_SIGMF_SOURCE_H 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | namespace gr { 29 | namespace sigmf { 30 | 31 | /*! 32 | * \brief Source Block to read from SigMF recordings. 33 | * \ingroup sigmf 34 | * 35 | */ 36 | class SIGMF_API source : virtual public gr::sync_block { 37 | public: 38 | typedef std::shared_ptr sptr; 39 | 40 | /*! 41 | * \brief Return a shared_ptr to a new instance of sigmf::source. 42 | * 43 | * To avoid accidental use of raw pointers, sigmf::source's 44 | * constructor is in a private implementation 45 | * class. sigmf::source::make is the public interface for 46 | * creating new instances. 47 | */ 48 | static sptr 49 | make(std::string filename, std::string output_datatype, bool repeat = false); 50 | 51 | /*! 52 | * \brief Return a shared_ptr to a new instance of sigmf::source. 53 | * 54 | * This constructor will build a sink that will always use the 55 | * native datatype of the input file as the output datatype. 56 | */ 57 | static sptr 58 | make_no_datatype(std::string filename, bool repeat = false); 59 | 60 | /*! 61 | * \brief Add a stream tag to the first sample of the file if true 62 | * @param val the tag to add 63 | */ 64 | virtual void set_begin_tag(pmt::pmt_t val) = 0; 65 | 66 | /*! 67 | * \brief retrieve the global metadata for this source 68 | */ 69 | virtual gr::sigmf::meta_namespace &global_meta() = 0; 70 | 71 | /*! 72 | * \brief retrieve the capture segments for this source 73 | */ 74 | virtual std::vector &capture_segments() = 0; 75 | }; 76 | 77 | } // namespace sigmf 78 | } // namespace gr 79 | 80 | #endif /* INCLUDED_SIGMF_SOURCE_H */ 81 | -------------------------------------------------------------------------------- /python/bindings/header_utils.py: -------------------------------------------------------------------------------- 1 | # Utilities for reading values in header files 2 | 3 | from argparse import ArgumentParser 4 | import re 5 | 6 | 7 | class PybindHeaderParser: 8 | def __init__(self, pathname): 9 | with open(pathname,'r') as f: 10 | self.file_txt = f.read() 11 | 12 | def get_flag_automatic(self): 13 | # p = re.compile(r'BINDTOOL_GEN_AUTOMATIC\(([^\s])\)') 14 | # m = p.search(self.file_txt) 15 | m = re.search(r'BINDTOOL_GEN_AUTOMATIC\(([^\s])\)', self.file_txt) 16 | if (m and m.group(1) == '1'): 17 | return True 18 | else: 19 | return False 20 | 21 | def get_flag_pygccxml(self): 22 | # p = re.compile(r'BINDTOOL_USE_PYGCCXML\(([^\s])\)') 23 | # m = p.search(self.file_txt) 24 | m = re.search(r'BINDTOOL_USE_PYGCCXML\(([^\s])\)', self.file_txt) 25 | if (m and m.group(1) == '1'): 26 | return True 27 | else: 28 | return False 29 | 30 | def get_header_filename(self): 31 | # p = re.compile(r'BINDTOOL_HEADER_FILE\(([^\s]*)\)') 32 | # m = p.search(self.file_txt) 33 | m = re.search(r'BINDTOOL_HEADER_FILE\(([^\s]*)\)', self.file_txt) 34 | if (m): 35 | return m.group(1) 36 | else: 37 | return None 38 | 39 | def get_header_file_hash(self): 40 | # p = re.compile(r'BINDTOOL_HEADER_FILE_HASH\(([^\s]*)\)') 41 | # m = p.search(self.file_txt) 42 | m = re.search(r'BINDTOOL_HEADER_FILE_HASH\(([^\s]*)\)', self.file_txt) 43 | if (m): 44 | return m.group(1) 45 | else: 46 | return None 47 | 48 | def get_flags(self): 49 | return f'{self.get_flag_automatic()};{self.get_flag_pygccxml()};{self.get_header_filename()};{self.get_header_file_hash()};' 50 | 51 | 52 | 53 | def argParse(): 54 | """Parses commandline args.""" 55 | desc='Reads the parameters from the comment block in the pybind files' 56 | parser = ArgumentParser(description=desc) 57 | 58 | parser.add_argument("function", help="Operation to perform on comment block of pybind file", choices=["flag_auto","flag_pygccxml","header_filename","header_file_hash","all"]) 59 | parser.add_argument("pathname", help="Pathname of pybind c++ file to read, e.g. blockname_python.cc") 60 | 61 | return parser.parse_args() 62 | 63 | if __name__ == "__main__": 64 | # Parse command line options and set up doxyxml. 65 | args = argParse() 66 | 67 | pbhp = PybindHeaderParser(args.pathname) 68 | 69 | if args.function == "flag_auto": 70 | print(pbhp.get_flag_automatic()) 71 | elif args.function == "flag_pygccxml": 72 | print(pbhp.get_flag_pygccxml()) 73 | elif args.function == "header_filename": 74 | print(pbhp.get_header_filename()) 75 | elif args.function == "header_file_hash": 76 | print(pbhp.get_header_file_hash()) 77 | elif args.function == "all": 78 | print(pbhp.get_flags()) -------------------------------------------------------------------------------- /python/qa_archive.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import os 3 | import shutil 4 | import tarfile 5 | 6 | import numpy as np 7 | from gnuradio import gr_unittest 8 | 9 | import apps_test_helper 10 | 11 | 12 | class qa_archive(gr_unittest.TestCase): 13 | 14 | def setUp(self): 15 | 16 | # Create a temporary directory 17 | self.test_dir = tempfile.mkdtemp() 18 | 19 | def tearDown(self): 20 | 21 | # Remove the directory after the test 22 | shutil.rmtree(self.test_dir) 23 | 24 | def test_archive(self): 25 | 26 | runner = apps_test_helper.AppRunner(self.test_dir, "sigmf-archive") 27 | 28 | filename = "temp" 29 | 30 | file_path = os.path.join(self.test_dir, filename) 31 | data_file = file_path + apps_test_helper.SIGMF_DATASET_EXT 32 | archive_file = file_path + ".sigmf" 33 | 34 | apps_test_helper.run_flowgraph(data_file) 35 | 36 | # archive 37 | proc = runner.run("archive " + data_file) 38 | out, err = proc.communicate() 39 | 40 | sigmf_tarfile = tarfile.open(archive_file, 41 | mode="r", format=tarfile.PAX_FORMAT) 42 | files = sigmf_tarfile.getmembers() 43 | 44 | file_extensions = {apps_test_helper.SIGMF_DATASET_EXT, 45 | apps_test_helper.SIGMF_METADATA_EXT} 46 | for f in files: 47 | 48 | # layout 49 | assert tarfile.TarInfo.isfile(f) 50 | f_dir = os.path.split(f.name)[0] 51 | 52 | # names and extensions 53 | assert f_dir == filename 54 | f_name, f_ext = os.path.splitext(f.name) 55 | assert f_ext in file_extensions 56 | if f.name.endswith(apps_test_helper.SIGMF_METADATA_EXT): 57 | m_file = f 58 | elif f.name.endswith(apps_test_helper.SIGMF_DATASET_EXT): 59 | d_file = f 60 | assert os.path.split(f_name)[1] == f_dir 61 | 62 | # permissions 63 | # assert f.mode == 0o644 64 | 65 | # type 66 | assert sigmf_tarfile.format == tarfile.PAX_FORMAT 67 | 68 | # extract 69 | proc = runner.run("extract " + archive_file) 70 | out, err = proc.communicate() 71 | 72 | # content 73 | meta_expected = open(os.path.join( 74 | self.test_dir, 75 | filename + apps_test_helper.SIGMF_METADATA_EXT), 76 | "r") 77 | meta_actual = open(os.path.join(self.test_dir, m_file.name), "r") 78 | assert meta_expected.read() == meta_actual.read() 79 | 80 | data_expected = open(os.path.join( 81 | self.test_dir, 82 | filename + apps_test_helper.SIGMF_DATASET_EXT), 83 | "rb") 84 | data_actual = open(os.path.join(self.test_dir, d_file.name), "rb") 85 | de = np.fromstring(data_expected.read()).all() 86 | da = np.fromstring(data_actual.read()).all() 87 | assert de == da 88 | 89 | 90 | if __name__ == '__main__': 91 | gr_unittest.run(qa_archive) 92 | -------------------------------------------------------------------------------- /include/sigmf/annotation_sink.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018 Paul Wicks 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | 22 | #ifndef INCLUDED_SIGMF_ANNOTATION_SINK_H 23 | #define INCLUDED_SIGMF_ANNOTATION_SINK_H 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | 31 | namespace gr { 32 | namespace sigmf { 33 | 34 | enum class annotation_filter_strategy { 35 | clear_existing, 36 | keep_existing 37 | }; 38 | 39 | 40 | /** 41 | * Determines how the annotation sink deals 42 | * with existing annotations. If keep(), then 43 | * they are all kept. If clear(), then existing 44 | * annotations are cleared, optionally with a globbing 45 | * expression as a filter for the annotations to remove 46 | */ 47 | struct SIGMF_API annotation_mode { 48 | annotation_filter_strategy filter_strategy; 49 | std::string filter_key; 50 | 51 | static annotation_mode keep() { 52 | return annotation_mode(annotation_filter_strategy::keep_existing); 53 | } 54 | 55 | static annotation_mode clear(std::string filter) { 56 | return annotation_mode(annotation_filter_strategy::clear_existing, filter); 57 | } 58 | 59 | protected: 60 | annotation_mode(annotation_filter_strategy strat, std::string key = "") 61 | : filter_strategy(strat), filter_key(key) {} 62 | }; 63 | 64 | /*! 65 | * \brief Sink block for writing annotations to an existing dataset 66 | * \ingroup sigmf 67 | * 68 | */ 69 | class SIGMF_API annotation_sink : virtual public gr::block 70 | { 71 | public: 72 | typedef std::shared_ptr sptr; 73 | 74 | /*! 75 | * \brief Return a shared_ptr to a new instance of sigmf::annotation_sink. 76 | * 77 | * To avoid accidental use of raw pointers, sigmf::annotation_sink's 78 | * constructor is in a private implementation 79 | * class. sigmf::annotation_sink::make is the public interface for 80 | * creating new instances. 81 | */ 82 | static sptr make(std::string filename, 83 | annotation_mode mode, 84 | sigmf_time_mode time_mode = sigmf_time_mode::relative); 85 | }; 86 | 87 | } // namespace sigmf 88 | } // namespace gr 89 | 90 | #endif /* INCLUDED_SIGMF_ANNOTATION_SINK_H */ 91 | 92 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | AccessModifierOffset: 0 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlinesLeft: true 7 | AlignOperands: false 8 | AlignTrailingComments: true 9 | 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: false 14 | AllowShortIfStatementsOnASingleLine: true 15 | AllowShortLoopsOnASingleLine: false 16 | 17 | AlwaysBreakAfterReturnType: AllDefinitions 18 | AlwaysBreakBeforeMultilineStrings: true 19 | AlwaysBreakTemplateDeclarations: true 20 | 21 | BinPackArguments: true 22 | BinPackParameters: false 23 | 24 | BreakBeforeBinaryOperators: false 25 | BreakBeforeBraces: Custom 26 | 27 | BraceWrapping: 28 | AfterClass: false 29 | AfterControlStatement: false 30 | AfterEnum: false 31 | AfterFunction: true 32 | AfterNamespace: false 33 | AfterObjCDeclaration: false 34 | AfterStruct: false 35 | AfterUnion: false 36 | BeforeCatch: true 37 | BeforeElse: false 38 | IndentBraces: false 39 | 40 | BreakBeforeTernaryOperators: false 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakStringLiterals: true 43 | 44 | ColumnLimit: 90 45 | # CommentPragmas: # Don't need this at the moment, might need later 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 0 48 | ContinuationIndentWidth: 2 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | 52 | IncludeBlocks: Regroup 53 | IncludeCategories: 54 | - Regex: '^"' 55 | Priority: 1 56 | - Regex: '^(\<|")sigmf\/' 57 | Priority: 1 58 | - Regex: '^\ 25 | #include 26 | #include 27 | #include 28 | 29 | namespace gr { 30 | namespace sigmf { 31 | 32 | class SIGMF_API nmea_message { 33 | }; 34 | 35 | class SIGMF_API gprmc_message : public nmea_message { 36 | public: 37 | uint32_t timestamp; 38 | std::string date; 39 | std::string time; 40 | bool valid; 41 | double lat; 42 | double lon; 43 | double speed_knots; 44 | double track_angle; 45 | double magnetic_variation; 46 | 47 | gprmc_message(uint32_t timestamp, 48 | std::string date, 49 | std::string time, 50 | bool valid, 51 | double lat, 52 | double lon, 53 | double speed_knots, 54 | double track_angle, 55 | double magnetic_variation); 56 | 57 | static gprmc_message parse(std::string raw); 58 | }; 59 | 60 | class SIGMF_API gpgga_message : public nmea_message { 61 | public: 62 | std::string time; 63 | double lat; 64 | double lon; 65 | uint32_t fix_quality; 66 | uint32_t num_sats; 67 | double hdop; 68 | double altitude_msl; 69 | double geoid_hae; 70 | 71 | gpgga_message(std::string time, 72 | double lat, 73 | double lon, 74 | uint32_t fix_quality, 75 | uint32_t num_sats, 76 | double hdop, 77 | double altitude_msl, 78 | double geoid_hae); 79 | 80 | static gpgga_message parse(std::string raw); 81 | }; 82 | 83 | SIGMF_API std::vector nmea_split(std::string s); 84 | 85 | SIGMF_API std::string nmea_extract(std::string raw); 86 | 87 | SIGMF_API double nmea_parse_degrees(std::string value, std::string dir); 88 | 89 | SIGMF_API double nmea_parse_magnetic_variation(std::string value, std::string dir); 90 | 91 | SIGMF_API std::time_t nmea_parse_datetime(std::string date, std::string time); 92 | 93 | } // namespace sigmf 94 | } // namespace gr 95 | 96 | #endif /* INCLUDED_SIGMF_NMEA_PARSER_H */ 97 | 98 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011,2012,2016,2018,2019 Free Software Foundation, Inc. 2 | # 3 | # This file was generated by gr_modtool, a tool from the GNU Radio framework 4 | # This file is a part of gr-sigmf 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | ######################################################################## 10 | # Setup library 11 | ######################################################################## 12 | include(GrPlatform) #define LIB_SUFFIX 13 | 14 | include_directories(${Boost_INCLUDE_DIR} ${RapidJson_INCLUDE_DIR}) 15 | link_directories(${Boost_LIBRARY_DIRS}) 16 | 17 | find_package(UHD REQUIRED) 18 | include_directories(${UHD_INCLUDE_DIRS}) 19 | 20 | # Tell boost time to use higher precision 21 | add_definitions(-DBOOST_DATE_TIME_POSIX_TIME_STD_CONFIG) 22 | 23 | list(APPEND sigmf_sources 24 | meta_namespace.cc 25 | nmea_parser.cc 26 | sink_impl.cc 27 | source_impl.cc 28 | sigmf_utils.cc 29 | annotation_sink_impl.cc 30 | writer_utils.cc 31 | reader_utils.cc 32 | usrp_gps_message_source_impl.cc 33 | ) 34 | 35 | set(sigmf_sources "${sigmf_sources}" PARENT_SCOPE) 36 | if(NOT sigmf_sources) 37 | MESSAGE(STATUS "No C++ sources... skipping lib/") 38 | return() 39 | endif(NOT sigmf_sources) 40 | 41 | add_library(gnuradio-sigmf SHARED ${sigmf_sources}) 42 | target_link_libraries(gnuradio-sigmf 43 | ${UHD_LIBRARIES} 44 | ${Boost_LIBRARIES} 45 | gnuradio::gnuradio-runtime 46 | gnuradio::gnuradio-uhd 47 | gnuradio::gnuradio-blocks 48 | ) 49 | target_include_directories(gnuradio-sigmf 50 | PUBLIC $ 51 | PUBLIC $ 52 | ) 53 | set_target_properties(gnuradio-sigmf PROPERTIES DEFINE_SYMBOL "gnuradio_sigmf_EXPORTS") 54 | 55 | if(APPLE) 56 | set_target_properties(gnuradio-sigmf PROPERTIES 57 | INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib" 58 | ) 59 | endif(APPLE) 60 | 61 | ######################################################################## 62 | # Install built library files 63 | ######################################################################## 64 | include(GrMiscUtils) 65 | GR_LIBRARY_FOO(gnuradio-sigmf) 66 | 67 | ######################################################################## 68 | # Print summary 69 | ######################################################################## 70 | message(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") 71 | message(STATUS "Building for version: ${VERSION} / ${LIBVER}") 72 | 73 | ######################################################################## 74 | # Build and register unit test 75 | ######################################################################## 76 | include(GrTest) 77 | 78 | # If your unit tests require special include paths, add them here 79 | #include_directories() 80 | # List all files that contain Boost.UTF unit tests here 81 | list(APPEND test_sigmf_sources 82 | ) 83 | # Anything we need to link to for the unit tests go here 84 | list(APPEND GR_TEST_TARGET_DEPS gnuradio-sigmf) 85 | 86 | if(NOT test_sigmf_sources) 87 | MESSAGE(STATUS "No C++ unit tests... skipping") 88 | return() 89 | endif(NOT test_sigmf_sources) 90 | 91 | foreach(qa_file ${test_sigmf_sources}) 92 | GR_ADD_CPP_TEST("sigmf_${qa_file}" 93 | ${CMAKE_CURRENT_SOURCE_DIR}/${qa_file} 94 | ) 95 | endforeach(qa_file) 96 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to gr-sigmf will be documented in this file. 3 | Note that changes before 1.0.2 are not reflected in this file. 4 | ## 2.1.0 5 | * Migrated module to GNU Radio 3.8 6 | 7 | ## 2.0.0 8 | 9 | * Renamed module to gr_sigmf to avoid name conflict with official gnuradio sigmf python module 10 | * Fix an error in parsing of certain gps messages in gps_message_source 11 | * Fix a bug in the source block that caused some tags to have bad offsets 12 | * Improved a few tests 13 | * open throws an exception on an error, rather than return false 14 | * Remove `core:length` support, as it was removed from the standard 15 | * Improved error messages for `sigmf-record` 16 | * Added more tests 17 | * Fixed a bug in source block releated to repeats 18 | 19 | ## 1.1.6 20 | * Add support for writing `core:length` to capture segments 21 | * Update `sigmf-record` to write `core:recorder` field 22 | * Improvements to documentation 23 | * Fixed an error in gps logging 24 | * Sink block will now throw an error if an invalid file is supplied 25 | * Change the default time mode of the sink to be relative 26 | * Handle thread interruption correctly in usrp_gps_message block 27 | * Add a sigmf-crop tool 28 | * Miscellaneous fixes and improvements 29 | 30 | ## 1.1.5 31 | 32 | * Add support for building a pkg-config file when building gr-sigmf 33 | * Fixed flaky test: test_relative_time_mode_initial_closed 34 | * Cleaned up output of blocks to stdout and stderr in preference to standard logging macros 35 | * Update the handler for the close command pmt message to call `do_update` immediately 36 | This fixes an issue where a lot of time may pass between when the command to 37 | close is received and when the work function runs again. 38 | * Fixed a couple other flaky tests 39 | * Updates and improvements to the USRP GPS Message Source 40 | * Miscellaneous fixes and improvements 41 | 42 | ## 1.1.4 43 | 44 | * Update the grc file for the annotation sink to correspond to the actual sink 45 | * Add documentation in annotation_sink.h for annotation_mode struct 46 | * Fix an edge case in time tag handling when in relative mode in sink block 47 | * Make --freq a required parameter to sigmf-record 48 | * Add USRP GPS Message Source 49 | 50 | ## 1.1.3 51 | 52 | * Fixed a bug in the logging configuration for gr-sigmf 53 | * Updated .gitignore for gr-sigmf 54 | 55 | ## 1.1.2 56 | 57 | * Fix a bug in the sink grc file 58 | 59 | ## 1.1.1 60 | 61 | * Fix a bug in `sigmf-record` that caused int-N tuning not to get set 62 | * Fix a bug related to setting the subdev spec in `sigmf-record`. It is now set before all other parameters instead of last 63 | * Improved the wait logic in `sigmf-record` to allow the user to cancel in the case that the duration is specified and it takes longer than expected to receive the needed samples 64 | 65 | ## 1.1.0 66 | 67 | * Add annotation sink to gr-sigmf 68 | * Fix bug that caused deleted keys in namespaces to not actually delete 69 | * Refactor `sink_time_mode` to `sigmf_time_mode` now that it is needed in multiple places 70 | * Add a get method to `meta_namespace` that takes a single pmt object as a key 71 | * Make the `print` method of `meta_namespace` const 72 | * Change how ondisk metadata is loaded to ensure that sample_rate is always loaded as a double 73 | 74 | ## 1.0.3 75 | 76 | * Fixed a bug that caused numerical values over int32 max to not be loaded correctly 77 | 78 | ## 1.0.2 79 | 80 | * Fixed a bug in how offsets were computed for tags in the source block 81 | -------------------------------------------------------------------------------- /lib/source_impl.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2017 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifndef INCLUDED_SIGMF_SOURCE_IMPL_H 22 | #define INCLUDED_SIGMF_SOURCE_IMPL_H 23 | 24 | #include 25 | #include 26 | #include 27 | #include "type_converter.h" 28 | 29 | namespace gr { 30 | namespace sigmf { 31 | 32 | static const pmt::pmt_t COMMAND = pmt::mp("command"); 33 | static const pmt::pmt_t META = pmt::mp("meta"); 34 | static const pmt::pmt_t TAG_KEY = pmt::string_to_symbol("tag"); 35 | 36 | class source_impl : public source { 37 | private: 38 | FILE *d_data_fp; 39 | FILE *d_meta_fp; 40 | 41 | // size of a sample 42 | size_t d_sample_size; 43 | 44 | // base size of a data, might be item_size / 2 45 | size_t d_base_size; 46 | size_t d_input_size; 47 | int d_num_samps_to_base; 48 | 49 | bool d_repeat; 50 | bool d_file_begin; 51 | 52 | pmt::pmt_t d_add_begin_tag; 53 | pmt::pmt_t d_id; 54 | 55 | // A multimap of tags to output that maps from tag index to tag 56 | std::multimap d_tags_to_output; 57 | size_t d_num_samples_in_file; 58 | 59 | uint64_t d_repeat_count; 60 | 61 | boost::mutex d_open_mutex; 62 | 63 | boost::filesystem::path d_data_path; 64 | boost::filesystem::path d_meta_path; 65 | 66 | convert_function_t d_convert_func; 67 | 68 | meta_namespace d_global; 69 | std::vector d_captures; 70 | std::vector d_annotations; 71 | 72 | boost::posix_time::ptime iso_string_to_ptime(const std::string &str); 73 | 74 | void on_command_message(pmt::pmt_t msg); 75 | 76 | bool open(); 77 | void load_metadata(); 78 | void build_tag_list(); 79 | void add_global_tags(const meta_namespace &global_segment); 80 | void add_tags_from_meta_list(const std::vector &meta_list, uint64_t shift_amount); 81 | void emit_tags(uint64_t window_start, int window_length); 82 | 83 | public: 84 | source_impl(std::string filename, std::string type, bool repeat); 85 | ~source_impl(); 86 | 87 | // Where all the action really happens 88 | int work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items); 89 | 90 | void set_begin_tag(pmt::pmt_t tag); 91 | 92 | gr::sigmf::meta_namespace &global_meta(); 93 | std::vector &capture_segments(); 94 | }; 95 | 96 | } // namespace sigmf 97 | } // namespace gr 98 | 99 | #endif /* INCLUDED_SIGMF_SOURCE_IMPL_H */ 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gr-sigmf: GNU Radio SigMF Blocks 2 | 3 | This module contains blocks to read from and write to SigMF (the Signal 4 | Metadata Format) recordings in GNU Radio. 5 | 6 | Currently gr-sigmf is best described as alpha software. Basic interactions 7 | work, but features are not complete and the API should be considered somewhat 8 | unstable. We welcome any feature requests or bug reports. Pull requests are 9 | fine too, but will be met with more success if you make an issue first. 10 | 11 | Data correctness issues will be prioritized over reliability issues, which will 12 | in turn be prioritized over new feature development. 13 | 14 | ## Spec Version 15 | 16 | Note that `gr-sigmf` does not yet target version 1.0 of the SigMF 17 | specification. Once a SigMF 1.0 spec is finalized, this OOT module will be 18 | updated soon after. 19 | 20 | ## Quick Start 21 | 22 | Dependencies required: 23 | 24 | * GNU Radio 25 | * RapidJSON 26 | * Swig (for Python support) 27 | * UHD (for USRP recording and playback tools) 28 | 29 | To install dependencies on Ubuntu 18.04 LTS: 30 | 31 | $ sudo apt install rapidjson-dev swig gnuradio libuhd-dev 32 | 33 | To install from source: 34 | 35 | $ git clone git@github.com:skysafe/gr-sigmf.git 36 | $ cd gr-sigmf 37 | $ mkdir build; cd build 38 | $ cmake .. 39 | $ make 40 | $ sudo make install 41 | 42 | To make a SigMF recording using an Ettus Research USRP: 43 | 44 | $ sigmf-record --sample-rate 10e6 --freq 88.5e6 --gain 30 example.sigmf 45 | 46 | Note that the `gr-sigmf` Python module is named `gr_sigmf` to avoid conflicts with the 47 | [official GNURadio sigmf module](https://github.com/gnuradio/SigMF). This is 48 | *different* from the typical GNU Radio OOT module convention, where the Python 49 | module would simply be named `sigmf`. 50 | 51 | ## Design Principles 52 | 53 | * Correctly and completely implement the [SigMF 54 | Specification](https://github.com/gnuradio/SigMF/blob/master/sigmf-spec.md). 55 | * Be a "good GNU Radio citizen", and interact in useful ways with the existing 56 | core blocks, existing OOT blocks, and hardware interfaces such as ``gr-uhd``. 57 | 58 | ## Contents 59 | 60 | * A set of blocks for reading and writing SigMF datasets 61 | * sink: Create SigMF datasets 62 | * source: Read SigMF datasets 63 | * annotation_sink: Write SigMF metadata only 64 | * usrp_gps_message_source: Surface gps metadata from a usrp as messages so it 65 | can easily be used by a SigMF sink. 66 | 67 | * A set of command line tools for creating/working with SigMF datasets. They 68 | include: 69 | * sigmf-record: Create SigMF datasets from NI/Ettus radios 70 | * sigmf-play: Replay SigMF datasets to NI/Ettus radios 71 | * sigmf-archive: Convert SigMF datasets to and from archive files 72 | * sigmf-crop: Extract subsections from SigMF datasets 73 | * sigmf-hash: Verify and calculate hashes for SigMF datasets 74 | 75 | ## Roadmap 76 | 77 | ### Near Future 78 | 79 | * Output metadata via message (for support in applications like 'such samples') 80 | * Make sure that UHD generates/reads stream tags that are nicely handled by 81 | SigMF blocks. 82 | * Make waterfall, frequency, and time sinks interact well with stream tags as 83 | translated from metadata. 84 | * Build stream tag converter block to convert uhd stream tags to tags with keys 85 | that match sigmf keys 86 | 87 | ### Farther Future 88 | 89 | * Dynamic control over recording state via message 90 | * Tar archive support 91 | * Multi-channel recording support, if it is introduced into the main spec 92 | * Automatic file rotation? 93 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Free Software Foundation, Inc. 2 | # 3 | # This file is part of GNU Radio 4 | # 5 | # GNU Radio is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 3, or (at your option) 8 | # any later version. 9 | # 10 | # GNU Radio is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with GNU Radio; see the file COPYING. If not, write to 17 | # the Free Software Foundation, Inc., 51 Franklin Street, 18 | # Boston, MA 02110-1301, USA. 19 | 20 | include(GrPython) 21 | 22 | ######################################################################## 23 | # Dependencies 24 | ######################################################################## 25 | 26 | include_directories(${Boost_INCLUDE_DIRS}) 27 | include_directories(${RapidJson_INCLUDE_DIR}) 28 | 29 | ## UHD 30 | find_package(UHD REQUIRED) 31 | include_directories(${UHD_INCLUDE_DIRS}) 32 | 33 | #################################################### 34 | ############### sigmf-record ####################### 35 | #################################################### 36 | 37 | set(SIGMF_RECORD_SRCFILES 38 | "sigmf_record.cc" 39 | ) 40 | 41 | find_package(Threads) 42 | 43 | add_executable(sigmf-record 44 | ${SIGMF_RECORD_SRCFILES} 45 | ) 46 | 47 | target_link_libraries (sigmf-record ${CMAKE_THREAD_LIBS_INIT}) 48 | # get_cmake_property(_variableNames VARIABLES) 49 | # foreach (_variableName ${_variableNames}) 50 | # message(STATUS "${_variableName}=${${_variableName}}") 51 | # endforeach() 52 | 53 | target_link_libraries(sigmf-record 54 | ${UHD_LIBRARIES} 55 | ${Boost_LIBRARIES} 56 | gnuradio::gnuradio-runtime 57 | gnuradio::gnuradio-uhd 58 | gnuradio::gnuradio-blocks 59 | gnuradio-sigmf 60 | ) 61 | 62 | install(TARGETS sigmf-record DESTINATION bin) 63 | 64 | #################################################### 65 | ############### sigmf-play ######################### 66 | #################################################### 67 | 68 | set(SIGMF_PLAY_SRCFILES 69 | "sigmf_play.cc" 70 | ) 71 | 72 | add_executable(sigmf-play 73 | ${SIGMF_PLAY_SRCFILES} 74 | ) 75 | 76 | target_link_libraries(sigmf-play 77 | ${UHD_LIBRARIES} 78 | ${Boost_LIBRARIES} 79 | gnuradio::gnuradio-runtime 80 | gnuradio::gnuradio-uhd 81 | gnuradio::gnuradio-blocks 82 | gnuradio-sigmf 83 | ) 84 | 85 | install(TARGETS sigmf-play DESTINATION bin) 86 | 87 | #################################################### 88 | ############### sigmf-crop ######################### 89 | #################################################### 90 | 91 | set(SIGMF_CROP_SRCFILES 92 | "sigmf_crop.cc" 93 | ) 94 | 95 | add_executable(sigmf-crop 96 | ${SIGMF_CROP_SRCFILES} 97 | ) 98 | 99 | target_link_libraries(sigmf-crop 100 | ${UHD_LIBRARIES} 101 | ${Boost_LIBRARIES} 102 | gnuradio::gnuradio-runtime 103 | gnuradio::gnuradio-uhd 104 | gnuradio::gnuradio-blocks 105 | gnuradio-sigmf 106 | ) 107 | 108 | install(TARGETS sigmf-crop DESTINATION bin) 109 | 110 | #################################################### 111 | ############### Python-based apps ################## 112 | #################################################### 113 | GR_PYTHON_INSTALL( 114 | PROGRAMS 115 | sigmf-archive 116 | sigmf-hash 117 | DESTINATION ${GR_RUNTIME_DIR} 118 | COMPONENT "sigmf_python" 119 | ) 120 | -------------------------------------------------------------------------------- /apps/sigmf-archive: -------------------------------------------------------------------------------- 1 | # vim: set ft=python: 2 | 3 | import argparse 4 | import tarfile 5 | import re 6 | import os.path 7 | import sys 8 | from itertools import groupby 9 | 10 | # TODO: Add options for output name on archive 11 | # TODO: Add tests 12 | 13 | 14 | class SigmfArchive(object): 15 | 16 | def __init__(self): 17 | parser = argparse.ArgumentParser( 18 | description='Convert sigmf files to and from archives', 19 | usage='''sigmf-archive [] 20 | 21 | The available commands are: 22 | archive Put files in archive 23 | extract Extract files from archive 24 | ''') 25 | parser.add_argument('command', help='Subcommand to run') 26 | 27 | # parse_args defaults to [1:] for args, but you need to 28 | # exclude the rest of the args too, or validation will fail 29 | args = parser.parse_args(sys.argv[1:2]) 30 | if not hasattr(self, args.command): 31 | print("Unrecognized command") 32 | parser.print_help() 33 | exit(1) 34 | getattr(self, args.command)() 35 | 36 | def archive(self): 37 | parser = argparse.ArgumentParser( 38 | description='Archive files') 39 | parser.add_argument('files', nargs="+") 40 | parser.add_argument('--remove-files', action='store_true') 41 | 42 | args = parser.parse_args(sys.argv[2:]) 43 | file_list = args.files 44 | final_file_list = [] 45 | 46 | # Determine file names to archive 47 | for k, g in groupby(file_list, lambda f: os.path.splitext(f)[0]): 48 | group_list = list(g) 49 | if (len(group_list) == 1): 50 | base, ext = os.path.splitext(group_list[0]) 51 | if ext == ".sigmf-meta": 52 | other_file = base + ".sigmf-data" 53 | else: 54 | other_file = base + ".sigmf-meta" 55 | final_file_list.append(other_file) 56 | final_file_list.extend(group_list) 57 | 58 | # then we can just use the name of that file 59 | if len(final_file_list) == 2: 60 | output_file = os.path.splitext(final_file_list[0])[0] 61 | 62 | output_file += ".sigmf" 63 | 64 | # Archive the files 65 | tfile = tarfile.TarFile( 66 | name=output_file, mode="w", format=tarfile.PAX_FORMAT) 67 | for f in final_file_list: 68 | base_name = os.path.split(f)[1] 69 | base_name_sans_ext, ext = os.path.splitext(base_name) 70 | tfile_path = "{0}/{0}{1}".format(base_name_sans_ext, ext) 71 | tfile.add(f, tfile_path) 72 | 73 | # Remove the originals if requested 74 | if (args.remove_files): 75 | for f in final_file_list: 76 | os.remove(f) 77 | 78 | # TODO: Figure out how to name files when there is more than 1 data set 79 | 80 | def extract(self): 81 | parser = argparse.ArgumentParser( 82 | description='Extract files') 83 | parser.add_argument('files', nargs="+") 84 | parser.add_argument('--remove-files', action='store_true') 85 | args = parser.parse_args(sys.argv[2:]) 86 | file_list = args.files 87 | 88 | # Extract files 89 | for tar_file in file_list: 90 | tf = tarfile.TarFile(tar_file) 91 | members = tf.getmembers() 92 | for k, g in groupby( 93 | members, lambda m: re.search( 94 | r"([^\/]+)\/\1\.sigmf-(meta|data)", m.name)): 95 | for f in g: 96 | tf.extract(f, path=os.path.split(tar_file)[0]) 97 | 98 | # Delete archive if asked 99 | if (args.remove_files): 100 | for f in file_list: 101 | os.remove(f) 102 | 103 | 104 | if __name__ == '__main__': 105 | SigmfArchive() 106 | -------------------------------------------------------------------------------- /python/qa_nmea_parser.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | 4 | from gnuradio import gr_unittest 5 | 6 | try: 7 | import gr_sigmf as sigmf 8 | except ImportError: 9 | import sys 10 | dirname, filename = os.path.split(os.path.abspath(__file__)) 11 | sys.path.append(os.path.join(dirname, "bindings")) 12 | import gr_sigmf as sigmf 13 | 14 | 15 | # Example NMEA sentences from reference docs 16 | good_gprmc = \ 17 | "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A" 18 | good_gpgga = \ 19 | "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47" 20 | good_datetime = datetime(1994, 3, 23, 12, 35, 19) 21 | 22 | # Sentences emitted by Jackson Labs LC_XO with no fix 23 | good_gprmc_nofix = \ 24 | "$GPRMC,015534.00,V,0000.0000,N,00000.0000,E,0.0,0.0,060106,,*23" 25 | good_gpgga_nofix = \ 26 | "$GPGGA,015534.00,0000.0000,N,00000.0000,E,0,99,1.0,0.0,M,0.0,M,,*5A" 27 | 28 | # Sentences emitted by u-Blox LEA-M8 with no fix 29 | good_gprmc_empty = "$GPRMC,,V,,,,,,,,,*31" 30 | good_gpgga_empty = "$GPGGA,,,,,,0,00,99.99,,,,,,*48" 31 | 32 | # Intentionally malformed sentences 33 | bad_gprmc_missing_start = \ 34 | "GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A" 35 | bad_gprmc_missing_end = \ 36 | "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W" 37 | bad_gprmc_bad_checksum = \ 38 | "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*72" 39 | 40 | 41 | class qa_nmea_parser(gr_unittest.TestCase): 42 | 43 | def test_extract_good(self): 44 | payload = sigmf.nmea_extract(good_gprmc) 45 | self.assertTrue(payload.startswith("GPRMC")) 46 | 47 | def test_extract_missing_start(self): 48 | with self.assertRaises(ValueError): 49 | sigmf.nmea_extract(bad_gprmc_missing_start) 50 | 51 | def test_extract_missing_end(self): 52 | with self.assertRaises(ValueError): 53 | sigmf.nmea_extract(bad_gprmc_missing_end) 54 | 55 | def test_extract_bad_checksum(self): 56 | with self.assertRaises(ValueError): 57 | sigmf.nmea_extract(bad_gprmc_bad_checksum) 58 | 59 | def test_parse_gprmc(self): 60 | msg = sigmf.gprmc_message.parse(good_gprmc) 61 | self.assertAlmostEqual(msg.lat, 48.1173) 62 | self.assertAlmostEqual(msg.lon, 11.51666667) 63 | self.assertTrue(msg.valid) 64 | self.assertAlmostEqual(msg.speed_knots, 22.4) 65 | self.assertAlmostEqual(msg.track_angle, 84.4) 66 | self.assertAlmostEqual(msg.magnetic_variation, -3.1) 67 | 68 | dt = datetime.utcfromtimestamp(msg.timestamp) 69 | self.assertEqual(dt, good_datetime) 70 | 71 | self.assertEqual(msg.date, "230394") 72 | self.assertEqual(msg.time, "123519") 73 | 74 | def test_parse_gprmc_nofix(self): 75 | msg = sigmf.gprmc_message.parse(good_gprmc_nofix) 76 | self.assertFalse(msg.valid) 77 | 78 | def test_parse_gprmc_empty(self): 79 | msg = sigmf.gprmc_message.parse(good_gprmc_empty) 80 | self.assertFalse(msg.valid) 81 | 82 | def test_parse_gpgga(self): 83 | msg = sigmf.gpgga_message.parse(good_gpgga) 84 | self.assertAlmostEqual(msg.lat, 48.1173) 85 | self.assertAlmostEqual(msg.lon, 11.51666667) 86 | self.assertEqual(msg.fix_quality, 1) 87 | self.assertEqual(msg.num_sats, 8) 88 | self.assertAlmostEqual(msg.hdop, 0.9) 89 | self.assertAlmostEqual(msg.altitude_msl, 545.4) 90 | self.assertAlmostEqual(msg.geoid_hae, 46.9) 91 | 92 | self.assertEqual(msg.time, "123519") 93 | 94 | def test_parse_gpgga_nofix(self): 95 | msg = sigmf.gpgga_message.parse(good_gpgga_nofix) 96 | self.assertEqual(msg.fix_quality, 0) 97 | 98 | def test_parse_gpgga_empty(self): 99 | msg = sigmf.gpgga_message.parse(good_gpgga_empty) 100 | self.assertEqual(msg.fix_quality, 0) 101 | 102 | 103 | if __name__ == '__main__': 104 | gr_unittest.run(qa_nmea_parser) 105 | -------------------------------------------------------------------------------- /examples/sigmf-source.grc: -------------------------------------------------------------------------------- 1 | options: 2 | parameters: 3 | author: Cate Miller 4 | category: '[GRC Hier Blocks]' 5 | cmake_opt: '' 6 | comment: '' 7 | copyright: '' 8 | description: Example use of the SigMF Source block 9 | gen_cmake: 'On' 10 | gen_linking: dynamic 11 | generate_options: qt_gui 12 | hier_block_src_path: '.:' 13 | id: sigmf_source 14 | max_nouts: '0' 15 | output_language: python 16 | placement: (0,0) 17 | qt_qss_theme: '' 18 | realtime_scheduling: '' 19 | run: 'True' 20 | run_command: '{python} -u {filename}' 21 | run_options: prompt 22 | sizing_mode: fixed 23 | thread_safe_setters: '' 24 | title: SigMF Source 25 | window_size: '' 26 | states: 27 | bus_sink: false 28 | bus_source: false 29 | bus_structure: null 30 | coordinate: [8, 8] 31 | rotation: 0 32 | state: enabled 33 | 34 | blocks: 35 | - name: samp_rate 36 | id: variable 37 | parameters: 38 | comment: '' 39 | value: '32000' 40 | states: 41 | bus_sink: false 42 | bus_source: false 43 | bus_structure: null 44 | coordinate: [184, 12] 45 | rotation: 0 46 | state: enabled 47 | - name: blocks_message_debug_0 48 | id: blocks_message_debug 49 | parameters: 50 | affinity: '' 51 | alias: '' 52 | comment: '' 53 | states: 54 | bus_sink: false 55 | bus_source: false 56 | bus_structure: null 57 | coordinate: [288, 248] 58 | rotation: 0 59 | state: enabled 60 | - name: blocks_tag_debug_0 61 | id: blocks_tag_debug 62 | parameters: 63 | affinity: '' 64 | alias: '' 65 | comment: '' 66 | display: 'True' 67 | filter: '""' 68 | name: '' 69 | num_inputs: '1' 70 | type: complex 71 | vlen: '1' 72 | states: 73 | bus_sink: false 74 | bus_source: false 75 | bus_structure: null 76 | coordinate: [536, 204] 77 | rotation: 0 78 | state: enabled 79 | - name: blocks_throttle_0 80 | id: blocks_throttle 81 | parameters: 82 | affinity: '' 83 | alias: '' 84 | comment: '' 85 | ignoretag: 'True' 86 | maxoutbuf: '0' 87 | minoutbuf: '0' 88 | samples_per_second: samp_rate 89 | type: complex 90 | vlen: '1' 91 | states: 92 | bus_sink: false 93 | bus_source: false 94 | bus_structure: null 95 | coordinate: [264, 132] 96 | rotation: 0 97 | state: enabled 98 | - name: qtgui_waterfall_sink_x_0 99 | id: qtgui_waterfall_sink_x 100 | parameters: 101 | affinity: '' 102 | alias: '' 103 | alpha1: '1.0' 104 | alpha10: '1.0' 105 | alpha2: '1.0' 106 | alpha3: '1.0' 107 | alpha4: '1.0' 108 | alpha5: '1.0' 109 | alpha6: '1.0' 110 | alpha7: '1.0' 111 | alpha8: '1.0' 112 | alpha9: '1.0' 113 | axislabels: 'True' 114 | bw: samp_rate 115 | color1: '0' 116 | color10: '0' 117 | color2: '0' 118 | color3: '0' 119 | color4: '0' 120 | color5: '0' 121 | color6: '0' 122 | color7: '0' 123 | color8: '0' 124 | color9: '0' 125 | comment: '' 126 | fc: '0' 127 | fftsize: '1024' 128 | freqhalf: 'True' 129 | grid: 'False' 130 | gui_hint: '' 131 | int_max: '10' 132 | int_min: '-140' 133 | label1: '' 134 | label10: '' 135 | label2: '' 136 | label3: '' 137 | label4: '' 138 | label5: '' 139 | label6: '' 140 | label7: '' 141 | label8: '' 142 | label9: '' 143 | legend: 'True' 144 | maxoutbuf: '0' 145 | minoutbuf: '0' 146 | name: '""' 147 | nconnections: '1' 148 | showports: 'True' 149 | type: complex 150 | update_time: '0.10' 151 | wintype: firdes.WIN_BLACKMAN_hARRIS 152 | states: 153 | bus_sink: false 154 | bus_source: false 155 | bus_structure: null 156 | coordinate: [512, 116] 157 | rotation: 0 158 | state: enabled 159 | - name: sigmf_source_0 160 | id: sigmf_source 161 | parameters: 162 | affinity: '' 163 | alias: '' 164 | comment: '' 165 | debug: 'False' 166 | filename: out 167 | maxoutbuf: '0' 168 | minoutbuf: '0' 169 | repeat: 'True' 170 | type: fc32 171 | states: 172 | bus_sink: false 173 | bus_source: false 174 | bus_structure: null 175 | coordinate: [40, 132] 176 | rotation: 0 177 | state: enabled 178 | 179 | connections: 180 | - [blocks_throttle_0, '0', blocks_tag_debug_0, '0'] 181 | - [blocks_throttle_0, '0', qtgui_waterfall_sink_x_0, '0'] 182 | - [sigmf_source_0, '0', blocks_throttle_0, '0'] 183 | - [sigmf_source_0, meta, blocks_message_debug_0, print] 184 | 185 | metadata: 186 | file_format: 1 187 | -------------------------------------------------------------------------------- /python/bindings/sink_python.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | 10 | /***********************************************************************************/ 11 | /* This file is automatically generated using bindtool and can be manually edited */ 12 | /* The following lines can be configured to regenerate this file during cmake */ 13 | /* If manual edits are made, the following tags should be modified accordingly. */ 14 | /* BINDTOOL_GEN_AUTOMATIC(0) */ 15 | /* BINDTOOL_USE_PYGCCXML(0) */ 16 | /* BINDTOOL_HEADER_FILE(sink.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(8961b7a36fba749488f5ced350c2a6af) */ 18 | /***********************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace py = pybind11; 25 | 26 | #include 27 | // pydoc.h is automatically generated in the build directory 28 | #include 29 | 30 | void bind_sink(py::module& m) 31 | { 32 | 33 | using sink = ::gr::sigmf::sink; 34 | 35 | 36 | py::class_>(m, "sink", D(sink)) 38 | 39 | .def(py::init(&sink::make), 40 | py::arg("type"), 41 | py::arg("filename"), 42 | py::arg("time_mode") = ::gr::sigmf::sigmf_time_mode::absolute, 43 | py::arg("append") = false, 44 | D(sink,make) 45 | ) 46 | 47 | 48 | 49 | 50 | 51 | 52 | .def("get_data_path",&sink::get_data_path, 53 | D(sink,get_data_path) 54 | ) 55 | 56 | 57 | 58 | .def("get_meta_path",&sink::get_meta_path, 59 | D(sink,get_meta_path) 60 | ) 61 | 62 | 63 | 64 | .def("set_global_meta",(void (sink::*)(std::string const &, pmt::pmt_t))&sink::set_global_meta, 65 | py::arg("key"), 66 | py::arg("val"), 67 | D(sink,set_global_meta,0) 68 | ) 69 | 70 | 71 | 72 | .def("set_global_meta",(void (sink::*)(std::string const &, double))&sink::set_global_meta, 73 | py::arg("key"), 74 | py::arg("val"), 75 | D(sink,set_global_meta,1) 76 | ) 77 | 78 | 79 | 80 | .def("set_global_meta",(void (sink::*)(std::string const &, int64_t))&sink::set_global_meta, 81 | py::arg("key"), 82 | py::arg("val"), 83 | D(sink,set_global_meta,2) 84 | ) 85 | 86 | 87 | 88 | .def("set_global_meta",(void (sink::*)(std::string const &, uint64_t))&sink::set_global_meta, 89 | py::arg("key"), 90 | py::arg("val"), 91 | D(sink,set_global_meta,3) 92 | ) 93 | 94 | 95 | 96 | .def("set_global_meta",(void (sink::*)(std::string const &, std::string const &))&sink::set_global_meta, 97 | py::arg("key"), 98 | py::arg("val"), 99 | D(sink,set_global_meta,4) 100 | ) 101 | 102 | 103 | 104 | .def("set_global_meta",(void (sink::*)(std::string const &, bool))&sink::set_global_meta, 105 | py::arg("key"), 106 | py::arg("val"), 107 | D(sink,set_global_meta,5) 108 | ) 109 | 110 | 111 | 112 | .def("set_annotation_meta",&sink::set_annotation_meta, 113 | py::arg("sample_start"), 114 | py::arg("sample_count"), 115 | py::arg("key"), 116 | py::arg("val"), 117 | D(sink,set_annotation_meta) 118 | ) 119 | 120 | 121 | 122 | .def("set_capture_meta",&sink::set_capture_meta, 123 | py::arg("index"), 124 | py::arg("key"), 125 | py::arg("val"), 126 | D(sink,set_capture_meta) 127 | ) 128 | 129 | 130 | 131 | .def("open",&sink::open, 132 | py::arg("filename"), 133 | D(sink,open) 134 | ) 135 | 136 | 137 | 138 | .def("close",&sink::close, 139 | D(sink,close) 140 | ) 141 | 142 | ; 143 | 144 | 145 | 146 | 147 | } 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /python/bindings/nmea_parser_python.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Free Software Foundation, Inc. 3 | * 4 | * This file is part of GNU Radio 5 | * 6 | * SPDX-License-Identifier: GPL-3.0-or-later 7 | * 8 | */ 9 | 10 | /***********************************************************************************/ 11 | /* This file is automatically generated using bindtool and can be manually edited */ 12 | /* The following lines can be configured to regenerate this file during cmake */ 13 | /* If manual edits are made, the following tags should be modified accordingly. */ 14 | /* BINDTOOL_GEN_AUTOMATIC(0) */ 15 | /* BINDTOOL_USE_PYGCCXML(0) */ 16 | /* BINDTOOL_HEADER_FILE(nmea_parser.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(2e6cd04ac2be28662dddf7cd03b48fe4) */ 18 | /***********************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace py = pybind11; 25 | 26 | #include 27 | 28 | void bind_nmea_parser(py::module& m) 29 | { 30 | using nmea_message = ::gr::sigmf::nmea_message; 31 | using gprmc_message = ::gr::sigmf::gprmc_message; 32 | using gpgga_message = ::gr::sigmf::gpgga_message; 33 | 34 | py::class_>(m, "nmea_message") 36 | .def(py::init(), py::arg("arg0")) 37 | .def(py::init<>()) 38 | ; 39 | 40 | py::class_>(m, "gprmc_message") 42 | .def(py::init(), 43 | py::arg("timestamp"), 44 | py::arg("date"), 45 | py::arg("time"), 46 | py::arg("valid"), 47 | py::arg("lat"), 48 | py::arg("lon"), 49 | py::arg("speed_knots"), 50 | py::arg("track_angle"), 51 | py::arg("magnetic_variation") 52 | ) 53 | .def(py::init(), py::arg("arg0")) 54 | .def_static("parse", &gprmc_message::parse, py::arg("raw")) 55 | .def_readwrite("timestamp", &gprmc_message::timestamp) 56 | .def_readwrite("date", &gprmc_message::date) 57 | .def_readwrite("time", &gprmc_message::time) 58 | .def_readwrite("valid", &gprmc_message::valid) 59 | .def_readwrite("lat", &gprmc_message::lat) 60 | .def_readwrite("lon", &gprmc_message::lon) 61 | .def_readwrite("speed_knots", &gprmc_message::speed_knots) 62 | .def_readwrite("track_angle", &gprmc_message::track_angle) 63 | .def_readwrite("magnetic_variation", &gprmc_message::magnetic_variation) 64 | ; 65 | 66 | py::class_>(m, "gpgga_message") 68 | 69 | .def(py::init(), 70 | py::arg("time"), 71 | py::arg("lat"), 72 | py::arg("lon"), 73 | py::arg("fix_quality"), 74 | py::arg("num_sats"), 75 | py::arg("hdop"), 76 | py::arg("altitude_msl"), 77 | py::arg("geoid_hae") 78 | ) 79 | .def(py::init(), py::arg("arg0")) 80 | .def_static("parse", &gpgga_message::parse, py::arg("raw")) 81 | .def_readwrite("time", &gpgga_message::time) 82 | .def_readwrite("lat", &gpgga_message::lat) 83 | .def_readwrite("lon", &gpgga_message::lon) 84 | .def_readwrite("fix_quality", &gpgga_message::fix_quality) 85 | .def_readwrite("num_sats", &gpgga_message::num_sats) 86 | .def_readwrite("hdop", &gpgga_message::hdop) 87 | .def_readwrite("altitude_msl", &gpgga_message::altitude_msl) 88 | .def_readwrite("geoid_hae", &gpgga_message::geoid_hae) 89 | ; 90 | 91 | m.def("nmea_split", &::gr::sigmf::nmea_split, py::arg("s")); 92 | m.def("nmea_extract", &::gr::sigmf::nmea_extract, py::arg("raw")); 93 | m.def("nmea_parse_degrees", &::gr::sigmf::nmea_parse_degrees, 94 | py::arg("value"), 95 | py::arg("dir") 96 | ); 97 | 98 | m.def("nmea_parse_magnetic_variation", &::gr::sigmf::nmea_parse_magnetic_variation, 99 | py::arg("value"), 100 | py::arg("dir") 101 | ); 102 | 103 | m.def("nmea_parse_datetime", &::gr::sigmf::nmea_parse_datetime, 104 | py::arg("date"), 105 | py::arg("time") 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /cmake/Modules/LibFindMacros.cmake: -------------------------------------------------------------------------------- 1 | # Version 1.0 (2013-04-12) 2 | # Public Domain, originally written by Lasse Kärkkäinen 3 | # Published at http://www.cmake.org/Wiki/CMake:How_To_Find_Libraries 4 | 5 | # If you improve the script, please modify the forementioned wiki page because 6 | # I no longer maintain my scripts (hosted as static files at zi.fi). Feel free 7 | # to remove this entire header if you use real version control instead. 8 | 9 | # Changelog: 10 | # 2013-04-12 Added version number (1.0) and this header, no other changes 11 | # 2009-10-08 Originally published 12 | 13 | 14 | # Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments 15 | # used for the current package. For this to work, the first parameter must be the 16 | # prefix of the current package, then the prefix of the new package etc, which are 17 | # passed to find_package. 18 | macro (libfind_package PREFIX) 19 | set (LIBFIND_PACKAGE_ARGS ${ARGN}) 20 | if (${PREFIX}_FIND_QUIETLY) 21 | set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET) 22 | endif (${PREFIX}_FIND_QUIETLY) 23 | if (${PREFIX}_FIND_REQUIRED) 24 | set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED) 25 | endif (${PREFIX}_FIND_REQUIRED) 26 | find_package(${LIBFIND_PACKAGE_ARGS}) 27 | endmacro (libfind_package) 28 | 29 | # CMake developers made the UsePkgConfig system deprecated in the same release (2.6) 30 | # where they added pkg_check_modules. Consequently I need to support both in my scripts 31 | # to avoid those deprecated warnings. Here's a helper that does just that. 32 | # Works identically to pkg_check_modules, except that no checks are needed prior to use. 33 | macro (libfind_pkg_check_modules PREFIX PKGNAME) 34 | if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) 35 | include(UsePkgConfig) 36 | pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS) 37 | else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) 38 | find_package(PkgConfig) 39 | if (PKG_CONFIG_FOUND) 40 | pkg_check_modules(${PREFIX} ${PKGNAME}) 41 | endif (PKG_CONFIG_FOUND) 42 | endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) 43 | endmacro (libfind_pkg_check_modules) 44 | 45 | # Do the final processing once the paths have been detected. 46 | # If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain 47 | # all the variables, each of which contain one include directory. 48 | # Ditto for ${PREFIX}_PROCESS_LIBS and library files. 49 | # Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. 50 | # Also handles errors in case library detection was required, etc. 51 | macro (libfind_process PREFIX) 52 | # Skip processing if already processed during this run 53 | if (NOT ${PREFIX}_FOUND) 54 | # Start with the assumption that the library was found 55 | set (${PREFIX}_FOUND TRUE) 56 | 57 | # Process all includes and set _FOUND to false if any are missing 58 | foreach (i ${${PREFIX}_PROCESS_INCLUDES}) 59 | if (${i}) 60 | set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}}) 61 | mark_as_advanced(${i}) 62 | else (${i}) 63 | set (${PREFIX}_FOUND FALSE) 64 | endif (${i}) 65 | endforeach (i) 66 | 67 | # Process all libraries and set _FOUND to false if any are missing 68 | foreach (i ${${PREFIX}_PROCESS_LIBS}) 69 | if (${i}) 70 | set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}}) 71 | mark_as_advanced(${i}) 72 | else (${i}) 73 | set (${PREFIX}_FOUND FALSE) 74 | endif (${i}) 75 | endforeach (i) 76 | 77 | # Print message and/or exit on fatal error 78 | if (${PREFIX}_FOUND) 79 | if (NOT ${PREFIX}_FIND_QUIETLY) 80 | message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") 81 | endif (NOT ${PREFIX}_FIND_QUIETLY) 82 | else (${PREFIX}_FOUND) 83 | if (${PREFIX}_FIND_REQUIRED) 84 | foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS}) 85 | message("${i}=${${i}}") 86 | endforeach (i) 87 | message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.") 88 | endif (${PREFIX}_FIND_REQUIRED) 89 | endif (${PREFIX}_FOUND) 90 | endif (NOT ${PREFIX}_FOUND) 91 | endmacro (libfind_process) 92 | 93 | macro(libfind_library PREFIX basename) 94 | set(TMP "") 95 | if(MSVC80) 96 | set(TMP -vc80) 97 | endif(MSVC80) 98 | if(MSVC90) 99 | set(TMP -vc90) 100 | endif(MSVC90) 101 | set(${PREFIX}_LIBNAMES ${basename}${TMP}) 102 | if(${ARGC} GREATER 2) 103 | set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2}) 104 | string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES}) 105 | set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP}) 106 | endif(${ARGC} GREATER 2) 107 | find_library(${PREFIX}_LIBRARY 108 | NAMES ${${PREFIX}_LIBNAMES} 109 | PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS} 110 | ) 111 | endmacro(libfind_library) 112 | 113 | -------------------------------------------------------------------------------- /include/sigmf/sink.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2017 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifndef INCLUDED_SIGMF_SINK_H 22 | #define INCLUDED_SIGMF_SINK_H 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace gr { 30 | namespace sigmf { 31 | 32 | /*! 33 | * \brief Sink block to create SigMF recordings. 34 | * \ingroup sigmf 35 | * 36 | */ 37 | class SIGMF_API sink : virtual public gr::sync_block { 38 | public: 39 | typedef std::shared_ptr sptr; 40 | 41 | /*! 42 | * \brief Return a shared_ptr to a new instance of sigmf::sink. 43 | * 44 | * To avoid accidental use of raw pointers, sigmf::sink's 45 | * constructor is in a private implementation 46 | * class. sigmf::sink::make is the public interface for 47 | * creating new instances. 48 | */ 49 | static sptr make(std::string type, 50 | std::string filename, 51 | sigmf_time_mode time_mode = sigmf_time_mode::absolute, 52 | bool append = false); 53 | 54 | /*! 55 | * \brief Get the path the the current .sigmf-data file as a string 56 | * 57 | * If there is no currently open file, returns empty string 58 | * @return the path 59 | */ 60 | virtual std::string get_data_path() = 0; 61 | 62 | /*! 63 | * \brief Get the path the the current .sigmf-meta file as a string 64 | * 65 | * If there is no currently open file, returns empty string 66 | * @return the path 67 | */ 68 | virtual std::string get_meta_path() = 0; 69 | 70 | /*! 71 | * \brief Set a value in the global metadata for this data set. 72 | * @param key the key for the value 73 | * @param val the value to store 74 | */ 75 | virtual void set_global_meta(const std::string &key, pmt::pmt_t val) = 0; 76 | 77 | /*! 78 | * \brief Set a value in the global metadata for this data set. 79 | * @param key the key for the value 80 | * @param val the value to store 81 | */ 82 | virtual void set_global_meta(const std::string &key, double val) = 0; 83 | 84 | /*! 85 | * \brief Set a value in the global metadata for this data set. 86 | * @param key the key for the value 87 | * @param val the value to store 88 | */ 89 | virtual void set_global_meta(const std::string &key, int64_t val) = 0; 90 | 91 | /*! 92 | * \brief Set a value in the global metadata for this data set. 93 | * @param key the key for the value 94 | * @param val the value to store 95 | */ 96 | virtual void set_global_meta(const std::string &key, uint64_t val) = 0; 97 | 98 | /*! 99 | * \brief Set a value in the global metadata for this data set. 100 | * @param key the key for the value 101 | * @param val the value to store 102 | */ 103 | virtual void set_global_meta(const std::string &key, const std::string &val) = 0; 104 | 105 | /*! 106 | * \brief Set a value in the global metadata for this data set. 107 | * @param key the key for the value 108 | * @param val the value to store 109 | */ 110 | virtual void set_global_meta(const std::string &key, bool val) = 0; 111 | 112 | /*! 113 | * \brief Set a value in the annotations metadata for this data set. 114 | * @param sample_start sample start for the annotation 115 | * @param sample_count sample count for the annotation 116 | * @param key the key for the value 117 | * @param val the value to store 118 | * 119 | * If there is an existing annotation with the same sample start and count, 120 | * then the new value will be added to it. Otherwise, a new annotation will 121 | * be created. 122 | */ 123 | virtual void set_annotation_meta(uint64_t sample_start, 124 | uint64_t sample_count, 125 | std::string key, 126 | pmt::pmt_t val) = 0; 127 | 128 | /*! 129 | * \brief Set a value in the annotations metadata for this data set. 130 | * @param index the index of the desired capture segment 131 | * @param key the key for the value 132 | * @param val the value to store 133 | * 134 | * If no capture segment with the given index exists, then the new value is 135 | * dropped and an error is logged. 136 | */ 137 | virtual void set_capture_meta(uint64_t index, std::string key, pmt::pmt_t val) = 0; 138 | 139 | /*! 140 | * \brief Open a new file to start recording to 141 | * @param filename the file to write to 142 | * 143 | * The sigmf sink will coerce the filename to the names of the two files in a 144 | * SigMF data set 145 | */ 146 | virtual void open(const char *filename) = 0; 147 | 148 | /*! 149 | * \brief Stop writing to the current file 150 | */ 151 | virtual void close() = 0; 152 | }; 153 | 154 | } // namespace sigmf 155 | } // namespace gr 156 | 157 | #endif /* INCLUDED_SIGMF_SINK_H */ 158 | -------------------------------------------------------------------------------- /python/test_blocks.py: -------------------------------------------------------------------------------- 1 | from gnuradio import gr 2 | from threading import Timer 3 | import pmt 4 | import numpy 5 | 6 | 7 | class simple_tag_injector(gr.sync_block): 8 | def __init__(self): 9 | gr.sync_block.__init__( 10 | self, 11 | name="simple_tag_injector", 12 | in_sig=[numpy.complex64], 13 | out_sig=[numpy.complex64], 14 | ) 15 | self.inject_tag = None 16 | self.injected_offset = None 17 | 18 | def work(self, input_items, output_items): 19 | output_items[0][:] = input_items[0] 20 | if self.inject_tag: 21 | offset = self.nitems_read(0) 22 | for key, val in self.inject_tag.items(): 23 | self.add_item_tag( 24 | 0, offset, pmt.to_pmt(key), pmt.to_pmt(val)) 25 | self.injected_offset = offset 26 | self.inject_tag = None 27 | return len(output_items[0]) 28 | 29 | 30 | class advanced_tag_injector(gr.sync_block): 31 | '''Like simple_tag_injector, but can specify exact 32 | indices to inject the tags at''' 33 | def __init__(self, tags_to_inject): 34 | gr.sync_block.__init__( 35 | self, 36 | name="advanced_tag_injector", 37 | in_sig=[numpy.complex64], 38 | out_sig=[numpy.complex64], 39 | ) 40 | self.tags_to_inject = tags_to_inject 41 | 42 | def work(self, input_items, output_items): 43 | output_items[0][:] = input_items[0] 44 | start_of_range = self.nitems_read(0) 45 | end_of_range = start_of_range + len(input_items[0]) 46 | for index, tags in self.tags_to_inject: 47 | if index >= start_of_range and index < end_of_range: 48 | print(tags) 49 | for key, val in tags.items(): 50 | self.add_item_tag( 51 | 0, index, pmt.to_pmt(key), pmt.to_pmt(val)) 52 | print(start_of_range) 53 | return len(output_items[0]) 54 | 55 | 56 | class sample_counter(gr.sync_block): 57 | def __init__(self): 58 | gr.sync_block.__init__( 59 | self, 60 | name="sample_counter", 61 | in_sig=[numpy.complex64], 62 | out_sig=[numpy.complex64], 63 | ) 64 | self.count = 0 65 | 66 | def work(self, input_items, output_items): 67 | output_items[0][:] = input_items[0] 68 | self.count += len(output_items[0]) 69 | return len(output_items[0]) 70 | 71 | 72 | class msg_sender(gr.sync_block): 73 | def __init__(self): 74 | gr.sync_block.__init__( 75 | self, 76 | name="msg_sender", 77 | in_sig=None, 78 | out_sig=None 79 | ) 80 | self.message_port_register_out(pmt.intern("out")) 81 | 82 | def send_msg(self, msg_to_send): 83 | self.message_port_pub(pmt.intern("out"), pmt.to_pmt(msg_to_send)) 84 | 85 | 86 | class sample_producer(gr.sync_block): 87 | def __init__(self, limit, limit_ev, continue_ev): 88 | gr.sync_block.__init__( 89 | self, 90 | name="sample_producer", 91 | in_sig=None, 92 | out_sig=[numpy.complex64], 93 | ) 94 | self.limit = limit 95 | self.limit_ev = limit_ev 96 | self.continue_ev = continue_ev 97 | self.fired = False 98 | 99 | def work(self, input_items, output_items): 100 | if self.limit <= 0: 101 | if not self.fired: 102 | self.limit_ev.set() 103 | self.continue_ev.wait() 104 | self.fired = True 105 | samples_to_produce = len(output_items[0]) 106 | for i in range(samples_to_produce): 107 | output_items[0][i] = 1 + 1j 108 | return samples_to_produce 109 | 110 | if self.limit > 0: 111 | samples_to_produce = int(min(self.limit, len(output_items[0]))) 112 | for i in range(samples_to_produce): 113 | output_items[0][i] = 1 + 1j 114 | self.limit -= samples_to_produce 115 | return samples_to_produce 116 | 117 | 118 | class message_generator(gr.basic_block): 119 | def __init__(self, message): 120 | gr.basic_block.__init__(self, name="message_generator", 121 | in_sig=None, out_sig=None) 122 | self.message = message 123 | self.message_port_register_out(pmt.intern('messages')) 124 | self.timer = None 125 | 126 | def send_msg(self): 127 | msg = pmt.to_pmt(self.message) 128 | self.message_port_pub(pmt.intern('messages'), msg) 129 | 130 | def stop(self): 131 | if self.timer: 132 | self.timer.cancel() 133 | self.timer.join() 134 | return True 135 | 136 | def start(self): 137 | self.timer = Timer(1, self.send_msg) 138 | self.timer.start() 139 | return True 140 | 141 | 142 | # Block that just collects all tags that pass thorough it 143 | class tag_collector(gr.sync_block): 144 | def __init__(self): 145 | gr.sync_block.__init__( 146 | self, 147 | name="tag_collector", 148 | in_sig=[numpy.complex64], 149 | out_sig=[numpy.complex64], 150 | ) 151 | self.tags = [] 152 | 153 | def work(self, input_items, output_items): 154 | nread = self.nitems_read(0) 155 | tags = self.get_tags_in_range(0, nread, nread + len(input_items[0])) 156 | for tag in tags: 157 | self.tags.append({ 158 | "key": pmt.to_python(tag.key), 159 | "offset": tag.offset, 160 | "value": pmt.to_python(tag.value) 161 | }) 162 | output_items[0][:] = input_items[0] 163 | return len(output_items[0]) 164 | 165 | def assertTagExists(self, offset, key, val): 166 | matching = [t for t in self.tags if t["offset"] == 167 | offset and t["key"] == key and t["value"] == val] 168 | assert len(matching) == 1, "got tags: %r" % matching 169 | 170 | def assertTagExistsMsg(self, offset, key, val, msg, unit_test): 171 | unit_test.assertEqual( 172 | len([t for t in self.tags if t["offset"] == 173 | offset and t["key"] == key and t["value"] == val]), 1, msg) 174 | -------------------------------------------------------------------------------- /cmake/Modules/CMakeParseArgumentsCopy.cmake: -------------------------------------------------------------------------------- 1 | # CMAKE_PARSE_ARGUMENTS( args...) 2 | # 3 | # CMAKE_PARSE_ARGUMENTS() is intended to be used in macros or functions for 4 | # parsing the arguments given to that macro or function. 5 | # It processes the arguments and defines a set of variables which hold the 6 | # values of the respective options. 7 | # 8 | # The argument contains all options for the respective macro, 9 | # i.e. keywords which can be used when calling the macro without any value 10 | # following, like e.g. the OPTIONAL keyword of the install() command. 11 | # 12 | # The argument contains all keywords for this macro 13 | # which are followed by one value, like e.g. DESTINATION keyword of the 14 | # install() command. 15 | # 16 | # The argument contains all keywords for this macro 17 | # which can be followed by more than one value, like e.g. the TARGETS or 18 | # FILES keywords of the install() command. 19 | # 20 | # When done, CMAKE_PARSE_ARGUMENTS() will have defined for each of the 21 | # keywords listed in , and 22 | # a variable composed of the given 23 | # followed by "_" and the name of the respective keyword. 24 | # These variables will then hold the respective value from the argument list. 25 | # For the keywords this will be TRUE or FALSE. 26 | # 27 | # All remaining arguments are collected in a variable 28 | # _UNPARSED_ARGUMENTS, this can be checked afterwards to see whether 29 | # your macro was called with unrecognized parameters. 30 | # 31 | # As an example here a my_install() macro, which takes similar arguments as the 32 | # real install() command: 33 | # 34 | # function(MY_INSTALL) 35 | # set(options OPTIONAL FAST) 36 | # set(oneValueArgs DESTINATION RENAME) 37 | # set(multiValueArgs TARGETS CONFIGURATIONS) 38 | # cmake_parse_arguments(MY_INSTALL "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) 39 | # ... 40 | # 41 | # Assume my_install() has been called like this: 42 | # my_install(TARGETS foo bar DESTINATION bin OPTIONAL blub) 43 | # 44 | # After the cmake_parse_arguments() call the macro will have set the following 45 | # variables: 46 | # MY_INSTALL_OPTIONAL = TRUE 47 | # MY_INSTALL_FAST = FALSE (this option was not used when calling my_install() 48 | # MY_INSTALL_DESTINATION = "bin" 49 | # MY_INSTALL_RENAME = "" (was not used) 50 | # MY_INSTALL_TARGETS = "foo;bar" 51 | # MY_INSTALL_CONFIGURATIONS = "" (was not used) 52 | # MY_INSTALL_UNPARSED_ARGUMENTS = "blub" (no value expected after "OPTIONAL" 53 | # 54 | # You can the continue and process these variables. 55 | # 56 | # Keywords terminate lists of values, e.g. if directly after a one_value_keyword 57 | # another recognized keyword follows, this is interpreted as the beginning of 58 | # the new option. 59 | # E.g. my_install(TARGETS foo DESTINATION OPTIONAL) would result in 60 | # MY_INSTALL_DESTINATION set to "OPTIONAL", but MY_INSTALL_DESTINATION would 61 | # be empty and MY_INSTALL_OPTIONAL would be set to TRUE therefore. 62 | 63 | #============================================================================= 64 | # Copyright 2010 Alexander Neundorf 65 | # 66 | # Distributed under the OSI-approved BSD License (the "License"); 67 | # see accompanying file Copyright.txt for details. 68 | # 69 | # This software is distributed WITHOUT ANY WARRANTY; without even the 70 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 71 | # See the License for more information. 72 | #============================================================================= 73 | # (To distribute this file outside of CMake, substitute the full 74 | # License text for the above reference.) 75 | 76 | 77 | if(__CMAKE_PARSE_ARGUMENTS_INCLUDED) 78 | return() 79 | endif() 80 | set(__CMAKE_PARSE_ARGUMENTS_INCLUDED TRUE) 81 | 82 | 83 | function(CMAKE_PARSE_ARGUMENTS prefix _optionNames _singleArgNames _multiArgNames) 84 | # first set all result variables to empty/FALSE 85 | foreach(arg_name ${_singleArgNames} ${_multiArgNames}) 86 | set(${prefix}_${arg_name}) 87 | endforeach(arg_name) 88 | 89 | foreach(option ${_optionNames}) 90 | set(${prefix}_${option} FALSE) 91 | endforeach(option) 92 | 93 | set(${prefix}_UNPARSED_ARGUMENTS) 94 | 95 | set(insideValues FALSE) 96 | set(currentArgName) 97 | 98 | # now iterate over all arguments and fill the result variables 99 | foreach(currentArg ${ARGN}) 100 | list(FIND _optionNames "${currentArg}" optionIndex) # ... then this marks the end of the arguments belonging to this keyword 101 | list(FIND _singleArgNames "${currentArg}" singleArgIndex) # ... then this marks the end of the arguments belonging to this keyword 102 | list(FIND _multiArgNames "${currentArg}" multiArgIndex) # ... then this marks the end of the arguments belonging to this keyword 103 | 104 | if(${optionIndex} EQUAL -1 AND ${singleArgIndex} EQUAL -1 AND ${multiArgIndex} EQUAL -1) 105 | if(insideValues) 106 | if("${insideValues}" STREQUAL "SINGLE") 107 | set(${prefix}_${currentArgName} ${currentArg}) 108 | set(insideValues FALSE) 109 | elseif("${insideValues}" STREQUAL "MULTI") 110 | list(APPEND ${prefix}_${currentArgName} ${currentArg}) 111 | endif() 112 | else(insideValues) 113 | list(APPEND ${prefix}_UNPARSED_ARGUMENTS ${currentArg}) 114 | endif(insideValues) 115 | else() 116 | if(NOT ${optionIndex} EQUAL -1) 117 | set(${prefix}_${currentArg} TRUE) 118 | set(insideValues FALSE) 119 | elseif(NOT ${singleArgIndex} EQUAL -1) 120 | set(currentArgName ${currentArg}) 121 | set(${prefix}_${currentArgName}) 122 | set(insideValues "SINGLE") 123 | elseif(NOT ${multiArgIndex} EQUAL -1) 124 | set(currentArgName ${currentArg}) 125 | set(${prefix}_${currentArgName}) 126 | set(insideValues "MULTI") 127 | endif() 128 | endif() 129 | 130 | endforeach(currentArg) 131 | 132 | # propagate the result variables to the caller: 133 | foreach(arg_name ${_singleArgNames} ${_multiArgNames} ${_optionNames}) 134 | set(${prefix}_${arg_name} ${${prefix}_${arg_name}} PARENT_SCOPE) 135 | endforeach(arg_name) 136 | set(${prefix}_UNPARSED_ARGUMENTS ${${prefix}_UNPARSED_ARGUMENTS} PARENT_SCOPE) 137 | 138 | endfunction(CMAKE_PARSE_ARGUMENTS _options _singleArgs _multiArgs) 139 | -------------------------------------------------------------------------------- /examples/sigmf-sink.grc: -------------------------------------------------------------------------------- 1 | options: 2 | parameters: 3 | author: Cate Miller 4 | category: '[GRC Hier Blocks]' 5 | cmake_opt: '' 6 | comment: '' 7 | copyright: '' 8 | description: Example use of the SigMF Sink block 9 | gen_cmake: 'On' 10 | gen_linking: dynamic 11 | generate_options: qt_gui 12 | hier_block_src_path: '.:' 13 | id: sigmf_sink 14 | max_nouts: '0' 15 | output_language: python 16 | placement: (0,0) 17 | qt_qss_theme: '' 18 | realtime_scheduling: '' 19 | run: 'True' 20 | run_command: '{python} -u {filename}' 21 | run_options: run 22 | sizing_mode: fixed 23 | thread_safe_setters: '' 24 | title: SigMF Sink 25 | window_size: '' 26 | states: 27 | bus_sink: false 28 | bus_source: false 29 | bus_structure: null 30 | coordinate: [8, 8] 31 | rotation: 0 32 | state: enabled 33 | 34 | blocks: 35 | - name: samp_rate 36 | id: variable 37 | parameters: 38 | comment: '' 39 | value: '32000' 40 | states: 41 | bus_sink: false 42 | bus_source: false 43 | bus_structure: null 44 | coordinate: [184, 12] 45 | rotation: 0 46 | state: enabled 47 | - name: analog_sig_source_x_0 48 | id: analog_sig_source_x 49 | parameters: 50 | affinity: '' 51 | alias: '' 52 | amp: '1' 53 | comment: '' 54 | freq: '1000' 55 | maxoutbuf: '0' 56 | minoutbuf: '0' 57 | offset: '0' 58 | phase: '0' 59 | samp_rate: samp_rate 60 | type: complex 61 | waveform: analog.GR_COS_WAVE 62 | states: 63 | bus_sink: false 64 | bus_source: false 65 | bus_structure: null 66 | coordinate: [24, 120] 67 | rotation: 0 68 | state: enabled 69 | - name: blocks_throttle_0 70 | id: blocks_throttle 71 | parameters: 72 | affinity: '' 73 | alias: '' 74 | comment: '' 75 | ignoretag: 'True' 76 | maxoutbuf: '0' 77 | minoutbuf: '0' 78 | samples_per_second: samp_rate 79 | type: complex 80 | vlen: '1' 81 | states: 82 | bus_sink: false 83 | bus_source: false 84 | bus_structure: null 85 | coordinate: [216, 148] 86 | rotation: 0 87 | state: enabled 88 | - name: qtgui_waterfall_sink_x_0 89 | id: qtgui_waterfall_sink_x 90 | parameters: 91 | affinity: '' 92 | alias: '' 93 | alpha1: '1.0' 94 | alpha10: '1.0' 95 | alpha2: '1.0' 96 | alpha3: '1.0' 97 | alpha4: '1.0' 98 | alpha5: '1.0' 99 | alpha6: '1.0' 100 | alpha7: '1.0' 101 | alpha8: '1.0' 102 | alpha9: '1.0' 103 | axislabels: 'True' 104 | bw: samp_rate 105 | color1: '0' 106 | color10: '0' 107 | color2: '0' 108 | color3: '0' 109 | color4: '0' 110 | color5: '0' 111 | color6: '0' 112 | color7: '0' 113 | color8: '0' 114 | color9: '0' 115 | comment: '' 116 | fc: '0' 117 | fftsize: '1024' 118 | freqhalf: 'True' 119 | grid: 'False' 120 | gui_hint: '' 121 | int_max: '10' 122 | int_min: '-140' 123 | label1: '' 124 | label10: '' 125 | label2: '' 126 | label3: '' 127 | label4: '' 128 | label5: '' 129 | label6: '' 130 | label7: '' 131 | label8: '' 132 | label9: '' 133 | legend: 'True' 134 | maxoutbuf: '0' 135 | minoutbuf: '0' 136 | name: '""' 137 | nconnections: '1' 138 | showports: 'True' 139 | type: complex 140 | update_time: '0.10' 141 | wintype: firdes.WIN_BLACKMAN_hARRIS 142 | states: 143 | bus_sink: false 144 | bus_source: false 145 | bus_structure: null 146 | coordinate: [408, 36] 147 | rotation: 0 148 | state: enabled 149 | - name: sigmf_sink_0 150 | id: sigmf_sink 151 | parameters: 152 | affinity: '' 153 | alias: '' 154 | author: Cate Miller 155 | comment: '' 156 | debug: 'False' 157 | description: This is an example output file 158 | field_10_key: '' 159 | field_10_type: string 160 | field_10_value: '' 161 | field_11_key: '' 162 | field_11_type: string 163 | field_11_value: '' 164 | field_12_key: '' 165 | field_12_type: string 166 | field_12_value: '' 167 | field_13_key: '' 168 | field_13_type: string 169 | field_13_value: '' 170 | field_14_key: '' 171 | field_14_type: string 172 | field_14_value: '' 173 | field_15_key: '' 174 | field_15_type: string 175 | field_15_value: '' 176 | field_16_key: '' 177 | field_16_type: string 178 | field_16_value: '' 179 | field_17_key: '' 180 | field_17_type: string 181 | field_17_value: '' 182 | field_18_key: '' 183 | field_18_type: string 184 | field_18_value: '' 185 | field_19_key: '' 186 | field_19_type: string 187 | field_19_value: '' 188 | field_1_key: extra1 189 | field_1_type: bool 190 | field_1_value: 'True' 191 | field_20_key: '' 192 | field_20_type: string 193 | field_20_value: '' 194 | field_21_key: '' 195 | field_21_type: string 196 | field_21_value: '' 197 | field_22_key: '' 198 | field_22_type: string 199 | field_22_value: '' 200 | field_23_key: '' 201 | field_23_type: string 202 | field_23_value: '' 203 | field_24_key: '' 204 | field_24_type: string 205 | field_24_value: '' 206 | field_25_key: '' 207 | field_25_type: string 208 | field_25_value: '' 209 | field_2_key: extra2 210 | field_2_type: string 211 | field_2_value: here is some metadata 212 | field_3_key: '' 213 | field_3_type: string 214 | field_3_value: '' 215 | field_4_key: '' 216 | field_4_type: string 217 | field_4_value: '' 218 | field_5_key: '' 219 | field_5_type: string 220 | field_5_value: '' 221 | field_6_key: '' 222 | field_6_type: string 223 | field_6_value: '' 224 | field_7_key: '' 225 | field_7_type: string 226 | field_7_value: '' 227 | field_8_key: '' 228 | field_8_type: string 229 | field_8_value: '' 230 | field_9_key: '' 231 | field_9_type: string 232 | field_9_value: '' 233 | filename: out 234 | hardware: None 235 | license: CC-BY-SA 236 | num_extra_fields: '0' 237 | samp_rate: samp_rate 238 | type: fc32 239 | states: 240 | bus_sink: false 241 | bus_source: false 242 | bus_structure: null 243 | coordinate: [408, 136] 244 | rotation: 0 245 | state: enabled 246 | 247 | connections: 248 | - [analog_sig_source_x_0, '0', blocks_throttle_0, '0'] 249 | - [blocks_throttle_0, '0', qtgui_waterfall_sink_x_0, '0'] 250 | - [blocks_throttle_0, '0', sigmf_sink_0, '0'] 251 | 252 | metadata: 253 | file_format: 1 254 | -------------------------------------------------------------------------------- /python/qa_crop.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import json 3 | import struct 4 | import os 5 | import shutil 6 | 7 | from subprocess import PIPE, Popen 8 | from uuid import uuid4 9 | 10 | import numpy as np 11 | from gnuradio import gr_unittest, blocks, gr 12 | 13 | try: 14 | import gr_sigmf as sigmf 15 | except ImportError: 16 | import sys 17 | dirname, filename = os.path.split(os.path.abspath(__file__)) 18 | sys.path.append(os.path.join(dirname, "bindings")) 19 | import gr_sigmf as sigmf 20 | 21 | from test_utils import sig_source_c 22 | 23 | 24 | class qa_crop(gr_unittest.TestCase): 25 | 26 | def setUp(self): 27 | 28 | # Create a temporary directory 29 | self.test_dir = tempfile.mkdtemp() 30 | 31 | def tearDown(self): 32 | 33 | # Remove the directory after the test 34 | shutil.rmtree(self.test_dir) 35 | 36 | def make_file(self, filename, N=1000, type="cf32_le"): 37 | if (not filename.startswith("/")): 38 | filename = os.path.join(self.test_dir, filename) 39 | samp_rate = 200000 40 | 41 | data = sig_source_c(samp_rate, 1000, 1, N) 42 | src = blocks.vector_source_c(data) 43 | 44 | file_sink = sigmf.sink(type, 45 | filename) 46 | data_path = file_sink.get_data_path() 47 | meta_path = file_sink.get_meta_path() 48 | 49 | tb = gr.top_block() 50 | tb.connect(src, file_sink) 51 | tb.run() 52 | with open(meta_path, "r") as f: 53 | meta_json = json.load(f) 54 | return data, meta_json, data_path, meta_path 55 | 56 | def run_crop(self, args, filename): 57 | cmd = ["sigmf-crop"] 58 | split_args = args.split() 59 | cmd.extend(split_args) 60 | out_file = os.path.join(self.test_dir, str(uuid4()) + ".sigmf-data") 61 | out_meta = os.path.splitext(out_file)[0] + ".sigmf-meta" 62 | cmd.append("-o") 63 | cmd.append(out_file) 64 | cmd.append(filename) 65 | p = Popen(cmd, stdout=PIPE, stderr=PIPE) 66 | std_out, std_err = p.communicate() 67 | if (p.returncode == 0): 68 | with open(out_file, "rb") as f: 69 | raw_data = f.read() 70 | num_samps = len(raw_data) // 8 71 | s = struct.Struct("ff" * num_samps) 72 | out_tuple = s.unpack(raw_data) 73 | out_data = [complex(x, y) 74 | for x, y in zip(out_tuple[0::2], out_tuple[1::2])] 75 | t = 0, std_out, std_err, out_file, out_data, out_meta 76 | else: 77 | t = p.returncode, std_out, std_err, out_file, [], out_meta 78 | return t 79 | 80 | def test_crop1(self): 81 | data, meta_json, filename, meta_file = self.make_file("normal") 82 | rc, out, err, out_file, out_data, out_meta = self.run_crop( 83 | "-s 0 -e 100", filename) 84 | self.assertEqual(rc, 0, "Return code not equal to 0") 85 | np.testing.assert_almost_equal(data[:100], out_data) 86 | 87 | def test_crop2(self): 88 | data, meta_json, filename, meta_file = self.make_file("normal") 89 | rc, out, err, out_file, out_data, out_meta = self.run_crop( 90 | "-s 0 -l 100", filename) 91 | self.assertEqual(rc, 0, "Return code not equal to 0") 92 | np.testing.assert_almost_equal(data[:100], out_data) 93 | 94 | def test_crop3(self): 95 | data, meta_json, filename, meta_file = self.make_file("normal") 96 | rc, out, err, out_file, out_data, out_meta = self.run_crop( 97 | "-e 100 -l 100", filename) 98 | self.assertEqual(rc, 0, "Return code not equal to 0") 99 | np.testing.assert_almost_equal(data[:100], out_data) 100 | 101 | def test_crop4(self): 102 | data, meta_json, filename, meta_file = self.make_file("normal") 103 | rc, out, err, out_file, out_data, out_meta = self.run_crop( 104 | "-s 100 -e 200", filename) 105 | self.assertEqual(rc, 0, "Return code not equal to 0") 106 | np.testing.assert_almost_equal(data[100:200], out_data) 107 | 108 | def test_crop_length_warning(self): 109 | data, meta_json, filename, meta_file = self.make_file("normal") 110 | rc, out, err, out_file, out_data, out_meta = self.run_crop( 111 | "-s 100 -l 200000000", filename) 112 | self.assertEqual(rc, 0, "Return code not equal to 0") 113 | self.assertIn( 114 | b"Warning: specified limits go beyond the extent of the file", 115 | out, "No warning for bad length") 116 | np.testing.assert_almost_equal(data[100:], out_data) 117 | 118 | def test_crop_reversed_stop_start(self): 119 | data, meta_json, filename, meta_file = self.make_file("normal") 120 | rc, out, err, out_file, out_data, out_meta = self.run_crop( 121 | "-s 10 -e 2", filename) 122 | self.assertNotEqual(rc, 0, "Return code equal to 0") 123 | self.assertFalse(os.path.exists(out_file), 124 | "Error, but output file exists") 125 | self.assertIn(b"End is before start!", err, "Missing error message") 126 | 127 | def test_crop_global_meta(self): 128 | data, meta_json, filename, meta_file = self.make_file("normal") 129 | with open(meta_file, "r") as f: 130 | meta = json.load(f) 131 | meta["global"]["test:foo"] = "bar" 132 | with open(meta_file, "w") as f: 133 | json.dump(meta, f) 134 | rc, out, err, out_file, out_data, out_meta = self.run_crop( 135 | "-s 0 -e 100", filename) 136 | self.assertEqual(rc, 0, "Return code not equal to 0") 137 | np.testing.assert_almost_equal(data[:100], out_data) 138 | with open(out_meta, "r") as f: 139 | meta = json.load(f) 140 | self.assertEqual( 141 | meta["global"]["test:foo"], "bar", 142 | "Additional global data not copied to output file") 143 | 144 | def test_crop_annotation(self): 145 | data, meta_json, filename, meta_file = self.make_file("normal") 146 | # Add an annotation 147 | with open(meta_file, "r") as f: 148 | meta = json.load(f) 149 | meta["annotations"].append({ 150 | "core:sample_start": 50, 151 | "core:sample_count": 10, 152 | "core:comment": "Test" 153 | }) 154 | with open(meta_file, "w") as f: 155 | json.dump(meta, f) 156 | rc, out, err, out_file, out_data, out_meta = self.run_crop( 157 | "-s 25 -l 100", filename) 158 | print(out) 159 | print(err) 160 | self.assertEqual(rc, 0, "Return code not equal to 0") 161 | np.testing.assert_almost_equal(data[25:125], out_data) 162 | with open(out_meta, "r") as f: 163 | meta = json.load(f) 164 | self.assertEqual(meta["annotations"][0]["core:sample_start"], 165 | 25, "Annotation has wrong sample start") 166 | 167 | 168 | if __name__ == '__main__': 169 | gr_unittest.run(qa_crop) 170 | -------------------------------------------------------------------------------- /docs/doxygen/doxyxml/base.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2010 Free Software Foundation, Inc. 3 | # 4 | # This file was generated by gr_modtool, a tool from the GNU Radio framework 5 | # This file is a part of gr-sigmf 6 | # 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | # 9 | # 10 | """ 11 | A base class is created. 12 | 13 | Classes based upon this are used to make more user-friendly interfaces 14 | to the doxygen xml docs than the generated classes provide. 15 | """ 16 | 17 | import os 18 | import pdb 19 | 20 | from xml.parsers.expat import ExpatError 21 | 22 | from .generated import compound 23 | 24 | 25 | class Base(object): 26 | 27 | class Duplicate(Exception): 28 | pass 29 | 30 | class NoSuchMember(Exception): 31 | pass 32 | 33 | class ParsingError(Exception): 34 | pass 35 | 36 | def __init__(self, parse_data, top=None): 37 | self._parsed = False 38 | self._error = False 39 | self._parse_data = parse_data 40 | self._members = [] 41 | self._dict_members = {} 42 | self._in_category = {} 43 | self._data = {} 44 | if top is not None: 45 | self._xml_path = top._xml_path 46 | # Set up holder of references 47 | else: 48 | top = self 49 | self._refs = {} 50 | self._xml_path = parse_data 51 | self.top = top 52 | 53 | @classmethod 54 | def from_refid(cls, refid, top=None): 55 | """ Instantiate class from a refid rather than parsing object. """ 56 | # First check to see if its already been instantiated. 57 | if top is not None and refid in top._refs: 58 | return top._refs[refid] 59 | # Otherwise create a new instance and set refid. 60 | inst = cls(None, top=top) 61 | inst.refid = refid 62 | inst.add_ref(inst) 63 | return inst 64 | 65 | @classmethod 66 | def from_parse_data(cls, parse_data, top=None): 67 | refid = getattr(parse_data, 'refid', None) 68 | if refid is not None and top is not None and refid in top._refs: 69 | return top._refs[refid] 70 | inst = cls(parse_data, top=top) 71 | if refid is not None: 72 | inst.refid = refid 73 | inst.add_ref(inst) 74 | return inst 75 | 76 | def add_ref(self, obj): 77 | if hasattr(obj, 'refid'): 78 | self.top._refs[obj.refid] = obj 79 | 80 | mem_classes = [] 81 | 82 | def get_cls(self, mem): 83 | for cls in self.mem_classes: 84 | if cls.can_parse(mem): 85 | return cls 86 | raise Exception(("Did not find a class for object '%s'." \ 87 | % (mem.get_name()))) 88 | 89 | def convert_mem(self, mem): 90 | try: 91 | cls = self.get_cls(mem) 92 | converted = cls.from_parse_data(mem, self.top) 93 | if converted is None: 94 | raise Exception('No class matched this object.') 95 | self.add_ref(converted) 96 | return converted 97 | except Exception as e: 98 | print(e) 99 | 100 | @classmethod 101 | def includes(cls, inst): 102 | return isinstance(inst, cls) 103 | 104 | @classmethod 105 | def can_parse(cls, obj): 106 | return False 107 | 108 | def _parse(self): 109 | self._parsed = True 110 | 111 | def _get_dict_members(self, cat=None): 112 | """ 113 | For given category a dictionary is returned mapping member names to 114 | members of that category. For names that are duplicated the name is 115 | mapped to None. 116 | """ 117 | self.confirm_no_error() 118 | if cat not in self._dict_members: 119 | new_dict = {} 120 | for mem in self.in_category(cat): 121 | if mem.name() not in new_dict: 122 | new_dict[mem.name()] = mem 123 | else: 124 | new_dict[mem.name()] = self.Duplicate 125 | self._dict_members[cat] = new_dict 126 | return self._dict_members[cat] 127 | 128 | def in_category(self, cat): 129 | self.confirm_no_error() 130 | if cat is None: 131 | return self._members 132 | if cat not in self._in_category: 133 | self._in_category[cat] = [mem for mem in self._members 134 | if cat.includes(mem)] 135 | return self._in_category[cat] 136 | 137 | def get_member(self, name, cat=None): 138 | self.confirm_no_error() 139 | # Check if it's in a namespace or class. 140 | bits = name.split('::') 141 | first = bits[0] 142 | rest = '::'.join(bits[1:]) 143 | member = self._get_dict_members(cat).get(first, self.NoSuchMember) 144 | # Raise any errors that are returned. 145 | if member in set([self.NoSuchMember, self.Duplicate]): 146 | raise member() 147 | if rest: 148 | return member.get_member(rest, cat=cat) 149 | return member 150 | 151 | def has_member(self, name, cat=None): 152 | try: 153 | mem = self.get_member(name, cat=cat) 154 | return True 155 | except self.NoSuchMember: 156 | return False 157 | 158 | def data(self): 159 | self.confirm_no_error() 160 | return self._data 161 | 162 | def members(self): 163 | self.confirm_no_error() 164 | return self._members 165 | 166 | def process_memberdefs(self): 167 | mdtss = [] 168 | for sec in self._retrieved_data.compounddef.sectiondef: 169 | mdtss += sec.memberdef 170 | # At the moment we lose all information associated with sections. 171 | # Sometimes a memberdef is in several sectiondef. 172 | # We make sure we don't get duplicates here. 173 | uniques = set([]) 174 | for mem in mdtss: 175 | converted = self.convert_mem(mem) 176 | pair = (mem.name, mem.__class__) 177 | if pair not in uniques: 178 | uniques.add(pair) 179 | self._members.append(converted) 180 | 181 | def retrieve_data(self): 182 | filename = os.path.join(self._xml_path, self.refid + '.xml') 183 | try: 184 | self._retrieved_data = compound.parse(filename) 185 | except ExpatError: 186 | print('Error in xml in file %s' % filename) 187 | self._error = True 188 | self._retrieved_data = None 189 | 190 | def check_parsed(self): 191 | if not self._parsed: 192 | self._parse() 193 | 194 | def confirm_no_error(self): 195 | self.check_parsed() 196 | if self._error: 197 | raise self.ParsingError() 198 | 199 | def error(self): 200 | self.check_parsed() 201 | return self._error 202 | 203 | def name(self): 204 | # first see if we can do it without processing. 205 | if self._parse_data is not None: 206 | return self._parse_data.name 207 | self.check_parsed() 208 | return self._retrieved_data.compounddef.name 209 | -------------------------------------------------------------------------------- /apps/sigmf_play.cc: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2017 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "app_utils.h" 30 | 31 | namespace po = boost::program_options; 32 | namespace fs = boost::filesystem; 33 | 34 | gr::top_block_sptr running_tb; 35 | void 36 | sig_int_handler(int /* unused */) 37 | { 38 | if(running_tb) { 39 | running_tb->stop(); 40 | } 41 | } 42 | 43 | int 44 | main(int argc, char *argv[]) 45 | { 46 | // USRP arguments 47 | std::string wire_format_str; 48 | std::string device_args; 49 | std::string antenna; 50 | std::string subdev_spec; 51 | double gain; 52 | double normalized_gain; 53 | double bandwidth; 54 | double sample_rate; 55 | double center_freq; 56 | 57 | // SigMF arguments 58 | std::string input_filename; 59 | 60 | // behavioral arguments 61 | double delay; 62 | bool repeat; 63 | 64 | // TODO: These args are from tx_samples_from_file, do we need any of them? 65 | // ("spb", po::value(&spb)->default_value(10000), "samples per buffer") 66 | // ("rate", po::value(&rate), "rate of outgoing samples") 67 | // ("lo_off", po::value(&lo_off), "Offset for frontend LO in Hz 68 | // (optional)") 69 | // ("ref", po::value(&ref)->default_value("internal"), "reference 70 | // source (internal, external, mimo)") 71 | // ("int-n", "tune USRP with integer-n tuning") 72 | 73 | po::options_description main_options("Allowed options"); 74 | // clang-format doesn't handle boost-program-options well at all 75 | // clang-format off 76 | main_options.add_options() 77 | ("help,h", "show help message") 78 | ("args,a", po::value(&device_args)->default_value(""), "Argument string for usrp") 79 | ("wire-format", po::value(&wire_format_str)->default_value(""), "Format of otw data") 80 | ("sample-rate,s", po::value(&sample_rate), "Sample rate in samples/second, only used if not provided in file") 81 | ("freq,f", po::value(¢er_freq), "Center frequency in hertz, only used if not provided in file") 82 | ("int-n", "Tune USRP LO in integer-N PLL mode") 83 | ("gain,g", po::value(&gain), "Gain in db") 84 | ("normalized-gain", po::value(&normalized_gain), "Normalized gain") 85 | ("antenna", po::value(&antenna),"Antenna for usrp") 86 | ("bandwidth", po::value(&bandwidth), "Bandwidth for usrp") 87 | ("subdev-spec", po::value(&subdev_spec), "Subdev spec for usrp") 88 | ("delay", po::value(&delay)->default_value(0.0), "Specify a delay between repeated transmission of file") 89 | ("repeat", po::bool_switch(&repeat)->default_value(false), "Repeatedly transmit file") 90 | ("input-file", po::value(&input_filename)->default_value(""), "File to read from"); 91 | // clang-format on 92 | po::positional_options_description positional_options; 93 | positional_options.add("input-file", 1); 94 | 95 | po::variables_map vm; 96 | po::store(po::command_line_parser(argc, argv) 97 | .options(main_options) 98 | .positional(positional_options) 99 | .run(), 100 | vm); 101 | po::notify(vm); 102 | 103 | if(vm.count("help")) { 104 | std::cout << "Play back a SigMF recording via a UHD device." << std::endl << std::endl; 105 | std::cout << boost::format("Usage: %s [options] ") % argv[0] << std::endl << std::endl; 106 | std::cout << main_options << std::endl; 107 | return ~0; 108 | } 109 | 110 | if(vm.count("gain") && vm.count("normalized-gain")) { 111 | std::cerr << "Can't set gain and normalized gain!\n"; 112 | return -1; 113 | } 114 | if(!vm.count("gain") && !vm.count("normalized-gain")) { 115 | std::cerr << "No gain supplied!\n"; 116 | return -1; 117 | } 118 | 119 | // Make a usrp sink 120 | uhd::device_addr_t device_addr(device_args); 121 | std::string cpu_format_str = "fc32"; 122 | uhd::stream_args_t stream_args(cpu_format_str, wire_format_str); 123 | gr::uhd::usrp_sink::sptr usrp_sink(gr::uhd::usrp_sink::make(device_addr, stream_args)); 124 | 125 | // Make a file source 126 | std::string sigmf_format = uhd_format_to_sigmf_format(cpu_format_str); 127 | gr::sigmf::source::sptr file_source( 128 | gr::sigmf::source::make(input_filename, sigmf_format, repeat)); 129 | 130 | // Look up sample rate in the metadata 131 | pmt::pmt_t sample_rate_pmt = file_source->global_meta().get("core:sample_rate"); 132 | if(pmt::is_null(sample_rate_pmt)) { 133 | if(vm.count("sample-rate") == 0) { 134 | std::cerr << "Error: no sample rate found in metadata and none provided on command line.\n"; 135 | return -1; 136 | } 137 | } else { 138 | sample_rate = pmt::to_double(sample_rate_pmt); 139 | } 140 | usrp_sink->set_samp_rate(sample_rate); 141 | 142 | // And the same for center_frequency 143 | pmt::pmt_t frequency_pmt = 144 | file_source->capture_segments().front().get("core:frequency"); 145 | if(pmt::is_null(frequency_pmt)) { 146 | if(vm.count("freq") == 0) { 147 | std::cerr << "Error: no frequency found in metadata and none provided on command line.\n"; 148 | return -1; 149 | } 150 | } else { 151 | center_freq = pmt::to_double(frequency_pmt); 152 | } 153 | 154 | std::cout << boost::format("Setting TX Freq: %f MHz...") % (center_freq/1e6) << std::endl; 155 | uhd::tune_request_t tune_request(center_freq); 156 | if(vm.count("int-n")) { 157 | std::cout << "Configuring PLL in integer-N mode..." << std::endl; 158 | tune_request.args = uhd::device_addr_t("mode_n=integer"); 159 | } 160 | usrp_sink->set_center_freq(center_freq); 161 | std::cout << boost::format("Actual TX Freq: %f MHz...") % (usrp_sink->get_center_freq()/1e6) << std::endl << std::endl; 162 | 163 | // This gets set from the args 164 | if(vm.count("gain")) { 165 | usrp_sink->set_gain(gain); 166 | } else if(vm.count("normalized-gain")) { 167 | usrp_sink->set_normalized_gain(normalized_gain); 168 | } 169 | 170 | gr::top_block_sptr tb(gr::make_top_block("sigmf_play")); 171 | tb->connect(file_source, 0, usrp_sink, 0); 172 | 173 | // Handle the interrupt signal 174 | std::signal(SIGINT, &sig_int_handler); 175 | std::cout << "Press Ctrl + C to stop streaming..." << std::endl; 176 | 177 | // run the flow graph 178 | running_tb = tb; 179 | tb->start(); 180 | tb->wait(); 181 | } 182 | -------------------------------------------------------------------------------- /apps/sigmf-hash: -------------------------------------------------------------------------------- 1 | # vim: set ft=python: 2 | 3 | import argparse 4 | import json 5 | import tarfile 6 | import tempfile 7 | import os 8 | import sys 9 | import hashlib 10 | from itertools import groupby 11 | import fnmatch 12 | import shutil 13 | 14 | # TODO: Finish archive support 15 | 16 | BUF_SIZE = 65536 17 | 18 | 19 | def hash_file(file_to_hash, length): 20 | sha512 = hashlib.sha512() 21 | data_to_read = length 22 | while True: 23 | with open(file_to_hash, "rb") as f: 24 | data = f.read(min(data_to_read, BUF_SIZE)) 25 | data_to_read -= len(data) 26 | if not data: 27 | break 28 | sha512.update(data) 29 | return "{0}".format(sha512.hexdigest()) 30 | 31 | 32 | def find(pattern, path): 33 | result = [] 34 | for root, dirs, files in os.walk(path): 35 | for name in files: 36 | if fnmatch.fnmatch(name, pattern): 37 | result.append(os.path.join(root, name)) 38 | return result 39 | 40 | 41 | class Archive(object): 42 | def __init__(self, archive_path): 43 | self.archive_path = archive_path 44 | 45 | def check(self): 46 | # TODO: This assumes a single recording per archive 47 | # Find the recording members in the tar file 48 | with tarfile.open(self.archive_path, "r:") as tar: 49 | data_member = None 50 | meta_member = None 51 | for member in tar.getmembers(): 52 | if member.name.endswith("sigmf-data"): 53 | data_member = member 54 | if member.name.endswith("sigmf-meta"): 55 | meta_member = member 56 | 57 | # Use the undocumented features of the tarfile 58 | if meta_member and data_member: 59 | meta_size = meta_member.size 60 | meta_offset = meta_member.offset_data 61 | data_size = data_member.size 62 | data_offset = data_member.offset_data 63 | with open(self.archive_path, "r") as archive: 64 | archive.seek(meta_offset) 65 | meta_string = archive.read(meta_size) 66 | meta = json.loads(meta_string) 67 | try: 68 | cur_hash = meta["global"]["core:sha512"] 69 | except KeyError: 70 | print("No hash in file %s" % self.archive_path) 71 | return 72 | archive.seek(data_offset) 73 | computed_hash = hash_file(archive, data_size) 74 | if cur_hash == computed_hash: 75 | print("Hash match") 76 | else: 77 | print("Hash doesn't match") 78 | else: 79 | print("couldn't find members") 80 | return 81 | 82 | def update(self): 83 | # TODO: This assumes a single recording per archive 84 | # Make a temp folder 85 | temp_dir = tempfile.mkdtemp() 86 | # Untar the archive 87 | archive_tar = tarfile.open(self.archive_path) 88 | archive_tar.extractall(path=temp_dir) 89 | meta_file = find("*.sigmf-meta", temp_dir) 90 | data_file = find("*.sigmf-data", temp_dir) 91 | # Then reuse the filepair stuff from below 92 | file_pair = FilePair([meta_file, data_file]) 93 | file_pair.update() 94 | # retar the archive 95 | temp_tar_path = os.path.join(temp_dir, "temp.sigmf") 96 | new_tar = tarfile.open(temp_tar_path, "w:") 97 | new_tar.add(meta_file) 98 | new_tar.add(data_file) 99 | new_tar.close() 100 | # Copy it over the existing archive 101 | shutil.copy2(temp_tar_path, self.archive_path) 102 | # Clean up the temp directory 103 | shutil.rmtree(temp_dir) 104 | 105 | 106 | class FilePair(object): 107 | def __init__(self, files): 108 | self.data_file = [f for f in files if f.endswith("sigmf-data")][0] 109 | self.meta_file = [f for f in files if f.endswith("sigmf-meta")][0] 110 | 111 | def check(self): 112 | with open(self.meta_file, "r") as mf: 113 | meta = json.load(mf) 114 | try: 115 | cur_hash = meta["global"]["core:sha512"] 116 | except KeyError: 117 | print("No hash in file %s" % self.data_file) 118 | return 119 | df_size = os.path.getsize(self.data_file) 120 | computed_hash = hash_file(self.data_file, df_size) 121 | if cur_hash != computed_hash: 122 | print(str(cur_hash)) 123 | print(str(computed_hash)) 124 | print("Hash doesn't match") 125 | else: 126 | print("Hash match") 127 | 128 | def update(self): 129 | with open(self.meta_file, "r+") as mf: 130 | meta = json.load(mf) 131 | df_size = os.path.getsize(self.data_file) 132 | computed_hash = hash_file(self.data_file, df_size) 133 | meta["global"]["core:sha512"] = computed_hash 134 | mf.seek(0) 135 | json.dump(meta, mf) 136 | mf.truncate() 137 | 138 | 139 | class SigmfHash(object): 140 | 141 | def __init__(self): 142 | parser = argparse.ArgumentParser( 143 | description='Check and update hashes on sigmf files', 144 | usage='''sigmf-hash [] 145 | 146 | The available commands are: 147 | check Verify the hash of a dataset 148 | update Recompute and update the hash of a dataset 149 | ''') 150 | parser.add_argument('command', help='Subcommand to run') 151 | 152 | # parse_args defaults to [1:] for args, but you need to 153 | # exclude the rest of the args too, or validation will fail 154 | args = parser.parse_args(sys.argv[1:2]) 155 | if not hasattr(self, args.command): 156 | print('Unrecognized command') 157 | parser.print_help() 158 | exit(1) 159 | getattr(self, args.command)() 160 | 161 | def _build_file_list(self, raw_list): 162 | final_file_list = [] 163 | for k, g in groupby(raw_list, lambda f: os.path.splitext(f)[0]): 164 | group_list = list(g) 165 | if (len(group_list) == 1): 166 | base, ext = os.path.splitext(group_list[0]) 167 | if ext == ".sigmf": 168 | final_file_list.append(Archive(group_list[0])) 169 | continue 170 | elif ext == ".sigmf-meta": 171 | other_file = base + ".sigmf-data" 172 | else: 173 | other_file = base + ".sigmf-meta" 174 | group_list.append(other_file) 175 | final_file_list.append(FilePair(group_list)) 176 | return final_file_list 177 | 178 | def check(self): 179 | parser = argparse.ArgumentParser( 180 | description='Check file hashes') 181 | parser.add_argument('files', nargs="+") 182 | 183 | args = parser.parse_args(sys.argv[2:]) 184 | # file_list = args.files 185 | final_file_list = self._build_file_list(args.files) 186 | for f in final_file_list: 187 | f.check() 188 | 189 | def update(self): 190 | parser = argparse.ArgumentParser( 191 | description='Update file hashes') 192 | parser.add_argument('files', nargs="+") 193 | 194 | args = parser.parse_args(sys.argv[2:]) 195 | # file_list = args.files 196 | final_file_list = self._build_file_list(args.files) 197 | for f in final_file_list: 198 | f.update() 199 | 200 | 201 | if __name__ == '__main__': 202 | SigmfHash() 203 | -------------------------------------------------------------------------------- /lib/meta_namespace.cc: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2017 Scott Torborg, Paul Wicks, Caitlin Miller 4 | * 5 | * This is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * 10 | * This software is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this software; see the file COPYING. If not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include "sigmf/meta_namespace.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | using namespace rapidjson; 29 | namespace gr { 30 | namespace sigmf { 31 | 32 | metafile_namespaces 33 | load_metafile(FILE *fp) 34 | { 35 | char buffer[65536]; 36 | metafile_namespaces meta_ns; 37 | FileReadStream file_stream(fp, buffer, sizeof(buffer)); 38 | Document doc; 39 | doc.ParseStream<0, UTF8<>, FileReadStream>(file_stream); 40 | 41 | if(doc.HasParseError()) { 42 | throw std::runtime_error("Meta namespace parse error - invalid metadata."); 43 | } 44 | 45 | meta_ns.global = json_value_to_pmt(doc["global"]); 46 | size_t num_captures = doc["captures"].Size(); 47 | 48 | for(size_t i = 0; i < num_captures; i++) { 49 | meta_ns.captures.push_back(json_value_to_pmt(doc["captures"][i])); 50 | } 51 | size_t num_annotations; 52 | if(doc.HasMember("annotations")) { 53 | num_annotations = doc["annotations"].Size(); 54 | } else { 55 | num_annotations = 0; 56 | } 57 | 58 | for(size_t i = 0; i < num_annotations; i++) { 59 | meta_ns.annotations.push_back(json_value_to_pmt(doc["annotations"][i])); 60 | } 61 | return meta_ns; 62 | } 63 | 64 | pmt::pmt_t 65 | json_value_to_pmt(const Value &val) 66 | { 67 | if(val.IsObject()) { 68 | pmt::pmt_t obj = pmt::make_dict(); 69 | for(Value::ConstMemberIterator itr = val.MemberBegin(); itr != val.MemberEnd(); ++itr) { 70 | std::string key_str = itr->name.GetString(); 71 | pmt::pmt_t key = pmt::string_to_symbol(key_str); 72 | if (key_str == "core:sample_rate") { 73 | // Coerce this to a double to prevent badness 74 | pmt::pmt_t val_for_key = pmt::from_double(itr->value.GetDouble()); 75 | obj = pmt::dict_add(obj, key, val_for_key); 76 | } else { 77 | pmt::pmt_t val_for_key = json_value_to_pmt(itr->value); 78 | obj = pmt::dict_add(obj, key, val_for_key); 79 | } 80 | } 81 | return obj; 82 | } else if(val.IsArray()) { 83 | pmt::pmt_t array = pmt::make_vector(val.Size(), pmt::get_PMT_NIL()); 84 | size_t index = 0; 85 | for(Value::ConstValueIterator itr = val.Begin(); itr != val.End(); ++itr) { 86 | pmt::vector_set(array, index, json_value_to_pmt(*itr)); 87 | index++; 88 | } 89 | return array; 90 | } else if(val.IsBool()) { 91 | return pmt::from_bool(val.GetBool()); 92 | } else if(val.IsUint64()) { 93 | return pmt::from_uint64(val.GetUint64()); 94 | } else if(val.IsInt64()) { 95 | return pmt::from_long(val.GetInt64()); 96 | } else if (val.IsInt()) { 97 | return pmt::from_long(val.GetInt()); 98 | } else if(val.IsDouble()) { 99 | return pmt::from_double(val.GetDouble()); 100 | } else if(val.IsNull()) { 101 | return pmt::get_PMT_NIL(); 102 | } else if(val.IsString()) { 103 | return pmt::string_to_symbol(val.GetString()); 104 | } else { 105 | std::cerr << "Invalid type in json" << std::endl; 106 | return NULL; 107 | } 108 | } 109 | 110 | 111 | meta_namespace 112 | meta_namespace::build_global_object(std::string datatype, std::string version) 113 | { 114 | meta_namespace ns; 115 | ns.set("core:datatype", datatype); 116 | ns.set("core:version", version); 117 | return ns; 118 | } 119 | 120 | meta_namespace 121 | meta_namespace::build_capture_segment(uint64_t sample_start) 122 | { 123 | meta_namespace ns; 124 | ns.set("core:sample_start", sample_start); 125 | return ns; 126 | } 127 | 128 | meta_namespace 129 | meta_namespace::build_annotation_segment(uint64_t sample_start, uint64_t sample_count) 130 | { 131 | meta_namespace ns; 132 | ns.set("core:sample_start", sample_start); 133 | ns.set("core:sample_count", sample_count); 134 | return ns; 135 | } 136 | 137 | meta_namespace::meta_namespace(pmt::pmt_t data) : d_data(data) 138 | { 139 | } 140 | 141 | meta_namespace::meta_namespace() 142 | { 143 | d_data = pmt::make_dict(); 144 | } 145 | 146 | meta_namespace::~meta_namespace() 147 | { 148 | } 149 | 150 | pmt::pmt_t 151 | meta_namespace::data() 152 | { 153 | return d_data; 154 | } 155 | 156 | void 157 | meta_namespace::set(const std::string &key, pmt::pmt_t val) 158 | { 159 | if(!validate_key(key)) { 160 | throw std::invalid_argument("key format is invalid:'" + key + "'"); 161 | } 162 | d_data = pmt::dict_add(d_data, pmt::mp(key), val); 163 | } 164 | 165 | pmt::pmt_t 166 | meta_namespace::get(const std::string &key) const 167 | { 168 | return get(pmt::mp(key)); 169 | } 170 | 171 | pmt::pmt_t 172 | meta_namespace::get(const std::string &key, pmt::pmt_t default_val) const 173 | { 174 | return get(pmt::mp(key), default_val); 175 | } 176 | 177 | pmt::pmt_t 178 | meta_namespace::get(pmt::pmt_t key, pmt::pmt_t default_val) const { 179 | return pmt::dict_ref(d_data, key, default_val); 180 | } 181 | 182 | pmt::pmt_t 183 | meta_namespace::get(pmt::pmt_t key) const 184 | { 185 | return get(key, pmt::get_PMT_NIL()); 186 | } 187 | 188 | pmt::pmt_t 189 | meta_namespace::get() const 190 | { 191 | return d_data; 192 | } 193 | 194 | std::string 195 | meta_namespace::get_str(const std::string &key) const 196 | { 197 | pmt::pmt_t ref = pmt::dict_ref(d_data, pmt::mp(key), pmt::get_PMT_NIL()); 198 | if(pmt::eqv(ref, pmt::get_PMT_NIL())) { 199 | throw std::runtime_error("key not found"); 200 | } else if(!pmt::is_symbol(ref)) { 201 | throw std::runtime_error("val is not str"); 202 | } 203 | return pmt::symbol_to_string(ref); 204 | } 205 | 206 | bool 207 | meta_namespace::has(const std::string &key) const 208 | { 209 | return pmt::dict_has_key(d_data, pmt::mp(key)); 210 | } 211 | 212 | std::set 213 | meta_namespace::keys() const 214 | { 215 | pmt::pmt_t keys_pmt = pmt::dict_keys(d_data); 216 | std::set keys; 217 | size_t num_keys = pmt::length(keys_pmt); 218 | for(size_t i = 0; i < num_keys; i++) { 219 | keys.insert(pmt::symbol_to_string(pmt::nth(i, keys_pmt))); 220 | } 221 | return keys; 222 | } 223 | 224 | bool 225 | meta_namespace::validate_key(const std::string &key) 226 | { 227 | boost::regex key_regex("(^\\w+:\\w+$)"); 228 | return boost::regex_match(key, key_regex); 229 | } 230 | 231 | void 232 | meta_namespace::del(const std::string &key) 233 | { 234 | d_data = pmt::dict_delete(d_data, pmt::mp(key)); 235 | } 236 | 237 | void 238 | meta_namespace::print() const 239 | { 240 | pmt::print(d_data); 241 | } 242 | 243 | } // namespace sigmf 244 | } // namespace gr 245 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011-2020 Free Software Foundation, Inc. 2 | # 3 | # This file was generated by gr_modtool, a tool from the GNU Radio framework 4 | # This file is a part of gr-sigmf 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | ######################################################################## 10 | # Project setup 11 | ######################################################################## 12 | cmake_minimum_required(VERSION 3.8) 13 | project(gr-sigmf CXX C) 14 | enable_testing() 15 | 16 | # Install to PyBOMBS target prefix if defined 17 | if(DEFINED ENV{PYBOMBS_PREFIX}) 18 | set(CMAKE_INSTALL_PREFIX $ENV{PYBOMBS_PREFIX}) 19 | message(STATUS "PyBOMBS installed GNU Radio. Setting CMAKE_INSTALL_PREFIX to $ENV{PYBOMBS_PREFIX}") 20 | endif() 21 | 22 | # Select the release build type by default to get optimization flags 23 | if(NOT CMAKE_BUILD_TYPE) 24 | set(CMAKE_BUILD_TYPE "Release") 25 | message(STATUS "Build type not specified: defaulting to release.") 26 | endif(NOT CMAKE_BUILD_TYPE) 27 | set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "") 28 | 29 | # Make sure our local CMake Modules path comes first 30 | list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules) 31 | 32 | # Set the version information here 33 | set(VERSION_MAJOR 1) 34 | set(VERSION_API 0) 35 | set(VERSION_ABI 0) 36 | set(VERSION_PATCH git) 37 | 38 | cmake_policy(SET CMP0011 NEW) 39 | 40 | # Enable generation of compile_commands.json for code completion engines 41 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 42 | 43 | ######################################################################## 44 | # Compiler specific setup 45 | ######################################################################## 46 | if((CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR 47 | CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 48 | AND NOT WIN32) 49 | #http://gcc.gnu.org/wiki/Visibility 50 | add_definitions(-fvisibility=hidden) 51 | endif() 52 | 53 | IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 54 | SET(CMAKE_CXX_STANDARD 14) 55 | ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 56 | SET(CMAKE_CXX_STANDARD 14) 57 | ELSEIF(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 58 | SET(CMAKE_CXX_STANDARD 14) 59 | ELSE() 60 | message(WARNING "C++ standard could not be set because compiler is not GNU, Clang or MSVC.") 61 | ENDIF() 62 | 63 | IF(CMAKE_C_COMPILER_ID STREQUAL "GNU") 64 | SET(CMAKE_C_STANDARD 11) 65 | ELSEIF(CMAKE_C_COMPILER_ID MATCHES "Clang") 66 | SET(CMAKE_C_STANDARD 11) 67 | ELSEIF(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 68 | SET(CMAKE_C_STANDARD 11) 69 | ELSE() 70 | message(WARNING "C standard could not be set because compiler is not GNU, Clang or MSVC.") 71 | ENDIF() 72 | 73 | ######################################################################## 74 | # Install directories 75 | ######################################################################## 76 | include(FindPkgConfig) 77 | find_package(Gnuradio "3.9" REQUIRED COMPONENTS runtime uhd blocks) 78 | include(GrVersion) 79 | 80 | include(GrPlatform) #define LIB_SUFFIX 81 | 82 | if(NOT CMAKE_MODULES_DIR) 83 | set(CMAKE_MODULES_DIR lib${LIB_SUFFIX}/cmake) 84 | endif(NOT CMAKE_MODULES_DIR) 85 | 86 | set(GR_INCLUDE_DIR include/sigmf) 87 | set(GR_CMAKE_DIR ${CMAKE_MODULES_DIR}/sigmf) 88 | set(GR_PKG_DATA_DIR ${GR_DATA_DIR}/${CMAKE_PROJECT_NAME}) 89 | set(GR_PKG_DOC_DIR ${GR_DOC_DIR}/${CMAKE_PROJECT_NAME}) 90 | set(GR_PKG_CONF_DIR ${GR_CONF_DIR}/${CMAKE_PROJECT_NAME}/conf.d) 91 | set(GR_PKG_LIBEXEC_DIR ${GR_LIBEXEC_DIR}/${CMAKE_PROJECT_NAME}) 92 | 93 | ######################################################################## 94 | # On Apple only, set install name and use rpath correctly, if not already set 95 | ######################################################################## 96 | if(APPLE) 97 | if(NOT CMAKE_INSTALL_NAME_DIR) 98 | set(CMAKE_INSTALL_NAME_DIR 99 | ${CMAKE_INSTALL_PREFIX}/${GR_LIBRARY_DIR} CACHE 100 | PATH "Library Install Name Destination Directory" FORCE) 101 | endif(NOT CMAKE_INSTALL_NAME_DIR) 102 | if(NOT CMAKE_INSTALL_RPATH) 103 | set(CMAKE_INSTALL_RPATH 104 | ${CMAKE_INSTALL_PREFIX}/${GR_LIBRARY_DIR} CACHE 105 | PATH "Library Install RPath" FORCE) 106 | endif(NOT CMAKE_INSTALL_RPATH) 107 | if(NOT CMAKE_BUILD_WITH_INSTALL_RPATH) 108 | set(CMAKE_BUILD_WITH_INSTALL_RPATH ON CACHE 109 | BOOL "Do Build Using Library Install RPath" FORCE) 110 | endif(NOT CMAKE_BUILD_WITH_INSTALL_RPATH) 111 | endif(APPLE) 112 | 113 | ######################################################################## 114 | # Find gnuradio build dependencies 115 | ######################################################################## 116 | find_package(CppUnit) 117 | find_package(Doxygen) 118 | 119 | ######################################################################## 120 | # Setup doxygen option 121 | ######################################################################## 122 | if(DOXYGEN_FOUND) 123 | option(ENABLE_DOXYGEN "Build docs using Doxygen" ON) 124 | else(DOXYGEN_FOUND) 125 | option(ENABLE_DOXYGEN "Build docs using Doxygen" OFF) 126 | endif(DOXYGEN_FOUND) 127 | 128 | ######################################################################## 129 | # Find boost 130 | ######################################################################## 131 | # Note: ordering is important here, we need to find Boost after GNU 132 | # Radio does, or else it will overwrite our required components. 133 | if(UNIX AND EXISTS "/usr/lib64") 134 | list(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix 135 | endif(UNIX AND EXISTS "/usr/lib64") 136 | set(Boost_ADDITIONAL_VERSIONS 137 | "1.35.0" "1.35" "1.36.0" "1.36" "1.37.0" "1.37" "1.38.0" "1.38" "1.39.0" "1.39" 138 | "1.40.0" "1.40" "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44" 139 | "1.45.0" "1.45" "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.49.0" "1.49" 140 | "1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54" 141 | "1.55.0" "1.55" "1.56.0" "1.56" "1.57.0" "1.57" "1.58.0" "1.58" "1.59.0" "1.59" 142 | "1.60.0" "1.60" "1.61.0" "1.61" "1.62.0" "1.62" "1.63.0" "1.63" "1.64.0" "1.64" 143 | "1.65.0" "1.65" "1.66.0" "1.66" "1.67.0" "1.67" "1.68.0" "1.68" "1.69.0" "1.69" 144 | ) 145 | find_package(Boost "1.58" REQUIRED COMPONENTS filesystem system regex chrono program_options 146 | log thread) 147 | 148 | if(NOT Boost_FOUND) 149 | message(FATAL_ERROR "Boost required to compile sigmf") 150 | endif() 151 | 152 | ######################################################################## 153 | # Find other dependencies 154 | ######################################################################## 155 | find_package(RapidJson REQUIRED) 156 | 157 | ######################################################################## 158 | # Create uninstall target 159 | ######################################################################## 160 | configure_file( 161 | ${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in 162 | ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake 163 | @ONLY) 164 | 165 | add_custom_target(uninstall 166 | ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake 167 | ) 168 | 169 | ######################################################################## 170 | # Add subdirectories 171 | ######################################################################## 172 | add_subdirectory(include/sigmf) 173 | add_subdirectory(lib) 174 | add_subdirectory(apps) 175 | add_subdirectory(docs) 176 | # NOTE: manually update below to use GRC to generate C++ flowgraphs w/o python 177 | if(ENABLE_PYTHON) 178 | message(STATUS "PYTHON and GRC components are enabled") 179 | add_subdirectory(python) 180 | add_subdirectory(grc) 181 | else(ENABLE_PYTHON) 182 | message(STATUS "PYTHON and GRC components are disabled") 183 | endif(ENABLE_PYTHON) 184 | 185 | ######################################################################## 186 | # Install cmake search helper for this library 187 | ######################################################################## 188 | 189 | install(FILES cmake/Modules/sigmfConfig.cmake 190 | DESTINATION ${CMAKE_MODULES_DIR}/sigmf 191 | ) 192 | --------------------------------------------------------------------------------