├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── MANIFEST.md ├── README.md ├── apps └── CMakeLists.txt ├── cmake ├── Modules │ ├── CMakeParseArgumentsCopy.cmake │ ├── gnuradio-fhss_utilsConfig.cmake │ ├── gnuradio-fhss_utilsConfigVersion.cmake.in │ └── targetConfig.cmake.in └── cmake_uninstall.cmake.in ├── docs ├── CMakeLists.txt ├── README.fhss_utils ├── burst_detector.png ├── doxygen │ ├── CMakeLists.txt │ ├── Doxyfile.in │ ├── doxyxml │ │ ├── __init__.py │ │ ├── base.py │ │ ├── doxyindex.py │ │ ├── generated │ │ │ ├── __init__.py │ │ │ ├── compound.py │ │ │ ├── compoundsuper.py │ │ │ ├── index.py │ │ │ └── indexsuper.py │ │ └── text.py │ ├── other │ │ ├── group_defs.dox │ │ └── main_page.dox │ ├── pydoc_macros.h │ └── update_pydoc.py ├── fhss.jpg └── figures │ ├── burst_detector.png │ ├── middle_out.png │ └── snl.png ├── examples ├── README ├── burst_extractor_hier.grc ├── burst_extractor_nohier.grc ├── fhss_detector_reference.grc └── hier_blocks │ ├── coarse_dehopper.grc │ ├── fft_peak.grc │ ├── fine_dehopper.grc │ ├── s_and_h_detector.grc │ ├── testbed_coarse_dehopper.grc │ └── testbed_fine_dehopper.grc ├── gnuradio-fhss_utils.pc.in ├── grc ├── CMakeLists.txt ├── fhss_utils_burst_tag_debug.block.yml ├── fhss_utils_cf_estimate.block.yml ├── fhss_utils_coarse_dehopper.block.yml ├── fhss_utils_fft_burst_tagger.block.yml ├── fhss_utils_fft_peak.block.yml ├── fhss_utils_fine_dehopper.block.yml ├── fhss_utils_fsk_burst_extractor_hier.block.yml ├── fhss_utils_s_and_h_detector.block.yml ├── fhss_utils_sigmf_meta_writer.block.yml └── fhss_utils_tagged_burst_to_pdu.block.yml ├── include └── gnuradio │ └── fhss_utils │ ├── CMakeLists.txt │ ├── api.h │ ├── cf_estimate.h │ ├── constants.h │ ├── fft_burst_tagger.h │ └── tagged_burst_to_pdu.h ├── lib ├── CMakeLists.txt ├── cf_estimate_impl.cc ├── cf_estimate_impl.h ├── constants.cc ├── fft_burst_tagger_impl.cc ├── fft_burst_tagger_impl.h ├── tagged_burst_to_pdu_impl.cc └── tagged_burst_to_pdu_impl.h └── python └── fhss_utils ├── CMakeLists.txt ├── __init__.py ├── bindings ├── CMakeLists.txt ├── README.md ├── bind_oot_file.py ├── cf_estimate_python.cc ├── constants_python.cc ├── docstrings │ ├── README.md │ ├── cf_estimate_pydoc_template.h │ ├── constants_pydoc_template.h │ ├── fft_burst_tagger_pydoc_template.h │ └── tagged_burst_to_pdu_pydoc_template.h ├── fft_burst_tagger_python.cc ├── header_utils.py ├── python_bindings.cc └── tagged_burst_to_pdu_python.cc ├── burst_tag_debug.py ├── coarse_dehopper.py ├── fft_peak.py ├── fine_dehopper.py ├── fsk_burst_extractor_hier.py ├── qa_cf_estimate.py ├── qa_constants.py ├── qa_fft_burst_tagger.py ├── qa_tagged_burst_to_pdu.py ├── s_and_h_detector.py └── sigmf_meta_writer.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: true 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BreakBeforeBraces: Custom 24 | BraceWrapping: 25 | AfterClass: true 26 | AfterControlStatement: false 27 | AfterEnum: false 28 | AfterFunction: true 29 | AfterNamespace: false 30 | AfterObjCDeclaration: false 31 | AfterStruct: false 32 | AfterUnion: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | BreakBeforeBinaryOperators: None 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | BreakAfterJavaFieldAnnotations: false 40 | BreakStringLiterals: true 41 | ColumnLimit: 90 42 | CommentPragmas: '^ IWYU pragma:' 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 44 | ConstructorInitializerIndentWidth: 4 45 | ContinuationIndentWidth: 4 46 | Cpp11BracedListStyle: false 47 | DerivePointerAlignment: false 48 | DisableFormat: false 49 | ExperimentalAutoDetectBinPacking: false 50 | ForEachMacros: 51 | - foreach 52 | - Q_FOREACH 53 | - BOOST_FOREACH 54 | IncludeCategories: 55 | - Regex: '^"(gnuradio)/' 56 | Priority: 1 57 | - Regex: '^<(gnuradio)/' 58 | Priority: 2 59 | - Regex: '^<(boost)/' 60 | Priority: 98 61 | - Regex: '^<[a-z]*>$' 62 | Priority: 99 63 | - Regex: '^".*"$' 64 | Priority: 0 65 | - Regex: '.*' 66 | Priority: 10 67 | 68 | IncludeIsMainRegex: '(Test)?$' 69 | IndentCaseLabels: false 70 | IndentWidth: 4 71 | IndentWrappedFunctionNames: false 72 | JavaScriptQuotes: Leave 73 | JavaScriptWrapImports: true 74 | KeepEmptyLinesAtTheStartOfBlocks: true 75 | MacroBlockBegin: '' 76 | MacroBlockEnd: '' 77 | MaxEmptyLinesToKeep: 2 78 | NamespaceIndentation: None 79 | ObjCBlockIndentWidth: 2 80 | ObjCSpaceAfterProperty: false 81 | ObjCSpaceBeforeProtocolList: true 82 | PenaltyBreakBeforeFirstCallParameter: 19 83 | PenaltyBreakComment: 300 84 | PenaltyBreakFirstLessLess: 120 85 | PenaltyBreakString: 1000 86 | PenaltyExcessCharacter: 1000000 87 | PenaltyReturnTypeOnItsOwnLine: 60 88 | PointerAlignment: Left 89 | ReflowComments: true 90 | SortIncludes: true 91 | SpaceAfterCStyleCast: false 92 | SpaceAfterTemplateKeyword: true 93 | SpaceBeforeAssignmentOperators: true 94 | SpaceBeforeParens: ControlStatements 95 | SpaceInEmptyParentheses: false 96 | SpacesBeforeTrailingComments: 1 97 | SpacesInAngles: false 98 | SpacesInContainerLiterals: true 99 | SpacesInCStyleCastParentheses: false 100 | SpacesInParentheses: false 101 | SpacesInSquareBrackets: false 102 | Standard: Cpp11 103 | TabWidth: 8 104 | UseTab: Never 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | *.pyo 10 | *.pyc 11 | *.gch 12 | 13 | # Temp VI files # 14 | ################# 15 | *~ 16 | *.swp 17 | *.bak 18 | 19 | # Packages # 20 | ############ 21 | # it's better to unpack these files and commit the raw source 22 | # git has its own built in compression methods 23 | *.7z 24 | *.dmg 25 | *.gz 26 | *.iso 27 | *.jar 28 | *.rar 29 | *.tar 30 | *.zip 31 | 32 | # Logs and databases # 33 | ###################### 34 | *.log 35 | *.sql 36 | *.sqlite 37 | 38 | # OS generated files # 39 | ###################### 40 | .DS_Store 41 | .DS_Store? 42 | ._* 43 | .Spotlight-V100 44 | .Trashes 45 | ehthumbs.db 46 | Thumbs.db 47 | 48 | # build files # 49 | ############### 50 | build 51 | build/* 52 | 53 | # GRC python files # 54 | #################### 55 | examples/*.py 56 | examples/*/*.py 57 | examples/*/*/*.py 58 | 59 | # unit test files # 60 | ################### 61 | .unittests 62 | .unittests/* 63 | 64 | /.cproject 65 | /.project 66 | /.pydevproject 67 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2011-2021 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-fhss_utils 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | # Select the release build type by default to get optimization flags. 10 | # This has to come before project() which otherwise initializes it. 11 | # Build type can still be overridden by setting -DCMAKE_BUILD_TYPE= 12 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") 13 | 14 | ######################################################################## 15 | # Project setup 16 | ######################################################################## 17 | cmake_minimum_required(VERSION 3.8) 18 | project(gr-fhss_utils CXX C) 19 | enable_testing() 20 | 21 | # Install to PyBOMBS target prefix if defined 22 | if(DEFINED ENV{PYBOMBS_PREFIX}) 23 | set(CMAKE_INSTALL_PREFIX $ENV{PYBOMBS_PREFIX}) 24 | message(STATUS "PyBOMBS installed GNU Radio. Setting CMAKE_INSTALL_PREFIX to $ENV{PYBOMBS_PREFIX}") 25 | endif() 26 | 27 | # Make sure our local CMake Modules path comes first 28 | list(INSERT CMAKE_MODULE_PATH 0 ${PROJECT_SOURCE_DIR}/cmake/Modules) 29 | # Find gnuradio to get access to the cmake modules 30 | find_package(Gnuradio "3.10" REQUIRED COMPONENTS filter runtime pmt blocks fft) 31 | 32 | # Set the version information here 33 | # cmake-format: off 34 | set(VERSION_MAJOR 3) 35 | set(VERSION_API 10) 36 | set(VERSION_ABI 0) 37 | set(VERSION_PATCH 0) 38 | # cmake-format: on 39 | 40 | cmake_policy(SET CMP0011 NEW) 41 | 42 | # Enable generation of compile_commands.json for code completion engines 43 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 44 | 45 | ######################################################################## 46 | # Minimum Version Requirements 47 | ######################################################################## 48 | 49 | include(GrMinReq) 50 | 51 | ######################################################################## 52 | # Compiler settings 53 | ######################################################################## 54 | 55 | include(GrCompilerSettings) 56 | 57 | ######################################################################## 58 | # Install directories 59 | ######################################################################## 60 | include(GrVersion) 61 | 62 | include(GrPlatform) #define LIB_SUFFIX 63 | 64 | if(NOT CMAKE_MODULES_DIR) 65 | set(CMAKE_MODULES_DIR lib${LIB_SUFFIX}/cmake) 66 | endif(NOT CMAKE_MODULES_DIR) 67 | 68 | set(GR_INCLUDE_DIR include/gnuradio/fhss_utils) 69 | set(GR_CMAKE_DIR ${CMAKE_MODULES_DIR}/gnuradio-fhss_utils) 70 | set(GR_PKG_DATA_DIR ${GR_DATA_DIR}/${CMAKE_PROJECT_NAME}) 71 | set(GR_PKG_DOC_DIR ${GR_DOC_DIR}/${CMAKE_PROJECT_NAME}) 72 | set(GR_PKG_CONF_DIR ${GR_CONF_DIR}/${CMAKE_PROJECT_NAME}/conf.d) 73 | set(GR_PKG_LIBEXEC_DIR ${GR_LIBEXEC_DIR}/${CMAKE_PROJECT_NAME}) 74 | 75 | ######################################################################## 76 | # On Apple only, set install name and use rpath correctly, if not already set 77 | ######################################################################## 78 | if(APPLE) 79 | add_definitions(-DDARWIN) 80 | if(NOT CMAKE_INSTALL_NAME_DIR) 81 | set(CMAKE_INSTALL_NAME_DIR 82 | ${CMAKE_INSTALL_PREFIX}/${GR_LIBRARY_DIR} CACHE 83 | PATH "Library Install Name Destination Directory" FORCE) 84 | endif(NOT CMAKE_INSTALL_NAME_DIR) 85 | if(NOT CMAKE_INSTALL_RPATH) 86 | set(CMAKE_INSTALL_RPATH 87 | ${CMAKE_INSTALL_PREFIX}/${GR_LIBRARY_DIR} CACHE 88 | PATH "Library Install RPath" FORCE) 89 | endif(NOT CMAKE_INSTALL_RPATH) 90 | if(NOT CMAKE_BUILD_WITH_INSTALL_RPATH) 91 | set(CMAKE_BUILD_WITH_INSTALL_RPATH ON CACHE 92 | BOOL "Do Build Using Library Install RPath" FORCE) 93 | endif(NOT CMAKE_BUILD_WITH_INSTALL_RPATH) 94 | endif(APPLE) 95 | 96 | ######################################################################## 97 | # Find gnuradio build dependencies 98 | ######################################################################## 99 | find_package(Doxygen) 100 | 101 | ######################################################################## 102 | # Dependencies 103 | ######################################################################## 104 | find_package(gnuradio-pdu_utils "3.10" REQUIRED) 105 | find_package(gnuradio-timing_utils "3.10" REQUIRED) 106 | 107 | ######################################################################## 108 | # Setup doxygen option 109 | ######################################################################## 110 | if(DOXYGEN_FOUND) 111 | option(ENABLE_DOXYGEN "Build docs using Doxygen" ON) 112 | else(DOXYGEN_FOUND) 113 | option(ENABLE_DOXYGEN "Build docs using Doxygen" OFF) 114 | endif(DOXYGEN_FOUND) 115 | 116 | ######################################################################## 117 | # Create uninstall target 118 | ######################################################################## 119 | configure_file( 120 | ${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in 121 | ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake 122 | @ONLY) 123 | 124 | add_custom_target(uninstall 125 | ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake 126 | ) 127 | 128 | ######################################################################## 129 | # Add subdirectories 130 | ######################################################################## 131 | add_subdirectory(include/gnuradio/fhss_utils) 132 | add_subdirectory(lib) 133 | add_subdirectory(apps) 134 | add_subdirectory(docs) 135 | # NOTE: manually update below to use GRC to generate C++ flowgraphs w/o python 136 | if(ENABLE_PYTHON) 137 | message(STATUS "PYTHON and GRC components are enabled") 138 | add_subdirectory(python/fhss_utils) 139 | add_subdirectory(grc) 140 | else(ENABLE_PYTHON) 141 | message(STATUS "PYTHON and GRC components are disabled") 142 | endif(ENABLE_PYTHON) 143 | 144 | ######################################################################## 145 | # Install cmake search helper for this library 146 | ######################################################################## 147 | 148 | install(FILES cmake/Modules/gnuradio-fhss_utilsConfig.cmake 149 | DESTINATION ${GR_CMAKE_DIR} 150 | ) 151 | 152 | include(CMakePackageConfigHelpers) 153 | configure_package_config_file( 154 | ${PROJECT_SOURCE_DIR}/cmake/Modules/targetConfig.cmake.in 155 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/Modules/${target}Config.cmake 156 | INSTALL_DESTINATION ${GR_CMAKE_DIR} 157 | ) 158 | 159 | configure_file(${PROJECT_SOURCE_DIR}/cmake/Modules/gnuradio-fhss_utilsConfigVersion.cmake.in 160 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/Modules/gnuradio-fhss_utilsConfigVersion.cmake 161 | @ONLY) 162 | 163 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/Modules/gnuradio-fhss_utilsConfigVersion.cmake 164 | DESTINATION ${GR_CMAKE_DIR} 165 | ) 166 | -------------------------------------------------------------------------------- /MANIFEST.md: -------------------------------------------------------------------------------- 1 | title: The FHSS_UTILS OOT Module 2 | brief: Broadband energy based burst detection blocks 3 | tags: 4 | - fhss 5 | - burst 6 | - pdu 7 | author: 8 | - Sandia National Laboratories 9 | - Jacob Gilbert 10 | - Peter Knee 11 | - Sam Whiting 12 | - Brian Adams 13 | - Darren Kartchner 14 | copyright_owner: 15 | - NTESS, LLC 16 | license: GPLv3 17 | gr_supported_version: v3.7, v3.8, v3.10 18 | dependencies: gr-pdu_utils 19 | repo: https://github.com/sandialabs/gr-fhss_utils 20 | icon: https://raw.githubusercontent.com/sandialabs/gr-fhss_utils/master/docs/fhss.jpg 21 | --- 22 | This GNU Radio module contains tools for detection of bursts in a frequency band of unknown or unpredictable time/frequency composition, originally intended for use with FHSS systems for which hopping behavior is not well understood. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![snl](docs/figures/snl.png "Sandia National Laboratories") 2 | 3 | ## GNU Radio FHSS Utilities 4 | 5 | This GNU Radio module contains tools for processing frequency hopping spread spectrum signals. Blocks derived from the gr-iridium project exist to detect narrowband bursts within wideband signals and downconvert and center them. Metadata is tracked through this process enabling reconstruction of where the bursts originated in time and frequency. Another set of blocks exists to baseband all bursts within a high fidelity signal capture which is useful for reverse engineering of FHSS datasets. 6 | 7 | --- 8 | 9 | ### General Concept of High Fidelity FHSS Signal Dehopping 10 | 11 | The dataset dehopper blocks were designed to quickly allow for good accuracy dehopping of high fidelity FHSS FSK recordings. This is accomplished by a two-stage dehopping process by which a coarse FFT is taken and peak values are taken by a simple sample-and-hold block when an amplitude threshold is crossed, then a second stage does fine frequency correction by taking an instantaneous frequency average. This works well for FSK signals but requires some work for other signals. 12 | 13 | This has been implemented without use of OOT DSP, though the module has python hier blocks that are installed with the build. Alternately there are GRC hier blocks located in the _gr-fhss_utils/examples/hier_blocks/_ directory 14 | 15 | --- 16 | 17 | ### General Concept of the FFT Burst Detector 18 | 19 | This block is a fast energy detector that processes a stream of complex data and will apply stream tags to identify the start, end, and approximate frequency of detected bursts of energy. 20 | 21 | Internally this block processes data one FFT at a time. It maintains a dynamic noise floor estimate for each bin over the prior `history_size` FFTs, and compares incoming FFT bins to see if they are `threshold` dB above the moving noise floor estimate. Once detected, potential bursts are observed to ensure they remain above the threshold for at least `lookahead` FFTs, after which they are tracked as detections and tagged; bursts that are shorter than this duration are likely to be charachterized as noise and ignored. 22 | 23 | Burst tags can be applied before / after the detected start / end of burst using the `burst_pre_len` and `burst_post_len` options respectively. End of burst tags are appended once the value of the center burst bin or either adjacent bin is gone for `burst_post_len` FFTs, so it is recommended to set this value to some nonzero number to reduce the chance a burst end is tagged too early. If this value is set too long, multiple bursts may be combined into one burst. 24 | 25 | Debug information including detected peak value files (saved in /tmp) and a complex PDU suitable for display on the in-tree QT Time Sink of FFTs and dynamic threshold can be endabled for debugging, but it is not recommended for normal operation as these take a non-trivial amount of compute cycles. Deeper level debug information can be enabled using compile options in the source. 26 | 27 | ![burst_detector](docs/figures/burst_detector.png "Burst Detector Operation") 28 | 29 | --- 30 | 31 | ### Middle Out Frequency and Bandwidth Estimation 32 | 33 | The center frequency estimation block contains an otherwise undocumented method for burst frequency boundary detection that is useful for accurate center frequency and bandwidth estimation of high fidelity signals. The noise floor estimate from the FFT burst tagger and user specified minimum signal to noise ratio is used to determine the threshold (halfway between these values) for burst edges, coupled with the narrow Gaussian window used by this block results in good definition many signal types. This method is depicted below: 34 | 35 | ![middle_out](docs/figures/middle_out.png "Middle Out Bandwidth Estimation") 36 | 37 | --- 38 | 39 | 40 | An overview of the Sandia National Laboratories Utilities GR Modules can be found in the README for https://github.com/sandialabs/gr-pdu_utils 41 | -------------------------------------------------------------------------------- /apps/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-fhss_utils 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | 8 | include(GrPython) 9 | 10 | GR_PYTHON_INSTALL( 11 | PROGRAMS 12 | DESTINATION bin 13 | ) 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmake/Modules/gnuradio-fhss_utilsConfig.cmake: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig) 2 | 3 | PKG_CHECK_MODULES(PC_GR_FHSS_UTILS gnuradio-fhss_utils) 4 | 5 | FIND_PATH( 6 | GR_FHSS_UTILS_INCLUDE_DIRS 7 | NAMES gnuradio/fhss_utils/api.h 8 | HINTS $ENV{FHSS_UTILS_DIR}/include 9 | ${PC_FHSS_UTILS_INCLUDEDIR} 10 | PATHS ${CMAKE_INSTALL_PREFIX}/include 11 | /usr/local/include 12 | /usr/include 13 | ) 14 | 15 | FIND_LIBRARY( 16 | GR_FHSS_UTILS_LIBRARIES 17 | NAMES gnuradio-fhss_utils 18 | HINTS $ENV{FHSS_UTILS_DIR}/lib 19 | ${PC_FHSS_UTILS_LIBDIR} 20 | PATHS ${CMAKE_INSTALL_PREFIX}/lib 21 | ${CMAKE_INSTALL_PREFIX}/lib64 22 | /usr/local/lib 23 | /usr/local/lib64 24 | /usr/lib 25 | /usr/lib64 26 | ) 27 | 28 | include("${CMAKE_CURRENT_LIST_DIR}/gnuradio-fhss_utilsTarget.cmake") 29 | 30 | INCLUDE(FindPackageHandleStandardArgs) 31 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(GR_FHSS_UTILS DEFAULT_MSG GR_FHSS_UTILS_LIBRARIES GR_FHSS_UTILS_INCLUDE_DIRS) 32 | MARK_AS_ADVANCED(GR_FHSS_UTILS_LIBRARIES GR_FHSS_UTILS_INCLUDE_DIRS) 33 | -------------------------------------------------------------------------------- /cmake/Modules/gnuradio-fhss_utilsConfigVersion.cmake.in: -------------------------------------------------------------------------------- 1 | # Copyright 2013 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 | set(MAJOR_VERSION @VERSION_MAJOR@) 21 | set(API_COMPAT @VERSION_API@) 22 | set(MINOR_VERSION @VERSION_ABI@) 23 | set(MAINT_VERSION @VERSION_PATCH@) 24 | 25 | set(PACKAGE_VERSION 26 | ${MAJOR_VERSION}.${API_COMPAT}.${MINOR_VERSION}.${MAINT_VERSION}) 27 | 28 | if(${PACKAGE_FIND_VERSION_MAJOR} EQUAL ${MAJOR_VERSION}) 29 | if(${PACKAGE_FIND_VERSION_MINOR} EQUAL ${API_COMPAT}) 30 | if(NOT ${PACKAGE_FIND_VERSION_PATCH} GREATER ${MINOR_VERSION}) 31 | set(PACKAGE_VERSION_EXACT 1) # exact match for API version 32 | set(PACKAGE_VERSION_COMPATIBLE 1) # compat for minor/patch version 33 | endif(NOT ${PACKAGE_FIND_VERSION_PATCH} GREATER ${MINOR_VERSION}) 34 | endif(${PACKAGE_FIND_VERSION_MINOR} EQUAL ${API_COMPAT}) 35 | endif(${PACKAGE_FIND_VERSION_MAJOR} EQUAL ${MAJOR_VERSION}) -------------------------------------------------------------------------------- /cmake/Modules/targetConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Free Software Foundation, Inc. 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | include(CMakeFindDependencyMacro) 6 | 7 | set(target_deps "@TARGET_DEPENDENCIES@") 8 | foreach(dep IN LISTS target_deps) 9 | find_dependency(${dep}) 10 | endforeach() 11 | include("${CMAKE_CURRENT_LIST_DIR}/@TARGET@Targets.cmake") 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-fhss_utils 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 | -------------------------------------------------------------------------------- /docs/README.fhss_utils: -------------------------------------------------------------------------------- 1 | This is the fhss_utils-write-a-block package meant as a guide to building 2 | out-of-tree packages. To use the fhss_utils blocks, the Python namespaces 3 | is in 'fhss_utils', which is imported as: 4 | 5 | import fhss_utils 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(fhss_utils) 12 | -------------------------------------------------------------------------------- /docs/burst_detector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/gr-fhss_utils/8993c6a89444e740b06ff4829b94555b04433bf7/docs/burst_detector.png -------------------------------------------------------------------------------- /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-fhss_utils 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 | -------------------------------------------------------------------------------- /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-fhss_utils 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/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-fhss_utils 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 | -------------------------------------------------------------------------------- /docs/doxygen/doxyxml/doxyindex.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-fhss_utils 6 | # 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | # 9 | # 10 | """ 11 | Classes providing more user-friendly interfaces to the doxygen xml 12 | docs than the generated classes provide. 13 | """ 14 | 15 | import os 16 | 17 | from .generated import index 18 | from .base import Base 19 | from .text import description 20 | 21 | class DoxyIndex(Base): 22 | """ 23 | Parses a doxygen xml directory. 24 | """ 25 | 26 | __module__ = "gnuradio.utils.doxyxml" 27 | 28 | def _parse(self): 29 | if self._parsed: 30 | return 31 | super(DoxyIndex, self)._parse() 32 | self._root = index.parse(os.path.join(self._xml_path, 'index.xml')) 33 | for mem in self._root.compound: 34 | converted = self.convert_mem(mem) 35 | # For files and namespaces we want the contents to be 36 | # accessible directly from the parent rather than having 37 | # to go through the file object. 38 | if self.get_cls(mem) == DoxyFile: 39 | if mem.name.endswith('.h'): 40 | self._members += converted.members() 41 | self._members.append(converted) 42 | elif self.get_cls(mem) == DoxyNamespace: 43 | self._members += converted.members() 44 | self._members.append(converted) 45 | else: 46 | self._members.append(converted) 47 | 48 | 49 | class DoxyCompMem(Base): 50 | 51 | 52 | kind = None 53 | 54 | def __init__(self, *args, **kwargs): 55 | super(DoxyCompMem, self).__init__(*args, **kwargs) 56 | 57 | @classmethod 58 | def can_parse(cls, obj): 59 | return obj.kind == cls.kind 60 | 61 | def set_descriptions(self, parse_data): 62 | bd = description(getattr(parse_data, 'briefdescription', None)) 63 | dd = description(getattr(parse_data, 'detaileddescription', None)) 64 | self._data['brief_description'] = bd 65 | self._data['detailed_description'] = dd 66 | 67 | def set_parameters(self, data): 68 | vs = [ddc.value for ddc in data.detaileddescription.content_] 69 | pls = [] 70 | for v in vs: 71 | if hasattr(v, 'parameterlist'): 72 | pls += v.parameterlist 73 | pis = [] 74 | for pl in pls: 75 | pis += pl.parameteritem 76 | dpis = [] 77 | for pi in pis: 78 | dpi = DoxyParameterItem(pi) 79 | dpi._parse() 80 | dpis.append(dpi) 81 | self._data['params'] = dpis 82 | 83 | 84 | class DoxyCompound(DoxyCompMem): 85 | pass 86 | 87 | class DoxyMember(DoxyCompMem): 88 | pass 89 | 90 | class DoxyFunction(DoxyMember): 91 | 92 | __module__ = "gnuradio.utils.doxyxml" 93 | 94 | kind = 'function' 95 | 96 | def _parse(self): 97 | if self._parsed: 98 | return 99 | super(DoxyFunction, self)._parse() 100 | self.set_descriptions(self._parse_data) 101 | self.set_parameters(self._parse_data) 102 | if not self._data['params']: 103 | # If the params weren't set by a comment then just grab the names. 104 | self._data['params'] = [] 105 | prms = self._parse_data.param 106 | for prm in prms: 107 | self._data['params'].append(DoxyParam(prm)) 108 | 109 | brief_description = property(lambda self: self.data()['brief_description']) 110 | detailed_description = property(lambda self: self.data()['detailed_description']) 111 | params = property(lambda self: self.data()['params']) 112 | 113 | Base.mem_classes.append(DoxyFunction) 114 | 115 | 116 | class DoxyParam(DoxyMember): 117 | 118 | __module__ = "gnuradio.utils.doxyxml" 119 | 120 | def _parse(self): 121 | if self._parsed: 122 | return 123 | super(DoxyParam, self)._parse() 124 | self.set_descriptions(self._parse_data) 125 | self._data['declname'] = self._parse_data.declname 126 | 127 | @property 128 | def description(self): 129 | descriptions = [] 130 | if self.brief_description: 131 | descriptions.append(self.brief_description) 132 | if self.detailed_description: 133 | descriptions.append(self.detailed_description) 134 | return '\n\n'.join(descriptions) 135 | 136 | brief_description = property(lambda self: self.data()['brief_description']) 137 | detailed_description = property(lambda self: self.data()['detailed_description']) 138 | name = property(lambda self: self.data()['declname']) 139 | 140 | class DoxyParameterItem(DoxyMember): 141 | """A different representation of a parameter in Doxygen.""" 142 | 143 | def _parse(self): 144 | if self._parsed: 145 | return 146 | super(DoxyParameterItem, self)._parse() 147 | names = [] 148 | for nl in self._parse_data.parameternamelist: 149 | for pn in nl.parametername: 150 | names.append(description(pn)) 151 | # Just take first name 152 | self._data['name'] = names[0] 153 | # Get description 154 | pd = description(self._parse_data.get_parameterdescription()) 155 | self._data['description'] = pd 156 | 157 | description = property(lambda self: self.data()['description']) 158 | name = property(lambda self: self.data()['name']) 159 | 160 | 161 | class DoxyClass(DoxyCompound): 162 | 163 | __module__ = "gnuradio.utils.doxyxml" 164 | 165 | kind = 'class' 166 | 167 | def _parse(self): 168 | if self._parsed: 169 | return 170 | super(DoxyClass, self)._parse() 171 | self.retrieve_data() 172 | if self._error: 173 | return 174 | self.set_descriptions(self._retrieved_data.compounddef) 175 | self.set_parameters(self._retrieved_data.compounddef) 176 | # Sectiondef.kind tells about whether private or public. 177 | # We just ignore this for now. 178 | self.process_memberdefs() 179 | 180 | brief_description = property(lambda self: self.data()['brief_description']) 181 | detailed_description = property(lambda self: self.data()['detailed_description']) 182 | params = property(lambda self: self.data()['params']) 183 | 184 | Base.mem_classes.append(DoxyClass) 185 | 186 | 187 | class DoxyFile(DoxyCompound): 188 | 189 | __module__ = "gnuradio.utils.doxyxml" 190 | 191 | kind = 'file' 192 | 193 | def _parse(self): 194 | if self._parsed: 195 | return 196 | super(DoxyFile, self)._parse() 197 | self.retrieve_data() 198 | self.set_descriptions(self._retrieved_data.compounddef) 199 | if self._error: 200 | return 201 | self.process_memberdefs() 202 | 203 | brief_description = property(lambda self: self.data()['brief_description']) 204 | detailed_description = property(lambda self: self.data()['detailed_description']) 205 | 206 | Base.mem_classes.append(DoxyFile) 207 | 208 | 209 | class DoxyNamespace(DoxyCompound): 210 | 211 | __module__ = "gnuradio.utils.doxyxml" 212 | 213 | kind = 'namespace' 214 | 215 | def _parse(self): 216 | if self._parsed: 217 | return 218 | super(DoxyNamespace, self)._parse() 219 | self.retrieve_data() 220 | self.set_descriptions(self._retrieved_data.compounddef) 221 | if self._error: 222 | return 223 | self.process_memberdefs() 224 | 225 | Base.mem_classes.append(DoxyNamespace) 226 | 227 | 228 | class DoxyGroup(DoxyCompound): 229 | 230 | __module__ = "gnuradio.utils.doxyxml" 231 | 232 | kind = 'group' 233 | 234 | def _parse(self): 235 | if self._parsed: 236 | return 237 | super(DoxyGroup, self)._parse() 238 | self.retrieve_data() 239 | if self._error: 240 | return 241 | cdef = self._retrieved_data.compounddef 242 | self._data['title'] = description(cdef.title) 243 | # Process inner groups 244 | grps = cdef.innergroup 245 | for grp in grps: 246 | converted = DoxyGroup.from_refid(grp.refid, top=self.top) 247 | self._members.append(converted) 248 | # Process inner classes 249 | klasses = cdef.innerclass 250 | for kls in klasses: 251 | converted = DoxyClass.from_refid(kls.refid, top=self.top) 252 | self._members.append(converted) 253 | # Process normal members 254 | self.process_memberdefs() 255 | 256 | title = property(lambda self: self.data()['title']) 257 | 258 | 259 | Base.mem_classes.append(DoxyGroup) 260 | 261 | 262 | class DoxyFriend(DoxyMember): 263 | 264 | __module__ = "gnuradio.utils.doxyxml" 265 | 266 | kind = 'friend' 267 | 268 | Base.mem_classes.append(DoxyFriend) 269 | 270 | 271 | class DoxyOther(Base): 272 | 273 | __module__ = "gnuradio.utils.doxyxml" 274 | 275 | kinds = set(['variable', 'struct', 'union', 'define', 'typedef', 'enum', 276 | 'dir', 'page', 'signal', 'slot', 'property']) 277 | 278 | @classmethod 279 | def can_parse(cls, obj): 280 | return obj.kind in cls.kinds 281 | 282 | Base.mem_classes.append(DoxyOther) 283 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-fhss_utils 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 | -------------------------------------------------------------------------------- /docs/doxygen/other/group_defs.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | * \defgroup block GNU Radio FHSS_UTILS C++ Signal Processing Blocks 3 | * \brief All C++ blocks that can be used from the FHSS_UTILS GNU Radio 4 | * module are listed here or in the subcategories below. 5 | * 6 | */ 7 | 8 | -------------------------------------------------------------------------------- /docs/doxygen/other/main_page.dox: -------------------------------------------------------------------------------- 1 | /*! \mainpage 2 | 3 | Welcome to the GNU Radio FHSS_UTILS Block 4 | 5 | This is the intro page for the Doxygen manual generated for the FHSS_UTILS 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 | -------------------------------------------------------------------------------- /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/doxygen/update_pydoc.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2010-2012 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 gnuradio 6 | # 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | # 9 | # 10 | """ 11 | Updates the *pydoc_h files for a module 12 | Execute using: python update_pydoc.py xml_path outputfilename 13 | 14 | The file instructs Pybind11 to transfer the doxygen comments into the 15 | python docstrings. 16 | 17 | """ 18 | 19 | import os, sys, time, glob, re, json 20 | from argparse import ArgumentParser 21 | 22 | from doxyxml import DoxyIndex, DoxyClass, DoxyFriend, DoxyFunction, DoxyFile 23 | from doxyxml import DoxyOther, base 24 | 25 | def py_name(name): 26 | bits = name.split('_') 27 | return '_'.join(bits[1:]) 28 | 29 | def make_name(name): 30 | bits = name.split('_') 31 | return bits[0] + '_make_' + '_'.join(bits[1:]) 32 | 33 | 34 | class Block(object): 35 | """ 36 | Checks if doxyxml produced objects correspond to a gnuradio block. 37 | """ 38 | 39 | @classmethod 40 | def includes(cls, item): 41 | if not isinstance(item, DoxyClass): 42 | return False 43 | # Check for a parsing error. 44 | if item.error(): 45 | return False 46 | friendname = make_name(item.name()) 47 | is_a_block = item.has_member(friendname, DoxyFriend) 48 | # But now sometimes the make function isn't a friend so check again. 49 | if not is_a_block: 50 | is_a_block = di.has_member(friendname, DoxyFunction) 51 | return is_a_block 52 | 53 | class Block2(object): 54 | """ 55 | Checks if doxyxml produced objects correspond to a new style 56 | gnuradio block. 57 | """ 58 | 59 | @classmethod 60 | def includes(cls, item): 61 | if not isinstance(item, DoxyClass): 62 | return False 63 | # Check for a parsing error. 64 | if item.error(): 65 | return False 66 | is_a_block2 = item.has_member('make', DoxyFunction) and item.has_member('sptr', DoxyOther) 67 | return is_a_block2 68 | 69 | 70 | def utoascii(text): 71 | """ 72 | Convert unicode text into ascii and escape quotes and backslashes. 73 | """ 74 | if text is None: 75 | return '' 76 | out = text.encode('ascii', 'replace') 77 | # swig will require us to replace blackslash with 4 backslashes 78 | # TODO: evaluate what this should be for pybind11 79 | out = out.replace(b'\\', b'\\\\\\\\') 80 | out = out.replace(b'"', b'\\"').decode('ascii') 81 | return str(out) 82 | 83 | 84 | def combine_descriptions(obj): 85 | """ 86 | Combines the brief and detailed descriptions of an object together. 87 | """ 88 | description = [] 89 | bd = obj.brief_description.strip() 90 | dd = obj.detailed_description.strip() 91 | if bd: 92 | description.append(bd) 93 | if dd: 94 | description.append(dd) 95 | return utoascii('\n\n'.join(description)).strip() 96 | 97 | def format_params(parameteritems): 98 | output = ['Args:'] 99 | template = ' {0} : {1}' 100 | for pi in parameteritems: 101 | output.append(template.format(pi.name, pi.description)) 102 | return '\n'.join(output) 103 | 104 | entry_templ = '%feature("docstring") {name} "{docstring}"' 105 | def make_entry(obj, name=None, templ="{description}", description=None, params=[]): 106 | """ 107 | Create a docstring key/value pair, where the key is the object name. 108 | 109 | obj - a doxyxml object from which documentation will be extracted. 110 | name - the name of the C object (defaults to obj.name()) 111 | templ - an optional template for the docstring containing only one 112 | variable named 'description'. 113 | description - if this optional variable is set then it's value is 114 | used as the description instead of extracting it from obj. 115 | """ 116 | if name is None: 117 | name=obj.name() 118 | if hasattr(obj,'_parse_data') and hasattr(obj._parse_data,'definition'): 119 | name=obj._parse_data.definition.split(' ')[-1] 120 | if "operator " in name: 121 | return '' 122 | if description is None: 123 | description = combine_descriptions(obj) 124 | if params: 125 | description += '\n\n' 126 | description += utoascii(format_params(params)) 127 | docstring = templ.format(description=description) 128 | 129 | return {name: docstring} 130 | 131 | 132 | def make_class_entry(klass, description=None, ignored_methods=[], params=None): 133 | """ 134 | Create a class docstring key/value pair. 135 | """ 136 | if params is None: 137 | params = klass.params 138 | output = {} 139 | output.update(make_entry(klass, description=description, params=params)) 140 | for func in klass.in_category(DoxyFunction): 141 | if func.name() not in ignored_methods: 142 | name = klass.name() + '::' + func.name() 143 | output.update(make_entry(func, name=name)) 144 | return output 145 | 146 | 147 | def make_block_entry(di, block): 148 | """ 149 | Create class and function docstrings of a gnuradio block 150 | """ 151 | descriptions = [] 152 | # Get the documentation associated with the class. 153 | class_desc = combine_descriptions(block) 154 | if class_desc: 155 | descriptions.append(class_desc) 156 | # Get the documentation associated with the make function 157 | make_func = di.get_member(make_name(block.name()), DoxyFunction) 158 | make_func_desc = combine_descriptions(make_func) 159 | if make_func_desc: 160 | descriptions.append(make_func_desc) 161 | # Get the documentation associated with the file 162 | try: 163 | block_file = di.get_member(block.name() + ".h", DoxyFile) 164 | file_desc = combine_descriptions(block_file) 165 | if file_desc: 166 | descriptions.append(file_desc) 167 | except base.Base.NoSuchMember: 168 | # Don't worry if we can't find a matching file. 169 | pass 170 | # And join them all together to make a super duper description. 171 | super_description = "\n\n".join(descriptions) 172 | # Associate the combined description with the class and 173 | # the make function. 174 | output = {} 175 | output.update(make_class_entry(block, description=super_description)) 176 | output.update(make_entry(make_func, description=super_description, 177 | params=block.params)) 178 | return output 179 | 180 | def make_block2_entry(di, block): 181 | """ 182 | Create class and function docstrings of a new style gnuradio block 183 | """ 184 | # For new style blocks all the relevant documentation should be 185 | # associated with the 'make' method. 186 | class_description = combine_descriptions(block) 187 | make_func = block.get_member('make', DoxyFunction) 188 | make_description = combine_descriptions(make_func) 189 | description = class_description + "\n\nConstructor Specific Documentation:\n\n" + make_description 190 | # Associate the combined description with the class and 191 | # the make function. 192 | output = {} 193 | output.update(make_class_entry( 194 | block, description=description, 195 | ignored_methods=['make'], params=make_func.params)) 196 | makename = block.name() + '::make' 197 | output.update(make_entry( 198 | make_func, name=makename, description=description, 199 | params=make_func.params)) 200 | return output 201 | 202 | def get_docstrings_dict(di, custom_output=None): 203 | 204 | output = {} 205 | if custom_output: 206 | output.update(custom_output) 207 | 208 | # Create docstrings for the blocks. 209 | blocks = di.in_category(Block) 210 | blocks2 = di.in_category(Block2) 211 | 212 | make_funcs = set([]) 213 | for block in blocks: 214 | try: 215 | make_func = di.get_member(make_name(block.name()), DoxyFunction) 216 | # Don't want to risk writing to output twice. 217 | if make_func.name() not in make_funcs: 218 | make_funcs.add(make_func.name()) 219 | output.update(make_block_entry(di, block)) 220 | except block.ParsingError: 221 | sys.stderr.write('Parsing error for block {0}\n'.format(block.name())) 222 | raise 223 | 224 | for block in blocks2: 225 | try: 226 | make_func = block.get_member('make', DoxyFunction) 227 | make_func_name = block.name() +'::make' 228 | # Don't want to risk writing to output twice. 229 | if make_func_name not in make_funcs: 230 | make_funcs.add(make_func_name) 231 | output.update(make_block2_entry(di, block)) 232 | except block.ParsingError: 233 | sys.stderr.write('Parsing error for block {0}\n'.format(block.name())) 234 | raise 235 | 236 | # Create docstrings for functions 237 | # Don't include the make functions since they have already been dealt with. 238 | funcs = [f for f in di.in_category(DoxyFunction) 239 | if f.name() not in make_funcs and not f.name().startswith('std::')] 240 | for f in funcs: 241 | try: 242 | output.update(make_entry(f)) 243 | except f.ParsingError: 244 | sys.stderr.write('Parsing error for function {0}\n'.format(f.name())) 245 | 246 | # Create docstrings for classes 247 | block_names = [block.name() for block in blocks] 248 | block_names += [block.name() for block in blocks2] 249 | klasses = [k for k in di.in_category(DoxyClass) 250 | if k.name() not in block_names and not k.name().startswith('std::')] 251 | for k in klasses: 252 | try: 253 | output.update(make_class_entry(k)) 254 | except k.ParsingError: 255 | sys.stderr.write('Parsing error for class {0}\n'.format(k.name())) 256 | 257 | # Docstrings are not created for anything that is not a function or a class. 258 | # If this excludes anything important please add it here. 259 | 260 | return output 261 | 262 | def sub_docstring_in_pydoc_h(pydoc_files, docstrings_dict, output_dir, filter_str=None): 263 | if filter_str: 264 | docstrings_dict = {k: v for k, v in docstrings_dict.items() if k.startswith(filter_str)} 265 | 266 | with open(os.path.join(output_dir,'docstring_status'),'w') as status_file: 267 | 268 | for pydoc_file in pydoc_files: 269 | if filter_str: 270 | filter_str2 = "::".join((filter_str,os.path.split(pydoc_file)[-1].split('_pydoc_template.h')[0])) 271 | docstrings_dict2 = {k: v for k, v in docstrings_dict.items() if k.startswith(filter_str2)} 272 | else: 273 | docstrings_dict2 = docstrings_dict 274 | 275 | 276 | 277 | file_in = open(pydoc_file,'r').read() 278 | for key, value in docstrings_dict2.items(): 279 | file_in_tmp = file_in 280 | try: 281 | doc_key = key.split("::") 282 | # if 'gr' in doc_key: 283 | # doc_key.remove('gr') 284 | doc_key = '_'.join(doc_key) 285 | regexp = r'(__doc_{} =\sR\"doc\()[^)]*(\)doc\")'.format(doc_key) 286 | regexp = re.compile(regexp, re.MULTILINE) 287 | 288 | (file_in, nsubs) = regexp.subn(r'\1'+value+r'\2', file_in, count=1) 289 | if nsubs == 1: 290 | status_file.write("PASS: " + pydoc_file + "\n") 291 | except KeyboardInterrupt: 292 | raise KeyboardInterrupt 293 | except: # be permissive, TODO log, but just leave the docstring blank 294 | status_file.write("FAIL: " + pydoc_file + "\n") 295 | file_in = file_in_tmp 296 | 297 | output_pathname = os.path.join(output_dir, os.path.basename(pydoc_file).replace('_template.h','.h')) 298 | # FIXME: Remove this debug print 299 | print('output docstrings to {}'.format(output_pathname)) 300 | with open(output_pathname,'w') as file_out: 301 | file_out.write(file_in) 302 | 303 | def copy_docstring_templates(pydoc_files, output_dir): 304 | with open(os.path.join(output_dir,'docstring_status'),'w') as status_file: 305 | for pydoc_file in pydoc_files: 306 | file_in = open(pydoc_file,'r').read() 307 | output_pathname = os.path.join(output_dir, os.path.basename(pydoc_file).replace('_template.h','.h')) 308 | # FIXME: Remove this debug print 309 | print('copy docstrings to {}'.format(output_pathname)) 310 | with open(output_pathname,'w') as file_out: 311 | file_out.write(file_in) 312 | status_file.write("DONE") 313 | 314 | def argParse(): 315 | """Parses commandline args.""" 316 | desc='Scrape the doxygen generated xml for docstrings to insert into python bindings' 317 | parser = ArgumentParser(description=desc) 318 | 319 | parser.add_argument("function", help="Operation to perform on docstrings", choices=["scrape","sub","copy"]) 320 | 321 | parser.add_argument("--xml_path") 322 | parser.add_argument("--bindings_dir") 323 | parser.add_argument("--output_dir") 324 | parser.add_argument("--json_path") 325 | parser.add_argument("--filter", default=None) 326 | 327 | return parser.parse_args() 328 | 329 | if __name__ == "__main__": 330 | # Parse command line options and set up doxyxml. 331 | args = argParse() 332 | if args.function.lower() == 'scrape': 333 | di = DoxyIndex(args.xml_path) 334 | docstrings_dict = get_docstrings_dict(di) 335 | with open(args.json_path, 'w') as fp: 336 | json.dump(docstrings_dict, fp) 337 | elif args.function.lower() == 'sub': 338 | with open(args.json_path, 'r') as fp: 339 | docstrings_dict = json.load(fp) 340 | pydoc_files = glob.glob(os.path.join(args.bindings_dir,'*_pydoc_template.h')) 341 | sub_docstring_in_pydoc_h(pydoc_files, docstrings_dict, args.output_dir, args.filter) 342 | elif args.function.lower() == 'copy': 343 | pydoc_files = glob.glob(os.path.join(args.bindings_dir,'*_pydoc_template.h')) 344 | copy_docstring_templates(pydoc_files, args.output_dir) 345 | 346 | 347 | -------------------------------------------------------------------------------- /docs/fhss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/gr-fhss_utils/8993c6a89444e740b06ff4829b94555b04433bf7/docs/fhss.jpg -------------------------------------------------------------------------------- /docs/figures/burst_detector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/gr-fhss_utils/8993c6a89444e740b06ff4829b94555b04433bf7/docs/figures/burst_detector.png -------------------------------------------------------------------------------- /docs/figures/middle_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/gr-fhss_utils/8993c6a89444e740b06ff4829b94555b04433bf7/docs/figures/middle_out.png -------------------------------------------------------------------------------- /docs/figures/snl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/gr-fhss_utils/8993c6a89444e740b06ff4829b94555b04433bf7/docs/figures/snl.png -------------------------------------------------------------------------------- /examples/README: -------------------------------------------------------------------------------- 1 | This module requires the installation of gr-pdu_utils. 2 | -------------------------------------------------------------------------------- /gnuradio-fhss_utils.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | includedir=@includedir@ 5 | 6 | Name: gr-fhss_utils 7 | Description: GNURadio OOT toolset for Frequency Hopping signals 8 | Requires: gnuradio-runtime 9 | Version: @LIBVER@ 10 | Libs: -L${libdir} 11 | Cflags: -I${includedir} 12 | -------------------------------------------------------------------------------- /grc/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-fhss_utils 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | install(FILES 8 | fhss_utils_cf_estimate.block.yml 9 | fhss_utils_fft_burst_tagger.block.yml 10 | fhss_utils_tagged_burst_to_pdu.block.yml 11 | fhss_utils_coarse_dehopper.block.yml 12 | fhss_utils_fft_peak.block.yml 13 | fhss_utils_fine_dehopper.block.yml 14 | fhss_utils_fsk_burst_extractor_hier.block.yml 15 | fhss_utils_s_and_h_detector.block.yml 16 | fhss_utils_burst_tag_debug.block.yml 17 | fhss_utils_sigmf_meta_writer.block.yml DESTINATION share/gnuradio/grc/blocks 18 | ) 19 | -------------------------------------------------------------------------------- /grc/fhss_utils_burst_tag_debug.block.yml: -------------------------------------------------------------------------------- 1 | id: fhss_utils_burst_tag_debug 2 | label: Burst Tagger Debug 3 | category: '[Sandia]/FHSS Utilities' 4 | 5 | templates: 6 | imports: from gnuradio import fhss_utils 7 | make: fhss_utils.burst_tag_debug(${filename}, ${nrows}, ${fft_size}, ${nrows_avg}, ${sample_rate}, ${quality}, ${always_on}) 8 | 9 | parameters: 10 | - id: filename 11 | label: Filename 12 | dtype: string 13 | default: "/tmp/btd" 14 | 15 | - id: nrows 16 | label: NRows per Image 17 | dtype: int 18 | default: "5000" 19 | 20 | - id: fft_size 21 | label: FFT Size 22 | dtype: int 23 | default: "1024" 24 | 25 | - id: nrows_avg 26 | label: NFFTs Avg'ed 27 | dtype: int 28 | default: "1" 29 | 30 | - id: sample_rate 31 | label: Sample Rate 32 | dtype: float 33 | default: "samp_rate" 34 | 35 | - id: quality 36 | label: JPEG Quality 37 | dtype: int 38 | default: "75" 39 | 40 | - id: always_on 41 | label: Always On 42 | dtype: int 43 | default: "False" 44 | 45 | inputs: 46 | - domain: message 47 | id: "fft_in" 48 | optional: true 49 | 50 | - domain: message 51 | id: "burst_in_r" 52 | optional: true 53 | 54 | - domain: message 55 | id: "burst_in_g" 56 | optional: true 57 | 58 | - domain: message 59 | id: "burst_in_b" 60 | optional: true 61 | 62 | outputs: 63 | - domain: message 64 | id: "pdu_out" 65 | optional: true 66 | 67 | file_format: 1 68 | -------------------------------------------------------------------------------- /grc/fhss_utils_cf_estimate.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: fhss_utils_cf_estimate 4 | label: Center Freq Estimation 5 | category: '[Sandia]/FHSS Utilities' 6 | 7 | parameters: 8 | - id: method 9 | label: Method 10 | dtype: enum 11 | default: fhss_utils.RMS 12 | options: [fhss_utils.RMS, fhss_utils.HALF_POWER, fhss_utils.MIDDLE_OUT, fhss_utils.COERCE] 13 | option_labels: [RMS, Half-Power, Middle Out, Coerce Only] 14 | - id: channel_freqs 15 | label: Channel Center Frequencies 16 | dtype: real_vector 17 | default: '[]' 18 | hide: part 19 | - id: snr_min 20 | label: Minimum SNR 21 | dtype: float 22 | default: 10.0 23 | hide: ${ ('none' if (method == "fhss_utils.MIDDLE_OUT") else 'all') } 24 | - id: thresh_min 25 | label: Min Threshold Rel. Peak 26 | dtype: float 27 | default: -25.0 28 | hide: ${ ('part' if (method == "fhss_utils.MIDDLE_OUT") else 'all') } 29 | 30 | inputs: 31 | - domain: message 32 | id: in 33 | optional: true 34 | 35 | outputs: 36 | - domain: message 37 | id: out 38 | optional: true 39 | - domain: message 40 | id: debug 41 | optional: true 42 | 43 | templates: 44 | imports: from gnuradio import fhss_utils 45 | make: |- 46 | fhss_utils.cf_estimate(${method}, ${channel_freqs}) 47 | % if method == "fhss_utils.MIDDLE_OUT": 48 | self.${id}.set_snr_min(${snr_min}) 49 | self.${id}.set_thresh_min(${thresh_min}) 50 | % endif 51 | callbacks: 52 | - set_freqs(${channel_freqs}) 53 | - set_method(${method}) 54 | - set_snr_min(${snr_min}) 55 | - set_thresh_min(${thresh_min}) 56 | 57 | file_format: 1 58 | -------------------------------------------------------------------------------- /grc/fhss_utils_coarse_dehopper.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: fhss_utils_coarse_dehopper 4 | label: Coarse Dehopper 5 | category: '[Sandia]/FHSS Utilities' 6 | 7 | parameters: 8 | - id: fft_len 9 | label: FFT Length 10 | dtype: raw 11 | default: '32' 12 | hide: none 13 | - id: freq_sample_delay_samps 14 | label: Freq S&H Delay 15 | dtype: raw 16 | default: '128' 17 | hide: none 18 | - id: freq_samps_to_avg 19 | label: Freq Avg Samples 20 | dtype: raw 21 | default: '128' 22 | hide: none 23 | - id: mag_samps_to_avg 24 | label: Mag Avg Samples 25 | dtype: raw 26 | default: '128' 27 | hide: none 28 | - id: thresh 29 | label: Threshold 30 | dtype: raw 31 | default: '0.005' 32 | hide: none 33 | 34 | inputs: 35 | - domain: stream 36 | dtype: complex 37 | vlen: 1 38 | 39 | outputs: 40 | - domain: stream 41 | dtype: complex 42 | vlen: 1 43 | 44 | templates: 45 | imports: from gnuradio import fhss_utils 46 | make: fhss_utils.coarse_dehopper(${fft_len}, ${freq_sample_delay_samps}, ${freq_samps_to_avg}, 47 | ${mag_samps_to_avg}, ${thresh}) 48 | callbacks: 49 | - set_fft_len(${fft_len}) 50 | - set_freq_sample_delay_samps(${freq_sample_delay_samps}) 51 | - set_freq_samps_to_avg(${freq_samps_to_avg}) 52 | - set_mag_samps_to_avg(${mag_samps_to_avg}) 53 | - set_thresh(${thresh}) 54 | 55 | file_format: 1 56 | -------------------------------------------------------------------------------- /grc/fhss_utils_fft_burst_tagger.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: fhss_utils_fft_burst_tagger 4 | label: FFT Burst Tagger 5 | category: '[Sandia]/FHSS Utilities' 6 | 7 | parameters: 8 | # "GENERAL" tab: 9 | - id: center_frequency 10 | label: Center Frequency 11 | dtype: float 12 | default: '1626000000' 13 | - id: sample_rate 14 | label: Sample Rate 15 | dtype: int 16 | - id: fft_size 17 | label: FFT Size 18 | dtype: int 19 | default: '1024' 20 | - id: threshold 21 | label: Threshold 22 | dtype: float 23 | default: '7' 24 | - id: debug 25 | label: Debug Output 26 | dtype: bool 27 | default: 'False' 28 | # "BURST CONFIG" tab: 29 | - id: max_bursts 30 | label: Max Simultaneous Bursts 31 | category: Burst Config 32 | dtype: int 33 | default: '0' 34 | hide: part 35 | - id: max_burst_len 36 | label: Max Length (FFTs) 37 | category: Burst Config 38 | dtype: int 39 | default: '0' 40 | hide: part 41 | - id: burst_pre_len 42 | label: Added Pre Burst Length (FFTs) 43 | category: Burst Config 44 | dtype: int 45 | default: '5' 46 | hide: part 47 | - id: burst_post_len 48 | label: Added Post Burst Length (FFTs) 49 | category: Burst Config 50 | dtype: int 51 | default: '20' 52 | hide: part 53 | - id: lookahead 54 | label: Burst Settling Time (FFTs) 55 | category: Burst Config 56 | dtype: int 57 | default: '10' 58 | hide: part 59 | - id: burst_width 60 | label: Burst Width (Hz) 61 | category: Burst Config 62 | dtype: int 63 | default: '500000' 64 | hide: part 65 | - id: history_size 66 | label: Noise History Length (FFTs) 67 | category: Burst Config 68 | dtype: int 69 | default: '64' 70 | hide: part 71 | # "ADVANCED" tab: 72 | - id: preload_nf_bool 73 | label: Preload Noise Floor? 74 | category: Advanced 75 | dtype: bool 76 | hide: part 77 | default: 'False' 78 | 79 | - id: preload_nf 80 | label: Noise Floor Preload (dB) 81 | category: Advanced 82 | dtype: float 83 | hide: ${ ('part' if (preload_nf_bool) else 'all') } 84 | default: '0' 85 | 86 | inputs: 87 | - domain: stream 88 | dtype: complex 89 | 90 | outputs: 91 | - domain: stream 92 | dtype: complex 93 | - domain: message 94 | id: debug 95 | optional: true 96 | 97 | templates: 98 | imports: from gnuradio import fhss_utils 99 | make: |- 100 | fhss_utils.fft_burst_tagger(${center_frequency}, ${fft_size}, ${sample_rate}, ${burst_pre_len}, ${burst_post_len}, ${burst_width}, ${max_bursts}, ${max_burst_len}, ${threshold}, ${history_size}, ${lookahead}, ${debug}) 101 | % if preload_nf_bool: 102 | self.${id}.preload_noise_floor(${preload_nf}, bool(${preload_nf})) 103 | % endif 104 | 105 | file_format: 1 106 | -------------------------------------------------------------------------------- /grc/fhss_utils_fft_peak.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: fhss_utils_fft_peak 4 | label: FFT Peak 5 | category: '[Sandia]/FHSS Utilities' 6 | 7 | parameters: 8 | - id: fft_len 9 | label: FFT Length 10 | dtype: raw 11 | default: '32' 12 | hide: none 13 | 14 | inputs: 15 | - domain: stream 16 | dtype: complex 17 | vlen: 1 18 | 19 | outputs: 20 | - label: max_i 21 | domain: stream 22 | dtype: float 23 | vlen: 1 24 | - label: fft 25 | domain: stream 26 | dtype: float 27 | vlen: 1 28 | optional: true 29 | 30 | templates: 31 | imports: from gnuradio import fhss_utils 32 | make: fhss_utils.fft_peak(${fft_len}) 33 | callbacks: 34 | - set_fft_len(${fft_len}) 35 | 36 | file_format: 1 37 | -------------------------------------------------------------------------------- /grc/fhss_utils_fine_dehopper.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: fhss_utils_fine_dehopper 4 | label: Fine Dehopper 5 | category: '[Sandia]/FHSS Utilities' 6 | 7 | parameters: 8 | - id: bias 9 | label: Bit Bias 10 | dtype: raw 11 | default: '0' 12 | hide: none 13 | - id: freq_sample_delay_samps 14 | label: Freq S&H Delay 15 | dtype: raw 16 | default: '128' 17 | hide: none 18 | - id: freq_samps_to_avg 19 | label: Freq Avg Samples 20 | dtype: raw 21 | default: '128' 22 | hide: none 23 | - id: mag_samps_to_avg 24 | label: Mag Avg Samples 25 | dtype: raw 26 | default: '128' 27 | hide: none 28 | - id: resamp_rate 29 | label: Resampler Rate 30 | dtype: raw 31 | default: '.125' 32 | hide: none 33 | - id: thresh 34 | label: Threshold 35 | dtype: raw 36 | default: '0.005' 37 | hide: none 38 | 39 | inputs: 40 | - domain: stream 41 | dtype: complex 42 | vlen: 1 43 | 44 | outputs: 45 | - domain: stream 46 | dtype: complex 47 | vlen: 1 48 | 49 | templates: 50 | imports: from gnuradio import fhss_utils 51 | make: fhss_utils.fine_dehopper(${bias}, ${freq_sample_delay_samps}, ${freq_samps_to_avg}, 52 | ${mag_samps_to_avg}, ${resamp_rate}, ${thresh}) 53 | callbacks: 54 | - set_bias(${bias}) 55 | - set_freq_sample_delay_samps(${freq_sample_delay_samps}) 56 | - set_freq_samps_to_avg(${freq_samps_to_avg}) 57 | - set_mag_samps_to_avg(${mag_samps_to_avg}) 58 | - set_resamp_rate(${resamp_rate}) 59 | - set_thresh(${thresh}) 60 | 61 | file_format: 1 62 | -------------------------------------------------------------------------------- /grc/fhss_utils_fsk_burst_extractor_hier.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: fsk_burst_extractor_hier 4 | label: Burst Extractor Hier 5 | category: '[Sandia]/FHSS Utilities' 6 | 7 | parameters: 8 | - id: samp_rate 9 | label: Sample Rate [Hz] 10 | dtype: float 11 | default: samp_rate 12 | hide: none 13 | - id: center_freq 14 | label: Center Frequency [Hz] 15 | dtype: float 16 | default: center_freq 17 | hide: none 18 | - id: fft_size 19 | label: FFT Size 20 | dtype: int 21 | default: '256' 22 | hide: none 23 | 24 | - id: threshold 25 | label: Threshold [dB] 26 | category: Burst Bounds 27 | dtype: real 28 | default: '6' 29 | hide: none 30 | - id: burst_width 31 | label: Burst Width [Hz] 32 | category: Burst Bounds 33 | dtype: int 34 | default: int(500e3) 35 | hide: part 36 | - id: hist_time 37 | label: History Time [s] 38 | category: Burst Bounds 39 | dtype: real 40 | default: '0.004' 41 | hide: part 42 | - id: lookahead_time 43 | label: Lookahead Time [s] 44 | category: Burst Bounds 45 | dtype: real 46 | default: '0.0005' 47 | hide: part 48 | - id: max_burst_time 49 | label: Max Burst Time [s] 50 | category: Burst Bounds 51 | dtype: real 52 | default: '0.5' 53 | hide: part 54 | - id: min_burst_time 55 | label: Min Burst Time [s] 56 | category: Burst Bounds 57 | dtype: real 58 | default: '0.001' 59 | hide: part 60 | - id: pre_burst_time 61 | label: Pre Burst Time [s] 62 | category: Burst Bounds 63 | dtype: real 64 | default: '0.00008' 65 | hide: part 66 | - id: post_burst_time 67 | label: Post Burst Time [s] 68 | category: Burst Bounds 69 | dtype: real 70 | default: '0.00008' 71 | hide: part 72 | 73 | - id: decimation 74 | label: Decimation 75 | category: Filter/Decimate 76 | dtype: int 77 | default: '20' 78 | hide: none 79 | - id: n_threads 80 | label: N Threads Downmix 81 | category: Filter/Decimate 82 | dtype: int 83 | default: '3' 84 | hide: part 85 | - id: output_cutoff 86 | label: Cutoff [cycles/samp] 87 | category: Filter/Decimate 88 | dtype: real 89 | default: '0.25' 90 | hide: part 91 | - id: output_trans_width 92 | label: Trans Width [cycles/samp] 93 | category: Filter/Decimate 94 | dtype: real 95 | default: '0.1' 96 | hide: part 97 | - id: output_attenuation 98 | label: Output Attenuation 99 | category: Filter/Decimate 100 | dtype: real 101 | default: '40' 102 | hide: part 103 | 104 | - id: cf_method 105 | label: Method 106 | category: Freq. Est. 107 | dtype: enum 108 | default: fhss_utils.RMS 109 | options: [fhss_utils.RMS, fhss_utils.HALF_POWER, fhss_utils.COERCE, fhss_utils.MIDDLE_OUT] 110 | option_labels: [RMS, Half-Power, Coerce Only, Middle Out] 111 | hide: part 112 | - id: channel_freqs 113 | label: Channel Center Freqs 114 | category: Freq. Est. 115 | dtype: real_vector 116 | default: '[]' 117 | hide: part 118 | 119 | - id: snr_min 120 | label: Minimum SNR 121 | category: Freq. Est. 122 | dtype: float 123 | default: 10.0 124 | hide: ${ ('none' if (cf_method == "fhss_utils.MIDDLE_OUT") else 'all') } 125 | - id: thresh_min 126 | label: Min Threshold Rel. Peak 127 | category: Freq. Est. 128 | dtype: float 129 | default: -25.0 130 | hide: ${ ('part' if (cf_method == "fhss_utils.MIDDLE_OUT") else 'all') } 131 | 132 | 133 | inputs: 134 | - domain: stream 135 | dtype: complex 136 | vlen: 1 137 | 138 | outputs: 139 | - domain: message 140 | id: pdu_out 141 | 142 | asserts: 143 | - ${ decimation % 2 == 0 } 144 | 145 | templates: 146 | imports: from gnuradio import fhss_utils 147 | make: |- 148 | fhss_utils.fsk_burst_extractor_hier( 149 | burst_width=${burst_width}, 150 | center_freq=${center_freq}, 151 | decimation=${decimation}, 152 | fft_size=${fft_size}, 153 | hist_time=${hist_time}, 154 | lookahead_time=${lookahead_time}, 155 | max_burst_time=${max_burst_time}, 156 | min_burst_time=${min_burst_time}, 157 | output_attenuation=${output_attenuation}, 158 | output_cutoff=${output_cutoff}, 159 | output_trans_width=${output_trans_width}, 160 | post_burst_time=${post_burst_time}, 161 | pre_burst_time=${pre_burst_time}, 162 | samp_rate=int(${samp_rate}), 163 | threshold=${threshold}, 164 | cf_method=${cf_method}, 165 | channel_freqs=${channel_freqs}, 166 | n_threads=${n_threads} 167 | ) 168 | % if cf_method == "fhss_utils.MIDDLE_OUT": 169 | self.${id}.cf_estimate.set_snr_min(${snr_min}) 170 | self.${id}.cf_estimate.set_thresh_min(${thresh_min}) 171 | % endif 172 | callbacks: 173 | - set_channel_freqs(${channel_freqs}) 174 | - set_cf_method(${cf_method}) 175 | - set_snr_min(${snr_min}) 176 | - set_thresh_min(${thresh_min}) 177 | - set_threshold(${threshold}) 178 | 179 | file_format: 1 180 | -------------------------------------------------------------------------------- /grc/fhss_utils_s_and_h_detector.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: fhss_utils_s_and_h_detector 4 | label: Sample and Hold Detector 5 | category: '[Sandia]/FHSS Utilities' 6 | 7 | parameters: 8 | - id: freq_sample_delay_samps 9 | label: Freq S&H Delay 10 | dtype: raw 11 | default: '128' 12 | hide: none 13 | - id: freq_samps_to_avg 14 | label: Freq Avg Samples 15 | dtype: raw 16 | default: '128' 17 | hide: none 18 | - id: mag_samps_to_avg 19 | label: Mag Avg Samples 20 | dtype: raw 21 | default: '128' 22 | hide: none 23 | - id: thresh 24 | label: Threshold 25 | dtype: raw 26 | default: '0.005' 27 | hide: none 28 | 29 | inputs: 30 | - label: mag 31 | domain: stream 32 | dtype: float 33 | vlen: 1 34 | - label: freq 35 | domain: stream 36 | dtype: float 37 | vlen: 1 38 | 39 | outputs: 40 | - domain: stream 41 | dtype: float 42 | vlen: 1 43 | - label: inst_f 44 | domain: stream 45 | dtype: float 46 | vlen: 1 47 | optional: true 48 | - label: m_t 49 | domain: stream 50 | dtype: float 51 | vlen: 1 52 | optional: true 53 | - label: m_avg 54 | domain: stream 55 | dtype: float 56 | vlen: 1 57 | optional: true 58 | 59 | templates: 60 | imports: from gnuradio import fhss_utils 61 | make: fhss_utils.s_and_h_detector(${freq_sample_delay_samps}, ${freq_samps_to_avg}, 62 | ${mag_samps_to_avg}, ${thresh}) 63 | callbacks: 64 | - set_freq_sample_delay_samps(${freq_sample_delay_samps}) 65 | - set_freq_samps_to_avg(${freq_samps_to_avg}) 66 | - set_mag_samps_to_avg(${mag_samps_to_avg}) 67 | - set_thresh(${thresh}) 68 | 69 | file_format: 1 70 | -------------------------------------------------------------------------------- /grc/fhss_utils_sigmf_meta_writer.block.yml: -------------------------------------------------------------------------------- 1 | id: fhss_utils_sigmf_meta_writer 2 | label: SigMF-meta Writer 3 | category: '[Sandia]/FHSS Utilities' 4 | 5 | templates: 6 | imports: from gnuradio import fhss_utils 7 | make: fhss_utils.sigmf_meta_writer(${filename}, ${freq}, ${rate}, ${label}, ${dtype}) 8 | 9 | parameters: 10 | # "GENERAL" tab: 11 | - id: filename 12 | label: Filename 13 | dtype: file_save 14 | - id: freq 15 | label: Frequency (Hz) 16 | dtype: float 17 | - id: rate 18 | label: Samp Rate (Hz) 19 | dtype: int 20 | - id: label 21 | label: Annotation Label 22 | dtype: string 23 | default: use_burst_id 24 | options: ['use_burst_id', 'use_snr_db'] 25 | option_labels: [Use Burst ID, Use SNR] 26 | - id: dtype 27 | label: SigMF Data Type 28 | dtype: string 29 | default: 'ci16_le' 30 | options: ['ci16_le', 'cf32_le'] 31 | option_labels: [Short, Float] 32 | hide: part 33 | 34 | inputs: 35 | - domain: message 36 | id: in 37 | 38 | file_format: 1 39 | -------------------------------------------------------------------------------- /grc/fhss_utils_tagged_burst_to_pdu.block.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by grc.converter 2 | 3 | id: fhss_utils_tagged_burst_to_pdu 4 | label: Tagged Burst to PDU 5 | category: '[Sandia]/FHSS Utilities' 6 | 7 | parameters: 8 | - id: decimation 9 | label: Decimation 10 | dtype: int 11 | default: '16' 12 | - id: taps 13 | label: Filter Taps 14 | dtype: float_vector 15 | - id: min_burst_time 16 | label: Min Burst Time 17 | dtype: float 18 | default: '.003' 19 | - id: max_burst_time 20 | label: Max Burst Time 21 | dtype: float 22 | default: '.5' 23 | - id: relative_center_frequency 24 | label: Relative Center Frequency 25 | dtype: float 26 | default: '0.0' 27 | - id: relative_span 28 | label: Relative Span 29 | dtype: float 30 | default: '1.0' 31 | - id: relative_sample_rate 32 | label: Relative Sample Rate 33 | dtype: float 34 | default: '1.0' 35 | - id: sample_rate 36 | label: Sample Rate 37 | dtype: float 38 | default: 16e6 39 | - id: threads 40 | label: Num Threads 41 | dtype: int 42 | default: '3' 43 | hide: part 44 | 45 | inputs: 46 | - domain: stream 47 | dtype: complex 48 | 49 | outputs: 50 | - domain: message 51 | id: cpdus 52 | 53 | templates: 54 | imports: from gnuradio import fhss_utils 55 | make: fhss_utils.tagged_burst_to_pdu(${decimation}, ${taps}, ${min_burst_time}, 56 | ${max_burst_time}, ${relative_center_frequency}, ${relative_span}, ${relative_sample_rate}, 57 | ${sample_rate}, ${threads}) 58 | 59 | file_format: 1 60 | -------------------------------------------------------------------------------- /include/gnuradio/fhss_utils/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-fhss_utils 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 | cf_estimate.h 15 | fft_burst_tagger.h 16 | tagged_burst_to_pdu.h 17 | constants.h DESTINATION include/gnuradio/fhss_utils 18 | ) 19 | -------------------------------------------------------------------------------- /include/gnuradio/fhss_utils/api.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 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-fhss_utils 6 | * 7 | * SPDX-License-Identifier: GPL-3.0-or-later 8 | * 9 | */ 10 | 11 | #ifndef INCLUDED_FHSS_UTILS_API_H 12 | #define INCLUDED_FHSS_UTILS_API_H 13 | 14 | #include 15 | 16 | #ifdef gnuradio_fhss_utils_EXPORTS 17 | #define FHSS_UTILS_API __GR_ATTR_EXPORT 18 | #else 19 | #define FHSS_UTILS_API __GR_ATTR_IMPORT 20 | #endif 21 | 22 | #endif /* INCLUDED_FHSS_UTILS_API_H */ 23 | -------------------------------------------------------------------------------- /include/gnuradio/fhss_utils/cf_estimate.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 4 | * (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 5 | * retains certain rights in this software. 6 | * 7 | * SPDX-License-Identifier: GPL-3.0-or-later 8 | */ 9 | 10 | #ifndef INCLUDED_FHSS_UTILS_CF_ESTIMATE_H 11 | #define INCLUDED_FHSS_UTILS_CF_ESTIMATE_H 12 | 13 | #include 14 | #include 15 | 16 | namespace gr { 17 | namespace fhss_utils { 18 | 19 | /*! 20 | * \brief Estimates center frequency of a burst and re-centers it. 21 | * \ingroup fhss_utils 22 | *\n 23 | * Four Methods are provided for Center Frequency Estimation:\n 24 | * -Root-Mean_Squared: RMS estimate, this is the best for general use.\n 25 | * -Half Power: uses the center point in the cumulative sum of the PSD, this 26 | * method is not great for general use.\n 27 | * -Middle Out: uses the relationship between the peak and noise floor to 28 | * estimate the bandwidth, this method is best for high SNR signals. \n 29 | * -Coerce Only moves center freq to the nearest entry in the provided 30 | * channel_freqs list\n 31 | * Frequency coercion also be applied to any method if the list is provided. 32 | *\n 33 | * Parameters:\n 34 | * -method selects method of center frequency estimation.\n 35 | *\n 36 | * Required metadata: 'sample_rate' and 'center_frequency'\n 37 | *\n 38 | * Optional metadata: 'relative_frequency'\n 39 | *\n 40 | * Produced metdata: 'bandwidth', 'snr_db', corrections to 'center_frequency' and 41 | *'relative_frequency'\n 42 | * 43 | */ 44 | class FHSS_UTILS_API cf_estimate : virtual public gr::block 45 | { 46 | public: 47 | typedef std::shared_ptr sptr; 48 | 49 | /*! 50 | * \brief Creates a new instance of fhss_utils::cf_estimate. 51 | * 52 | * 53 | * \param method Center Frequency Estimation method #cf_method 54 | * \param channel_freqs Channel frequencies to set for coerce method. 55 | */ 56 | static sptr make(int method = 0, 57 | std::vector channel_freqs = std::vector()); 58 | 59 | /*! 60 | * \brief Set channel center frequencies 61 | * 62 | * If the selected method is COERCE, the center frequency for each 63 | * burst will be coerced to the nearest channel frequency. 64 | * 65 | * \param channel_freqs Channel frequencies to set. 66 | */ 67 | virtual void set_freqs(std::vector channel_freqs) = 0; 68 | 69 | /*! 70 | * \brief Set center frequency estimation algorithm 71 | * 72 | * COERCE, RMS, HALF_POWER are the valid options, and are enumerated 73 | * types in the fhss_utils namespace 74 | * 75 | * \param method Method enum to select 76 | */ 77 | virtual void set_method(int method) = 0; 78 | 79 | /*! 80 | * \brief Set the expected minimum SNR (only used by middle out method). 81 | * 82 | * \param snr Expected minimum SNR. 83 | */ 84 | virtual void set_snr_min(float snr) = 0; 85 | /*! 86 | * \brief Set the minimum threshold to use (only used by middle out method). 87 | * 88 | * \param thresh Minimum threshold to use relative to the peak. 89 | */ 90 | virtual void set_thresh_min(float thresh) = 0; 91 | }; 92 | 93 | 94 | /*! 95 | * \brief Center frequency estimation method options 96 | * 97 | */ 98 | enum cf_method { RMS = 0, HALF_POWER, MIDDLE_OUT, COERCE }; 99 | 100 | 101 | } // namespace fhss_utils 102 | } // namespace gr 103 | 104 | #endif /* INCLUDED_FHSS_UTILS_CF_ESTIMATE_H */ 105 | -------------------------------------------------------------------------------- /include/gnuradio/fhss_utils/constants.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 4 | * (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 5 | * retains certain rights in this software. 6 | * 7 | * SPDX-License-Identifier: GPL-3.0-or-later 8 | */ 9 | 10 | 11 | #ifndef INCLUDED_FHSS_UTILS_CONSTANTS_H 12 | #define INCLUDED_FHSS_UTILS_CONSTANTS_H 13 | 14 | #include 15 | #include 16 | 17 | namespace gr { 18 | namespace fhss_utils { 19 | 20 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__in(); 21 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__out(); 22 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__center_frequency(); 23 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__relative_frequency(); 24 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__sample_rate(); 25 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__bandwidth(); 26 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__pwr_db(); 27 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__snr_db(); 28 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__debug(); 29 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__rx_freq(); 30 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__burst_id(); 31 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__magnitude(); 32 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__noise_density(); 33 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__new_burst(); 34 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__gone_burst(); 35 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__rx_time(); 36 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__start_time(); 37 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__duration(); 38 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__cpdus(); 39 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__start_offset(); 40 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__end_offset(); 41 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__input_rate(); 42 | FHSS_UTILS_API const pmt::pmt_t PMTCONSTSTR__cut_short(); 43 | 44 | } // namespace fhss_utils 45 | } // namespace gr 46 | 47 | #endif /* INCLUDED_FHSS_UTILS_CONSTANTS_H */ 48 | -------------------------------------------------------------------------------- /include/gnuradio/fhss_utils/fft_burst_tagger.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 4 | * (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 5 | * retains certain rights in this software. 6 | * 7 | * SPDX-License-Identifier: GPL-3.0-or-later 8 | */ 9 | 10 | #ifndef INCLUDED_FHSS_UTILS_FFT_BURST_TAGGER_H 11 | #define INCLUDED_FHSS_UTILS_FFT_BURST_TAGGER_H 12 | 13 | #include 14 | #include 15 | 16 | namespace gr { 17 | namespace fhss_utils { 18 | 19 | /*! 20 | * \brief This block is a fast energy detector that processes a stream of complex data and 21 | * will apply stream tags to identify the start, end, and approximate frequency of 22 | * detected bursts of energy. 23 | * 24 | * Internally this block processes data one FFT at a time. It maintains a dynamic noise 25 | * floor estimate for each bin over the prior `history_size` FFTs, and compares incoming 26 | * FFT bins to see if they are `threshold` dB above the moving noise floor estimate. Once 27 | * detected, potential bursts are observed to ensure they remain above the threshold for 28 | * at least `lookahead` FFTs, after which they are tracked as detections and tagged; 29 | * bursts that are shorter than this duration are likely to be charachterized as noise and 30 | * ignored. 31 | * 32 | * Burst tags can be applied before / after the detected start / end of burst using the 33 | * `burst_pre_len` and `burst_post_len` options respectively. End of burst tags are 34 | * appended once the value of the center burst bin or either adjacent bin is gone for 35 | * `burst_post_len` FFTs, so it is recommended to set this value to some nonzero number to 36 | * reduce the chance a burst end is tagged too early. If this value is set too long, 37 | * multiple bursts may be combined into one burst. 38 | * 39 | * Debug information including detected peak value files (saved in /tmp) and a complex PDU 40 | * suitable for display on the in-tree QT Time Sink of FFTs and dynamic threshold can be 41 | * endabled for debugging, but it is not recommended for normal operation as these take a 42 | * non-trivial amount of compute cycles. Deeper level debug information can be enabled 43 | * using compile options in the source. 44 | * 45 | * \ingroup fhss_utils 46 | * 47 | */ 48 | class FHSS_UTILS_API fft_burst_tagger : virtual public gr::block 49 | { 50 | public: 51 | typedef std::shared_ptr sptr; 52 | 53 | /*! 54 | * \brief Creates a new instance of fhss_utils::fft_burst_tagger. 55 | * 56 | * @param center_freq - center frequency of data stream, unit Hz 57 | * @param fft_size - number of bins in the primary FFT 58 | * @param sample_rate - sample rate of incoming stream 59 | * @param burst_pre_len - number of FFTs before the burst to place the START tag 60 | * @param burst_post_len - number of FFTs after the burst to place the END tag 61 | * @param burst_width - estimated bandwidth of bursts in Hz 62 | * @param max_bursts - maximum number of bursts allowed simultaneously (0=default) 63 | * @param max_burst_len - bursts exceeding this length will be tagged immediately 64 | * @param threshold - detection threshold above dynamic noise average (dB) 65 | * @param history_size - number of FFTs to compute noise estimate over 66 | * @param lookahead - number of FFTs a burst must be present to be tagged 67 | * @param debug - true enables debug file and message output functionality 68 | * @return shared_ptr 69 | */ 70 | static sptr make(float center_freq, 71 | int fft_size, 72 | int sample_rate, 73 | int burst_pre_len, 74 | int burst_post_len, 75 | int burst_width, 76 | int max_bursts = 0, 77 | int max_burst_len = 0, 78 | float threshold = 7, 79 | int history_size = 512, 80 | int lookahead = 10, 81 | bool debug = false); 82 | 83 | /** 84 | * Returns total number of bursts seen 85 | * 86 | * @return uint64_t - number of bursts 87 | */ 88 | virtual uint64_t get_n_tagged_bursts() = 0; 89 | 90 | /** 91 | * Resets burst tagger 92 | */ 93 | virtual void reset() = 0; 94 | 95 | /** 96 | * Sets threshold 97 | * Unit: dB 98 | * 99 | * @param threshold - threshold 100 | */ 101 | virtual void set_threshold(float threshold) = 0; 102 | 103 | /** 104 | * Sets max burst bandwidth 105 | * 106 | * @param bw - bandwidth in Hz 107 | */ 108 | virtual void set_max_burst_bandwidth(double bw) = 0; 109 | 110 | /** 111 | * Preloads the noise floor estimate with a uniform value so that bursts can be 112 | * detected immediately 113 | * 114 | * @param noise_density - noise density to preload 115 | * @param preload - must be set to true to take effect 116 | */ 117 | virtual void preload_noise_floor(double noise_density, bool preload = false) = 0; 118 | }; // end class fft_burst_tagger 119 | 120 | 121 | } // namespace fhss_utils 122 | } // namespace gr 123 | 124 | #endif /* INCLUDED_FHSS_UTILS_FFT_BURST_TAGGER_H */ 125 | -------------------------------------------------------------------------------- /include/gnuradio/fhss_utils/tagged_burst_to_pdu.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 4 | * (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 5 | * retains certain rights in this software. 6 | * 7 | * SPDX-License-Identifier: GPL-3.0-or-later 8 | */ 9 | 10 | #ifndef INCLUDED_FHSS_UTILS_TAGGED_BURST_TO_PDU_H 11 | #define INCLUDED_FHSS_UTILS_TAGGED_BURST_TO_PDU_H 12 | 13 | #include 14 | #include 15 | 16 | namespace gr { 17 | namespace fhss_utils { 18 | 19 | /*! 20 | * \brief Tagged Burst to PDU 21 | * \ingroup fhss_utils 22 | * 23 | * Converts Tagged burst stream to PDU messages 24 | */ 25 | class FHSS_UTILS_API tagged_burst_to_pdu : virtual public gr::sync_block 26 | { 27 | public: 28 | typedef std::shared_ptr sptr; 29 | 30 | /** 31 | * \brief Creates a new instance of fhss_utils::tagged_burst_to_pdu. 32 | * 33 | * @param decimation - 34 | * @param taps - 35 | * @param min_burst_time - 36 | * @param max_burst_time - 37 | * @param relative_center_frequency - 38 | * @param relative_span - 39 | * @param relative_sample_rate - 40 | * @param sample_rate - 41 | * @param num_threads - 42 | */ 43 | static sptr make(size_t decimation, 44 | const std::vector& taps, 45 | float min_burst_time, 46 | float max_burst_time, 47 | float relative_center_frequency, 48 | float relative_span, 49 | float relative_sample_rate, 50 | float sample_rate, 51 | int num_threads); 52 | 53 | // virtual uint64_t get_n_dropped_bursts() = 0; 54 | // virtual int get_output_queue_size() = 0; 55 | // virtual int get_output_max_queue_size() = 0; 56 | }; 57 | 58 | } // namespace fhss_utils 59 | } // namespace gr 60 | 61 | #endif /* INCLUDED_FHSS_UTILS_TAGGED_BURST_TO_PDU_H */ 62 | -------------------------------------------------------------------------------- /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-fhss_utils 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 | list(APPEND fhss_utils_sources 15 | cf_estimate_impl.cc 16 | fft_burst_tagger_impl.cc 17 | tagged_burst_to_pdu_impl.cc 18 | constants.cc 19 | ) 20 | 21 | set(fhss_utils_sources "${fhss_utils_sources}" PARENT_SCOPE) 22 | if(NOT fhss_utils_sources) 23 | MESSAGE(STATUS "No C++ sources... skipping lib/") 24 | return() 25 | endif(NOT fhss_utils_sources) 26 | 27 | add_library(gnuradio-fhss_utils SHARED ${fhss_utils_sources}) 28 | target_link_libraries(gnuradio-fhss_utils 29 | gnuradio::gnuradio-runtime 30 | gnuradio::gnuradio-filter 31 | gnuradio::gnuradio-blocks 32 | gnuradio::gnuradio-fft 33 | gnuradio::gnuradio-pmt 34 | ) 35 | target_include_directories(gnuradio-fhss_utils 36 | PUBLIC $ 37 | PUBLIC $ 38 | ) 39 | set_target_properties(gnuradio-fhss_utils PROPERTIES DEFINE_SYMBOL "gnuradio_fhss_utils_EXPORTS") 40 | 41 | if(APPLE) 42 | set_target_properties(gnuradio-fhss_utils PROPERTIES 43 | INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib" 44 | ) 45 | endif(APPLE) 46 | 47 | # This is to allow building on Apple Silicon, though removing AVX is not 48 | # necessary on Intel Macs... Just a quick patch for now 49 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") 50 | if(NOT APPLE) 51 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx -fext-numeric-literals") 52 | endif(APPLE) 53 | 54 | ######################################################################## 55 | # Install built library files 56 | ######################################################################## 57 | include(GrMiscUtils) 58 | GR_LIBRARY_FOO(gnuradio-fhss_utils) 59 | 60 | ######################################################################## 61 | # Print summary 62 | ######################################################################## 63 | message(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") 64 | message(STATUS "Building for version: ${VERSION} / ${LIBVER}") 65 | 66 | ######################################################################## 67 | # Build and register unit test 68 | ######################################################################## 69 | include(GrTest) 70 | 71 | # If your unit tests require special include paths, add them here 72 | #include_directories() 73 | # List all files that contain Boost.UTF unit tests here 74 | list(APPEND test_fhss_utils_sources 75 | ) 76 | # Anything we need to link to for the unit tests go here 77 | list(APPEND GR_TEST_TARGET_DEPS gnuradio-fhss_utils) 78 | 79 | if(NOT test_fhss_utils_sources) 80 | MESSAGE(STATUS "No C++ unit tests... skipping") 81 | return() 82 | endif(NOT test_fhss_utils_sources) 83 | 84 | foreach(qa_file ${test_fhss_utils_sources}) 85 | GR_ADD_CPP_TEST("fhss_utils_${qa_file}" 86 | ${CMAKE_CURRENT_SOURCE_DIR}/${qa_file} 87 | ) 88 | endforeach(qa_file) 89 | -------------------------------------------------------------------------------- /lib/cf_estimate_impl.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 4 | * (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 5 | * retains certain rights in this software. 6 | * Copyright 2021 Jacob Gilbert 7 | * 8 | * SPDX-License-Identifier: GPL-3.0-or-later 9 | */ 10 | 11 | #ifndef INCLUDED_FHSS_UTILS_CF_ESTIMATE_IMPL_H 12 | #define INCLUDED_FHSS_UTILS_CF_ESTIMATE_IMPL_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace gr { 20 | namespace fhss_utils { 21 | 22 | class cf_estimate_impl : public cf_estimate 23 | { 24 | private: 25 | // message handlers 26 | void pdu_handler(pmt::pmt_t pdu); 27 | 28 | // this block supports a few different estimation methods 29 | int d_method; 30 | std::vector d_channel_freqs; 31 | float d_snr_min; 32 | float d_thresh_min; 33 | std::vector d_bug; 34 | 35 | // fft tools 36 | void fft_setup(int power); 37 | void fft_cleanup(); 38 | std::vector d_ffts; 39 | std::vector d_windows; 40 | float* d_magnitude_shifted_f; 41 | 42 | // correction tools 43 | blocks::rotator d_rotate; 44 | std::vector d_corrected_burst; 45 | 46 | /** 47 | * \brief Coerce estimated center frequency to the closest Channel Freq list entry 48 | * 49 | * \param center_frequency Center frequency of input PDU data 50 | * \param sample_rate Sample Rate of PDU data 51 | * \param shift Reference to center frequency shift factor (output) 52 | * 53 | * returns a bool indicating if the burst SNR was below configured minimum 54 | */ 55 | bool coerce_frequency(float center_frequency, float sample_rate, float &shift); 56 | 57 | /** 58 | * \brief Return center frequency estimate using the RMS method 59 | * 60 | * \param mags2 Vector of magnitude^2 FFT of input PDU data 61 | * \param freq_axis Frequency of each bin in the mags2 vector 62 | * \param center_frequency Center Frequency of input data 63 | * \param sample_rate Sample Rate of input data 64 | * \param shift Reference to center frequency shift factor (output) 65 | * \param start_bin Lowest frequency bin to examine (Hz) 66 | * 67 | * returns a bool indicating if the burst SNR was below configured minimum 68 | */ 69 | bool rms_cf(const std::vector &mags2, 70 | const std::vector &freq_axis, 71 | float center_frequency, 72 | float sample_rate, 73 | float &shift, 74 | size_t start_bin); 75 | 76 | /*! 77 | * \brief Return center frequency estimate using the Half Power method 78 | * 79 | * \param mags2 Vector of magnitude^2 FFT of input PDU data 80 | * \param shift Reference to center frequency shift factor (output) 81 | * \param start_bin Lowest frequency bin to examine (Hz) 82 | * 83 | * returns a bool indicating if the burst SNR was below configured minimum 84 | */ 85 | bool half_power_cf(const std::vector &mags2, float &shift, size_t start_bin); 86 | 87 | /*! 88 | * \brief Return bandwidth estimate using the RMS method 89 | * 90 | * \param mags2 Vector of magnitude^2 FFT of input PDU data 91 | * \param freq_axis Frequency of each bin in the mags2 vector 92 | * \param center_frequency Center Frequency of input PDU data 93 | * \param bandwidth Reference to bandwidth estimate (output) 94 | * \param start_bin Lowest frequency bin to examine (Hz) 95 | * 96 | * returns a bool indicating if the burst SNR was below configured minimum 97 | */ 98 | bool rms_bw(const std::vector &mags2, 99 | const std::vector &freq_axis, 100 | float center_frequency, 101 | float &bandwidth, 102 | size_t start_bin); 103 | 104 | /*! 105 | * \brief Estimate bandwidth and center frequency using the Middle Out method 106 | * 107 | * \param mags2 Vector of magnitude^2 FFT of input PDU data 108 | * \param bin_resolution Span of each FFT bin for scaling bandwidth estimate 109 | * \param noise_floor Estimate of the noise floor in dB 110 | * \param bandwidth Reference to bandwidth estimate (output) 111 | * \param shift Reference to center frequency shift factor (output) 112 | * \param start_bin Lowest frequency bin to examine (Hz) 113 | * 114 | * returns a boool indicating if the burst SNR was below configured minimum 115 | */ 116 | bool middle_out(const std::vector &mags2, 117 | float bin_resolution, 118 | float noise_floor, 119 | float &bandwidth, 120 | float &shift, 121 | size_t start_bin); 122 | 123 | /*! 124 | * \brief Return estimated power in a burst 125 | * 126 | * \param mags2 Vector of magnitude^2 FFT of input PDU data 127 | * \param freq_axis Frequency of each bin in the mags2 vector 128 | * \param center_frequency Center Frequency of input PDU data 129 | * \param bandwidth Bandwidth of signal 130 | * \param power Reference to the power of the signal (output) 131 | * 132 | * returns a boool indicating if the burst SNR was below configured minimum 133 | */ 134 | bool estimate_pwr(const std::vector &mags2, 135 | const std::vector &freq_axis, 136 | float center_frequency, 137 | float bandwidth, 138 | float &power); 139 | 140 | 141 | public: 142 | cf_estimate_impl(int method, std::vector channel_freqs); 143 | 144 | ~cf_estimate_impl() override; 145 | 146 | void set_freqs(std::vector channel_freqs) override 147 | { 148 | d_channel_freqs = channel_freqs; 149 | }; 150 | void set_method(int method) override { d_method = method; }; 151 | void set_snr_min(float snr) override { d_snr_min = snr; }; 152 | void set_thresh_min(float thresh) override { d_thresh_min = thresh; }; 153 | }; 154 | 155 | } // namespace fhss_utils 156 | } // namespace gr 157 | 158 | #endif /* INCLUDED_FHSS_UTILS_CF_ESTIMATE_IMPL_H */ 159 | -------------------------------------------------------------------------------- /lib/constants.cc: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 4 | * (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 5 | * retains certain rights in this software. 6 | * 7 | * SPDX-License-Identifier: GPL-3.0-or-later 8 | */ 9 | 10 | 11 | #ifdef HAVE_CONFIG_H 12 | #include "config.h" 13 | #endif 14 | 15 | #include 16 | #include 17 | 18 | namespace gr { 19 | namespace fhss_utils { 20 | 21 | const pmt::pmt_t PMTCONSTSTR__in() 22 | { 23 | static const pmt::pmt_t val = pmt::mp("in"); 24 | return val; 25 | } 26 | const pmt::pmt_t PMTCONSTSTR__out() 27 | { 28 | static const pmt::pmt_t val = pmt::mp("out"); 29 | return val; 30 | } 31 | const pmt::pmt_t PMTCONSTSTR__center_frequency() 32 | { 33 | static const pmt::pmt_t val = pmt::mp("center_frequency"); 34 | return val; 35 | } 36 | const pmt::pmt_t PMTCONSTSTR__relative_frequency() 37 | { 38 | static const pmt::pmt_t val = pmt::mp("relative_frequency"); 39 | return val; 40 | } 41 | const pmt::pmt_t PMTCONSTSTR__sample_rate() 42 | { 43 | static const pmt::pmt_t val = pmt::mp("sample_rate"); 44 | return val; 45 | } 46 | const pmt::pmt_t PMTCONSTSTR__bandwidth() 47 | { 48 | static const pmt::pmt_t val = pmt::mp("bandwidth"); 49 | return val; 50 | } 51 | const pmt::pmt_t PMTCONSTSTR__pwr_db() 52 | { 53 | static const pmt::pmt_t val = pmt::mp("pwr_db"); 54 | return val; 55 | } 56 | const pmt::pmt_t PMTCONSTSTR__snr_db() 57 | { 58 | static const pmt::pmt_t val = pmt::mp("snr_db"); 59 | return val; 60 | } 61 | const pmt::pmt_t PMTCONSTSTR__debug() 62 | { 63 | static const pmt::pmt_t val = pmt::mp("debug"); 64 | return val; 65 | } 66 | const pmt::pmt_t PMTCONSTSTR__rx_freq() 67 | { 68 | static const pmt::pmt_t val = pmt::mp("rx_freq"); 69 | return val; 70 | } 71 | const pmt::pmt_t PMTCONSTSTR__burst_id() 72 | { 73 | static const pmt::pmt_t val = pmt::mp("burst_id"); 74 | return val; 75 | } 76 | const pmt::pmt_t PMTCONSTSTR__magnitude() 77 | { 78 | static const pmt::pmt_t val = pmt::mp("magnitude"); 79 | return val; 80 | } 81 | const pmt::pmt_t PMTCONSTSTR__noise_density() 82 | { 83 | static const pmt::pmt_t val = pmt::mp("noise_density"); 84 | return val; 85 | } 86 | const pmt::pmt_t PMTCONSTSTR__new_burst() 87 | { 88 | static const pmt::pmt_t val = pmt::mp("new_burst"); 89 | return val; 90 | } 91 | const pmt::pmt_t PMTCONSTSTR__gone_burst() 92 | { 93 | static const pmt::pmt_t val = pmt::mp("gone_burst"); 94 | return val; 95 | } 96 | const pmt::pmt_t PMTCONSTSTR__rx_time() 97 | { 98 | static const pmt::pmt_t val = pmt::mp("rx_time"); 99 | return val; 100 | } 101 | const pmt::pmt_t PMTCONSTSTR__start_time() 102 | { 103 | static const pmt::pmt_t val = pmt::mp("start_time"); 104 | return val; 105 | } 106 | const pmt::pmt_t PMTCONSTSTR__duration() 107 | { 108 | static const pmt::pmt_t val = pmt::mp("duration"); 109 | return val; 110 | } 111 | const pmt::pmt_t PMTCONSTSTR__cpdus() 112 | { 113 | static const pmt::pmt_t val = pmt::mp("cpdus"); 114 | return val; 115 | } 116 | const pmt::pmt_t PMTCONSTSTR__start_offset() 117 | { 118 | static const pmt::pmt_t val = pmt::mp("start_offset"); 119 | return val; 120 | } 121 | const pmt::pmt_t PMTCONSTSTR__end_offset() 122 | { 123 | static const pmt::pmt_t val = pmt::mp("end_offset"); 124 | return val; 125 | } 126 | const pmt::pmt_t PMTCONSTSTR__input_rate() 127 | { 128 | static const pmt::pmt_t val = pmt::mp("input_rate"); 129 | return val; 130 | } 131 | const pmt::pmt_t PMTCONSTSTR__cut_short() 132 | { 133 | static const pmt::pmt_t val = pmt::mp("cut_short"); 134 | return val; 135 | } 136 | 137 | } /* namespace fhss_utils */ 138 | } /* namespace gr */ 139 | -------------------------------------------------------------------------------- /lib/fft_burst_tagger_impl.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 4 | * (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 5 | * retains certain rights in this software. 6 | * Copyright 2021 Jacob Gilbert 7 | * 8 | * SPDX-License-Identifier: GPL-3.0-or-later 9 | */ 10 | 11 | #ifndef INCLUDED_FHSS_UTILS_FFT_BURST_TAGGER_IMPL_H 12 | #define INCLUDED_FHSS_UTILS_FFT_BURST_TAGGER_IMPL_H 13 | 14 | #include 15 | #include 16 | #include 17 | //#define __USE_MKL__ 18 | #ifdef __USE_MKL__ 19 | #include "mkl_dfti.h" 20 | #endif 21 | 22 | /* 23 | * DO_TIMER is used for profiling timer 24 | * 25 | * WARNING: Use of profiling timers has performance implications, they are only complied 26 | * if this option is enabled. 27 | */ 28 | //#define DO_TIMER 29 | #include 30 | using namespace std::chrono; 31 | using std::chrono::high_resolution_clock; 32 | 33 | namespace gr { 34 | namespace fhss_utils { 35 | 36 | struct peak { 37 | uint32_t bin; 38 | float relative_magnitude; 39 | }; 40 | 41 | class timer 42 | { 43 | public: 44 | timer() : total(0) {} 45 | 46 | #ifndef DO_TIMER 47 | void start() {} 48 | void end() {} 49 | #else 50 | void start() { start_time = high_resolution_clock::now(); } 51 | void end() 52 | { 53 | high_resolution_clock::time_point end_time = high_resolution_clock::now(); 54 | duration span = duration_cast>(end_time - start_time); 55 | total += span.count(); 56 | } 57 | #endif 58 | double elapsed() { return total; } 59 | void reset() { total = 0; } 60 | 61 | private: 62 | double total; 63 | high_resolution_clock::time_point start_time; 64 | }; // end class timer 65 | 66 | /** 67 | * Calculates a moving average utilizing circular buffer, used in this block for the noise 68 | * estimate in each bin. 69 | */ 70 | class moving_average 71 | { 72 | public: 73 | /** 74 | * Constructor 75 | * 76 | * @param size - size in samples of the moving average circular buffer 77 | */ 78 | moving_average(size_t size) : N(size) 79 | { 80 | current_index = 0; 81 | sum = 0.0; 82 | pp = 0.0; 83 | hist.resize(N); 84 | memset(&hist[0], 0, sizeof(float) * N); 85 | } 86 | 87 | /** 88 | * Add a value to circular buffer 89 | * 90 | * @param p - value to add 91 | * @return float - current accumulator value 92 | */ 93 | float add(double p) 94 | { 95 | sum += pp - hist[current_index]; 96 | hist[current_index++] = pp; 97 | pp = p; 98 | if (current_index == N) 99 | current_index = 0; 100 | return sum / N; 101 | } 102 | 103 | /** 104 | * Print the contents of the a given moving average buffer in a python-friendly 105 | * way. This is a debug function only that is not normally used 106 | */ 107 | std::string print(void) 108 | { 109 | std::stringstream sout; 110 | sout << "[" << hist[0]; 111 | for (auto ii = 1; ii < N; ii++) 112 | sout << ", " << hist[ii]; 113 | sout << "]"; 114 | return sout.str(); 115 | } 116 | 117 | private: 118 | std::vector hist; 119 | size_t current_index; 120 | size_t N; 121 | double sum; 122 | double pp; // delay noise floor sum by one FFT 123 | }; // end class moving_average 124 | 125 | /* 126 | * A pre burst is energy that has been detected as being above the configured threshold, 127 | * but is not yet sustained long enough to be considered a burst. 128 | */ 129 | struct pre_burst { 130 | uint64_t start; 131 | uint64_t peak_count; 132 | uint64_t id; 133 | uint64_t thresh_count; 134 | int center_bin; 135 | int start_bin; 136 | int stop_bin; 137 | float max_relative_magnitude; 138 | }; 139 | 140 | /* 141 | * A burst is sustained energy above the configured threshold and will result in a tag 142 | * being emitted. 143 | */ 144 | struct burst { 145 | uint64_t start; 146 | uint64_t stop; 147 | uint64_t last_active; 148 | uint64_t id; 149 | int center_bin; 150 | int start_bin; 151 | int stop_bin; 152 | float magnitude; 153 | float bandwidth; 154 | float center_freq; 155 | float noise; 156 | bool valid; 157 | }; 158 | 159 | struct owners { 160 | owners() { clear(); } 161 | uint64_t ids[4]; 162 | uint64_t _size; 163 | uint64_t uid; 164 | 165 | size_t size() { return _size; } 166 | 167 | void clear() 168 | { 169 | ids[0] = ids[1] = ids[2] = ids[3] = (uint64_t)-1; 170 | _size = 0; 171 | } 172 | 173 | /* return of zero is success, otherwise error */ 174 | int push_back(uint64_t id) 175 | { 176 | if (_size > 3) { 177 | return 1; 178 | // printf("Owners::push_back - Trying to add too many points for bin id=%zu, 179 | // size=%zu, burst=%zu\n", uid, _size, id); printf("Owner bins are %zu, %zu, 180 | // %zu %zu\n", ids[0], ids[1], ids[2], ids[3]); 181 | } 182 | ids[_size] = id; 183 | _size++; 184 | return 0; 185 | } 186 | 187 | int update(uint64_t oldID, uint64_t newID) 188 | { 189 | if (ids[0] == oldID) 190 | ids[0] = newID; 191 | else if (ids[1] == oldID) 192 | ids[1] = newID; 193 | else if (ids[2] == oldID) 194 | ids[2] = newID; 195 | else if (ids[3] == oldID) 196 | ids[3] = newID; 197 | else 198 | return 1; 199 | // printf("Owners::Update - Couldn't find id to update. This should never 200 | // happen\n"); 201 | return 0; 202 | } 203 | 204 | int erase(uint64_t id) 205 | { 206 | _size--; 207 | if (ids[0] == id) { 208 | if (_size) { 209 | ids[0] = ids[_size]; 210 | ids[_size] = (uint64_t)-1; 211 | } else 212 | ids[0] = (uint64_t)-1; 213 | } else if (ids[1] == id) { 214 | if (_size > 1) { 215 | ids[1] = ids[_size]; 216 | ids[_size] = (uint64_t)-1; 217 | } else 218 | ids[1] = (uint64_t)-1; 219 | } else if (ids[2] == id) { 220 | if (_size > 2) { 221 | ids[2] = ids[_size]; 222 | ids[_size] = (uint64_t)-1; 223 | } else 224 | ids[2] = (uint64_t)-1; 225 | } else if (ids[3] == id) { 226 | ids[3] = (uint64_t)-1; 227 | } else { 228 | _size++; // Increment size because we didn't remove anything. 229 | return 1; 230 | // printf("Owners::Remove - Couldn't find id to erase. This should never 231 | // happen - bin id = %zu, burst=%zu\n", uid, id); 232 | } 233 | return 0; 234 | } 235 | }; 236 | 237 | /** 238 | * Implementing class of fft_burst_tagger 239 | */ 240 | class fft_burst_tagger_impl : public fft_burst_tagger 241 | { 242 | private: 243 | float d_center_freq; 244 | int d_sample_rate; 245 | int d_fft_size; 246 | int d_burst_pre_len; 247 | int d_lookahead; 248 | uint64_t d_burst_id; 249 | uint64_t d_pre_burst_id; 250 | uint64_t d_n_tagged_bursts; 251 | uint64_t d_abs_fft_index; 252 | int d_max_burst_len; 253 | gr::fft::fft_complex_fwd* d_fft; 254 | int d_history_size; 255 | bool d_history_primed; 256 | int d_history_index; 257 | int d_burst_post_len; 258 | bool d_debug; 259 | bool d_pub_debug; 260 | FILE* d_burst_debug_file; 261 | std::vector d_mask_owners; 262 | 263 | int d_fine_fft_size; 264 | int d_burst_width; 265 | int d_max_bursts; 266 | uint64_t d_current_peaks; 267 | uint64_t extra; 268 | uint64_t d_rel_mag_hist; 269 | uint64_t d_rel_hist_index; 270 | uint32_t d_work_history_nffts; 271 | uint32_t d_work_sample_offset; 272 | 273 | float d_bin_width_db; 274 | 275 | float* d_window_f; 276 | float* d_magnitude_shifted_f; 277 | float* d_fine_window_f; 278 | float* d_fine_magnitude_shifted_f; 279 | float* d_baseline_sum_f; 280 | float* d_baseline_history_f; 281 | float* d_relative_magnitude_f; 282 | float* d_relative_history_f; 283 | uint32_t* d_burst_mask_i; 284 | uint32_t* d_burst_mask_j; // 1 FFT buffer to prevent burst energy from impacting noise 285 | float* d_ones_f; 286 | float d_threshold; 287 | float d_threshold_low; 288 | float d_filter_bandwidth; 289 | 290 | 291 | gr::fft::fft_complex_fwd* d_fine_fft; 292 | #ifdef __USE_MKL__ 293 | DFTI_DESCRIPTOR_HANDLE m_fft; 294 | DFTI_DESCRIPTOR_HANDLE m_fine_fft; 295 | #endif 296 | std::vector d_peaks; 297 | std::list d_pre_bursts; 298 | std::list d_bursts; 299 | std::list d_new_bursts; 300 | std::list d_gone_bursts; 301 | std::vector d_bin_averages; 302 | 303 | bool compute_relative_magnitude(void); 304 | void update_circular_buffer(void); 305 | void extract_peaks(void); 306 | void save_peaks_to_debug_file(char* filename); 307 | void remove_currently_tracked_bursts(void); 308 | void update_active_bursts(void); 309 | void update_potential_bursts(void); 310 | void delete_gone_bursts(void); 311 | void create_new_bursts(const gr_complex* input, int fft); 312 | void create_new_potential_bursts(void); 313 | void tag_new_bursts(void); 314 | void tag_gone_bursts(int noutput_items); 315 | 316 | void add_ownership(const pre_burst& b); 317 | void update_ownership(const pre_burst& pb, const burst& b); 318 | void remove_ownership(const pre_burst& b); 319 | void remove_ownership(const burst& b); 320 | 321 | bool check_prev_magnitude(size_t bin); 322 | void publish_debug(void); 323 | 324 | std::mutex d_work_mutex; 325 | 326 | void _reset(); 327 | 328 | timer d_fft_timer; 329 | timer d_update_pb_timer; 330 | timer d_update_ab_timer; 331 | timer d_remove_tb_timer; 332 | timer d_extract_timer; 333 | timer d_delete_timer; 334 | timer d_new_pb_timer; 335 | timer d_new_b_timer; 336 | timer d_update_cb_timer; 337 | timer d_total_timer; 338 | timer d_rel_mag_timer; 339 | timer d_other; 340 | 341 | public: 342 | fft_burst_tagger_impl(float center_freq, 343 | int fft_size, 344 | int sample_rate, 345 | int burst_pre_len, 346 | int burst_post_len, 347 | int burst_width, 348 | int max_bursts, 349 | int max_burst_len, 350 | float threshold, 351 | int history_size, 352 | int lookahead, 353 | bool debug); 354 | 355 | ~fft_burst_tagger_impl() override; 356 | bool start() override; 357 | bool stop() override; 358 | 359 | int general_work(int noutput_items, 360 | gr_vector_int& ninput_items, 361 | gr_vector_const_void_star& input_items, 362 | gr_vector_void_star& output_items) override; 363 | 364 | /** 365 | * Returns total number of bursts seen 366 | * 367 | * @return uint64_t - number of bursts 368 | */ 369 | uint64_t get_n_tagged_bursts() override; 370 | 371 | /** 372 | * Resets burst tagger 373 | */ 374 | void reset() override; 375 | 376 | int work(int noutput_items, 377 | gr_vector_const_void_star& input_items, 378 | gr_vector_void_star& output_items); 379 | 380 | /** 381 | * Sets detection threshold 382 | * Unit: dB 383 | * 384 | * @param threshold - threshold 385 | */ 386 | void set_threshold(float threshold) override; 387 | 388 | /** 389 | * Sets max burst bandwidth 390 | * Unit: Hz 391 | * 392 | * @param bw - bandwidth 393 | */ 394 | void set_max_burst_bandwidth(double bw) override { d_filter_bandwidth = bw; } 395 | void preload_noise_floor(double noise_density, bool preload) override; 396 | }; 397 | 398 | } // namespace fhss_utils 399 | } // namespace gr 400 | 401 | #endif /* INCLUDED_FHSS_UTILS_FFT_BURST_TAGGER_IMPL_H */ 402 | -------------------------------------------------------------------------------- /lib/tagged_burst_to_pdu_impl.h: -------------------------------------------------------------------------------- 1 | /* -*- c++ -*- */ 2 | /* 3 | * Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 4 | * (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 5 | * retains certain rights in this software. 6 | * 7 | * SPDX-License-Identifier: GPL-3.0-or-later 8 | */ 9 | 10 | #ifndef INCLUDED_FHSS_UTILS_TAGGED_BURST_TO_PDU_IMPL_H 11 | #define INCLUDED_FHSS_UTILS_TAGGED_BURST_TO_PDU_IMPL_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace gr { 20 | namespace fhss_utils { 21 | 22 | struct burst_data { 23 | uint64_t id; 24 | uint64_t offset; 25 | uint64_t data_skip; 26 | float relative_frequency; 27 | float center_frequency; 28 | float sample_rate; 29 | size_t len; 30 | size_t rot_skip; 31 | pmt::pmt_t dict; 32 | gr_complex* data; 33 | gr_complex* rot_tmp; 34 | blocks::rotator rotate; 35 | }; 36 | 37 | struct two_gr_complex { 38 | two_gr_complex() {} 39 | two_gr_complex(gr_complex* a, gr_complex* b) : one(a), two(b) {} 40 | gr_complex* one; 41 | gr_complex* two; 42 | }; 43 | 44 | struct buffer { 45 | buffer(size_t capacity = 0) 46 | { 47 | reset(); 48 | if (capacity > 0) 49 | data.resize(int(capacity * 1.1)); 50 | data.resize(0); 51 | desired_max = capacity; 52 | } 53 | std::vector new_burst_tags; 54 | std::vector rx_time_tags; 55 | std::vector gone_burst_tags; 56 | std::vector data; 57 | size_t desired_max; 58 | bool end_flag; 59 | size_t start; 60 | 61 | bool is_full() { return data.size() >= desired_max; } 62 | 63 | void reset() 64 | { 65 | new_burst_tags.resize(0); 66 | rx_time_tags.resize(0); 67 | gone_burst_tags.resize(0); 68 | data.resize(0); 69 | end_flag = false; 70 | } 71 | 72 | void add_data(const gr_complex* d, size_t length) 73 | { 74 | // size_t clength = std::min(length, data.capacity() - data.size()); 75 | // printf("Going to add %zu data of %zu, cap = %zu, size = %zu, ptr = %p\n", 76 | // clength, length, data.capacity(), data.size(), this); 77 | data.insert(data.end(), d, d + length); 78 | } 79 | 80 | void add_tags(const std::vector &new_b, 81 | const std::vector &rx_time, 82 | const std::vector &gone_b) 83 | { 84 | new_burst_tags.insert(new_burst_tags.end(), new_b.begin(), new_b.end()); 85 | rx_time_tags.insert(rx_time_tags.end(), rx_time.begin(), rx_time.end()); 86 | gone_burst_tags.insert(gone_burst_tags.end(), gone_b.begin(), gone_b.end()); 87 | } 88 | }; 89 | 90 | 91 | /** 92 | * Implementing class of tagged_burst_to_pdu 93 | */ 94 | class tagged_burst_to_pdu_impl : public tagged_burst_to_pdu 95 | { 96 | private: 97 | bool d_debug; 98 | float d_relative_center_frequency; 99 | float d_relative_sample_rate; 100 | int d_min_burst_size; 101 | int d_max_burst_size; 102 | int d_outstanding; 103 | int d_max_outstanding; 104 | uint64_t d_n_dropped_bursts; 105 | bool d_blocked; 106 | size_t d_decimation; 107 | std::vector d_taps; 108 | boost::lockfree::queue d_write_queue; 109 | boost::lockfree::queue d_work_queue; 110 | float d_sample_rate; 111 | int d_num_threads; 112 | 113 | size_t d_block_increment; 114 | size_t d_max_id; 115 | buffer* d_current_buffer; 116 | 117 | tag_t d_current_rx_time_tag; 118 | std::queue d_rx_time_tags; 119 | std::queue d_alloced_arrays; 120 | std::vector d_input_fir_filters; 121 | 122 | boost::thread* process_thread; 123 | 124 | float d_lower_border; 125 | float d_upper_border; 126 | 127 | std::map d_bursts; 128 | std::map d_new_bursts; 129 | 130 | void append_to_burst(burst_data& burst, const gr_complex* data, size_t n); 131 | void publish_burst(burst_data& burst); 132 | 133 | void create_new_bursts(const buffer& work_buffer); 134 | void publish_and_remove_old_bursts(const buffer& work_buffer); 135 | void update_current_bursts(int noutput_items, const gr_complex* in); 136 | void process_data(); 137 | 138 | int get_output_queue_size(); 139 | int get_output_max_queue_size(); 140 | void burst_handled(pmt::pmt_t msg); 141 | 142 | double convert_rx_time(const tag_t& rx_time_tag); 143 | 144 | // The gnuradio filter function reads memory that it shouldn't. We offset our array 145 | // by this amount to prevent bad things from happening. If we fix the filter 146 | // function, then we can get rid of this. 147 | const size_t ROT_TMP_OFFSET = 4; 148 | 149 | public: 150 | // d_block_size is used as the buffer size and should always be set to 151 | // far more than half of the tap length, since that is the overlap region 152 | static const size_t d_block_size = 32 * 1024; 153 | // d_num_buffers was empirically chosen to be 5. If we need more than 5, 154 | // the block is probably backed up significantly and should back-pressure 155 | // the gnuradio scheduler 156 | static const size_t d_num_buffers = 5; 157 | 158 | tagged_burst_to_pdu_impl(size_t decimation, 159 | const std::vector& taps, 160 | float min_burst_time, 161 | float max_burst_time, 162 | float relative_center_frequency, 163 | float relative_span, 164 | float relative_sample_rate, 165 | float sample_rate, 166 | int num_threads); 167 | 168 | ~tagged_burst_to_pdu_impl() override; 169 | 170 | bool stop() override; 171 | 172 | /*uint64_t get_n_dropped_bursts() override;*/ 173 | 174 | int work(int noutput_items, 175 | gr_vector_const_void_star& input_items, 176 | gr_vector_void_star& output_items) override; 177 | }; 178 | 179 | } // namespace fhss_utils 180 | } // namespace gr 181 | 182 | #endif /* INCLUDED_FHSS_UTILS_TAGGED_BURST_TO_PDU_IMPL_H */ 183 | -------------------------------------------------------------------------------- /python/fhss_utils/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-fhss_utils 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | 8 | ######################################################################## 9 | # Include python install macros 10 | ######################################################################## 11 | include(GrPython) 12 | if(NOT PYTHONINTERP_FOUND) 13 | return() 14 | endif() 15 | 16 | add_subdirectory(bindings) 17 | 18 | ######################################################################## 19 | # Install python sources 20 | ######################################################################## 21 | GR_PYTHON_INSTALL( 22 | FILES 23 | __init__.py 24 | fft_peak.py 25 | s_and_h_detector.py 26 | coarse_dehopper.py 27 | fine_dehopper.py 28 | fsk_burst_extractor_hier.py 29 | burst_tag_debug.py 30 | sigmf_meta_writer.py DESTINATION ${GR_PYTHON_DIR}/gnuradio/fhss_utils 31 | ) 32 | 33 | # only install burst_tag_debug if PIL package is found, don't require it 34 | set(has_PIL False) 35 | GR_PYTHON_CHECK_MODULE_RAW("Python PIL" "import PIL" "has_PIL") 36 | if(has_PIL) 37 | message("-- Found Python PIL package, will install burst_tag_debug") 38 | GR_PYTHON_INSTALL( 39 | FILES 40 | burst_tag_debug.py DESTINATION ${GR_PYTHON_DIR}/fhss_utils 41 | ) 42 | else() 43 | message("-- Could not find Python PIL, not installing burst_tag_debug") 44 | endif() 45 | 46 | ######################################################################## 47 | # Handle the unit tests 48 | ######################################################################## 49 | include(GrTest) 50 | 51 | set(GR_TEST_TARGET_DEPS gnuradio-fhss_utils) 52 | 53 | # Create a package directory that tests can import. It includes everything 54 | # from `python/`. 55 | add_custom_target( 56 | copy_module_for_tests ALL 57 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR} 58 | ${PROJECT_BINARY_DIR}/test_modules/gnuradio/fhss_utils/ 59 | ) 60 | 61 | GR_ADD_TEST(qa_cf_estimate ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_cf_estimate.py) 62 | GR_ADD_TEST(qa_fft_burst_tagger ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_fft_burst_tagger.py) 63 | GR_ADD_TEST(qa_tagged_burst_to_pdu ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_tagged_burst_to_pdu.py) 64 | GR_ADD_TEST(qa_constants ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_constants.py) 65 | -------------------------------------------------------------------------------- /python/fhss_utils/__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 FHSS_UTILS module. Place your Python package 11 | description here (python/__init__.py). 12 | ''' 13 | import os 14 | 15 | # import pybind11 generated symbols into the fhss_utils namespace 16 | try: 17 | # this might fail if the module is python-only 18 | from .fhss_utils_python import * 19 | except ModuleNotFoundError: 20 | pass 21 | 22 | # import any pure python here 23 | from .fft_peak import fft_peak 24 | from .s_and_h_detector import s_and_h_detector 25 | from .coarse_dehopper import coarse_dehopper 26 | from .fine_dehopper import fine_dehopper 27 | from .fsk_burst_extractor_hier import fsk_burst_extractor_hier 28 | from .sigmf_meta_writer import sigmf_meta_writer 29 | # 30 | 31 | try: 32 | import PIL 33 | from .burst_tag_debug import burst_tag_debug 34 | except ImportError as e: 35 | print("Python PIL library not found, not including burst_tag_debug in import", e) 36 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021 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 fhss_utils_sources) 12 | MESSAGE(STATUS "No C++ sources... skipping python bindings") 13 | return() 14 | endif(NOT fhss_utils_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 fhss_utils_python_files 32 | cf_estimate_python.cc 33 | fft_burst_tagger_python.cc 34 | tagged_burst_to_pdu_python.cc 35 | python_bindings.cc 36 | constants_python.cc) 37 | 38 | GR_PYBIND_MAKE_OOT(fhss_utils 39 | ../../.. 40 | gr::fhss_utils 41 | "${fhss_utils_python_files}") 42 | 43 | # copy bindings extension for use in QA test module 44 | add_custom_command(TARGET fhss_utils_python POST_BUILD 45 | COMMAND ${CMAKE_COMMAND} -E copy $ 46 | ${PROJECT_BINARY_DIR}/test_modules/gnuradio/fhss_utils/ 47 | ) 48 | 49 | install(TARGETS fhss_utils_python DESTINATION ${GR_PYTHON_DIR}/gnuradio/fhss_utils COMPONENT pythonapi) 50 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandialabs/gr-fhss_utils/8993c6a89444e740b06ff4829b94555b04433bf7/python/fhss_utils/bindings/README.md -------------------------------------------------------------------------------- /python/fhss_utils/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 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/cf_estimate_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(cf_estimate.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(f809e03f6aab1a5d1846d55e041d55b4) */ 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_cf_estimate(py::module& m) 31 | { 32 | 33 | using cf_estimate = ::gr::fhss_utils::cf_estimate; 34 | 35 | 36 | py::class_>( 37 | m, "cf_estimate", D(cf_estimate)) 38 | 39 | .def(py::init(&cf_estimate::make), 40 | py::arg("method") = 0, 41 | py::arg("channel_freqs") = std::vector(), 42 | D(cf_estimate, make)) 43 | 44 | 45 | .def("set_freqs", 46 | &cf_estimate::set_freqs, 47 | py::arg("channel_freqs"), 48 | D(cf_estimate, set_freqs)) 49 | 50 | 51 | .def("set_method", 52 | &cf_estimate::set_method, 53 | py::arg("method"), 54 | D(cf_estimate, set_method)) 55 | 56 | 57 | .def("set_snr_min", 58 | &cf_estimate::set_snr_min, 59 | py::arg("snr_min"), 60 | D(cf_estimate, set_snr_min)) 61 | 62 | 63 | .def("set_thresh_min", 64 | &cf_estimate::set_thresh_min, 65 | py::arg("thresh_min"), 66 | D(cf_estimate, set_thresh_min)) 67 | 68 | ; 69 | 70 | py::enum_<::gr::fhss_utils::cf_method>(m, "cf_method") 71 | .value("RMS", ::gr::fhss_utils::RMS) // 0 72 | .value("HALF_POWER", ::gr::fhss_utils::HALF_POWER) // 1 73 | .value("MIDDLE_OUT", ::gr::fhss_utils::MIDDLE_OUT) // 2 74 | .value("COERCE", ::gr::fhss_utils::COERCE) // 3 75 | .export_values(); 76 | 77 | py::implicitly_convertible(); 78 | } 79 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/constants_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(constants.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(2973a4c06038ea400d28a5f8f8f9087e) */ 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_constants(py::module& m) 31 | { 32 | 33 | 34 | m.def("PMTCONSTSTR__in", &::gr::fhss_utils::PMTCONSTSTR__in, D(PMTCONSTSTR__in)); 35 | 36 | 37 | m.def("PMTCONSTSTR__out", &::gr::fhss_utils::PMTCONSTSTR__out, D(PMTCONSTSTR__out)); 38 | 39 | 40 | m.def("PMTCONSTSTR__center_frequency", 41 | &::gr::fhss_utils::PMTCONSTSTR__center_frequency, 42 | D(PMTCONSTSTR__center_frequency)); 43 | 44 | 45 | m.def("PMTCONSTSTR__relative_frequency", 46 | &::gr::fhss_utils::PMTCONSTSTR__relative_frequency, 47 | D(PMTCONSTSTR__relative_frequency)); 48 | 49 | 50 | m.def("PMTCONSTSTR__sample_rate", 51 | &::gr::fhss_utils::PMTCONSTSTR__sample_rate, 52 | D(PMTCONSTSTR__sample_rate)); 53 | 54 | 55 | m.def("PMTCONSTSTR__bandwidth", 56 | &::gr::fhss_utils::PMTCONSTSTR__bandwidth, 57 | D(PMTCONSTSTR__bandwidth)); 58 | 59 | 60 | m.def("PMTCONSTSTR__pwr_db", 61 | &::gr::fhss_utils::PMTCONSTSTR__pwr_db, 62 | D(PMTCONSTSTR__pwr_db)); 63 | 64 | 65 | m.def("PMTCONSTSTR__snr_db", 66 | &::gr::fhss_utils::PMTCONSTSTR__snr_db, 67 | D(PMTCONSTSTR__snr_db)); 68 | 69 | 70 | m.def("PMTCONSTSTR__debug", 71 | &::gr::fhss_utils::PMTCONSTSTR__debug, 72 | D(PMTCONSTSTR__debug)); 73 | 74 | 75 | m.def("PMTCONSTSTR__rx_freq", 76 | &::gr::fhss_utils::PMTCONSTSTR__rx_freq, 77 | D(PMTCONSTSTR__rx_freq)); 78 | 79 | 80 | m.def("PMTCONSTSTR__burst_id", 81 | &::gr::fhss_utils::PMTCONSTSTR__burst_id, 82 | D(PMTCONSTSTR__burst_id)); 83 | 84 | 85 | m.def("PMTCONSTSTR__magnitude", 86 | &::gr::fhss_utils::PMTCONSTSTR__magnitude, 87 | D(PMTCONSTSTR__magnitude)); 88 | 89 | 90 | m.def("PMTCONSTSTR__noise_density", 91 | &::gr::fhss_utils::PMTCONSTSTR__noise_density, 92 | D(PMTCONSTSTR__noise_density)); 93 | 94 | 95 | m.def("PMTCONSTSTR__new_burst", 96 | &::gr::fhss_utils::PMTCONSTSTR__new_burst, 97 | D(PMTCONSTSTR__new_burst)); 98 | 99 | 100 | m.def("PMTCONSTSTR__gone_burst", 101 | &::gr::fhss_utils::PMTCONSTSTR__gone_burst, 102 | D(PMTCONSTSTR__gone_burst)); 103 | 104 | 105 | m.def("PMTCONSTSTR__rx_time", 106 | &::gr::fhss_utils::PMTCONSTSTR__rx_time, 107 | D(PMTCONSTSTR__rx_time)); 108 | 109 | 110 | m.def("PMTCONSTSTR__start_time", 111 | &::gr::fhss_utils::PMTCONSTSTR__start_time, 112 | D(PMTCONSTSTR__start_time)); 113 | 114 | 115 | m.def("PMTCONSTSTR__duration", 116 | &::gr::fhss_utils::PMTCONSTSTR__duration, 117 | D(PMTCONSTSTR__duration)); 118 | 119 | 120 | m.def("PMTCONSTSTR__cpdus", 121 | &::gr::fhss_utils::PMTCONSTSTR__cpdus, 122 | D(PMTCONSTSTR__cpdus)); 123 | 124 | 125 | m.def("PMTCONSTSTR__start_offset", 126 | &::gr::fhss_utils::PMTCONSTSTR__start_offset, 127 | D(PMTCONSTSTR__start_offset)); 128 | 129 | 130 | m.def("PMTCONSTSTR__end_offset", 131 | &::gr::fhss_utils::PMTCONSTSTR__end_offset, 132 | D(PMTCONSTSTR__end_offset)); 133 | 134 | 135 | m.def("PMTCONSTSTR__input_rate", 136 | &::gr::fhss_utils::PMTCONSTSTR__input_rate, 137 | D(PMTCONSTSTR__input_rate)); 138 | 139 | 140 | m.def("PMTCONSTSTR__cut_short", 141 | &::gr::fhss_utils::PMTCONSTSTR__cut_short, 142 | D(PMTCONSTSTR__cut_short)); 143 | } 144 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/docstrings/README.md: -------------------------------------------------------------------------------- 1 | This directory stores templates for docstrings that are scraped from the include header files for each block -------------------------------------------------------------------------------- /python/fhss_utils/bindings/docstrings/cf_estimate_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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, fhss_utils, __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 | static const char* __doc_gr_fhss_utils_cf_estimate = R"doc()doc"; 19 | 20 | 21 | static const char* __doc_gr_fhss_utils_cf_estimate_cf_estimate_0 = R"doc()doc"; 22 | 23 | 24 | static const char* __doc_gr_fhss_utils_cf_estimate_cf_estimate_1 = R"doc()doc"; 25 | 26 | 27 | static const char* __doc_gr_fhss_utils_cf_estimate_make = R"doc()doc"; 28 | 29 | 30 | static const char* __doc_gr_fhss_utils_cf_estimate_set_freqs = R"doc()doc"; 31 | 32 | 33 | static const char* __doc_gr_fhss_utils_cf_estimate_set_method = R"doc()doc"; 34 | 35 | 36 | static const char* __doc_gr_fhss_utils_cf_estimate_set_snr_min = R"doc()doc"; 37 | 38 | 39 | static const char* __doc_gr_fhss_utils_cf_estimate_set_thresh_min = R"doc()doc"; 40 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/docstrings/constants_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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, fhss_utils, __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 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__in = R"doc()doc"; 19 | 20 | 21 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__out = R"doc()doc"; 22 | 23 | 24 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__center_frequency = R"doc()doc"; 25 | 26 | 27 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__relative_frequency = R"doc()doc"; 28 | 29 | 30 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__sample_rate = R"doc()doc"; 31 | 32 | 33 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__bandwidth = R"doc()doc"; 34 | 35 | 36 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__pwr_db = R"doc()doc"; 37 | 38 | 39 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__snr_db = R"doc()doc"; 40 | 41 | 42 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__debug = R"doc()doc"; 43 | 44 | 45 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__rx_freq = R"doc()doc"; 46 | 47 | 48 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__burst_id = R"doc()doc"; 49 | 50 | 51 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__magnitude = R"doc()doc"; 52 | 53 | 54 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__noise_density = R"doc()doc"; 55 | 56 | 57 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__new_burst = R"doc()doc"; 58 | 59 | 60 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__gone_burst = R"doc()doc"; 61 | 62 | 63 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__rx_time = R"doc()doc"; 64 | 65 | 66 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__start_time = R"doc()doc"; 67 | 68 | 69 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__duration = R"doc()doc"; 70 | 71 | 72 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__cpdus = R"doc()doc"; 73 | 74 | 75 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__start_offset = R"doc()doc"; 76 | 77 | 78 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__end_offset = R"doc()doc"; 79 | 80 | 81 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__input_rate = R"doc()doc"; 82 | 83 | 84 | static const char* __doc_gr_fhss_utils_PMTCONSTSTR__cut_short = R"doc()doc"; 85 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/docstrings/fft_burst_tagger_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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, fhss_utils, __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 | static const char* __doc_gr_fhss_utils_fft_burst_tagger = R"doc()doc"; 19 | 20 | 21 | static const char* __doc_gr_fhss_utils_fft_burst_tagger_fft_burst_tagger_0 = R"doc()doc"; 22 | 23 | 24 | static const char* __doc_gr_fhss_utils_fft_burst_tagger_fft_burst_tagger_1 = R"doc()doc"; 25 | 26 | 27 | static const char* __doc_gr_fhss_utils_fft_burst_tagger_make = R"doc()doc"; 28 | 29 | 30 | static const char* __doc_gr_fhss_utils_fft_burst_tagger_get_n_tagged_bursts = R"doc()doc"; 31 | 32 | 33 | static const char* __doc_gr_fhss_utils_fft_burst_tagger_reset = R"doc()doc"; 34 | 35 | 36 | static const char* __doc_gr_fhss_utils_fft_burst_tagger_set_threshold = R"doc()doc"; 37 | 38 | 39 | static const char* __doc_gr_fhss_utils_fft_burst_tagger_set_max_burst_bandwidth = 40 | R"doc()doc"; 41 | 42 | 43 | static const char* __doc_gr_fhss_utils_fft_burst_tagger_preload_noise_floor = R"doc()doc"; 44 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/docstrings/tagged_burst_to_pdu_pydoc_template.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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, fhss_utils, __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 | static const char* __doc_gr_fhss_utils_tagged_burst_to_pdu = R"doc()doc"; 19 | 20 | 21 | static const char* __doc_gr_fhss_utils_tagged_burst_to_pdu_tagged_burst_to_pdu = 22 | R"doc()doc"; 23 | 24 | 25 | static const char* __doc_gr_fhss_utils_tagged_burst_to_pdu_make = R"doc()doc"; 26 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/fft_burst_tagger_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(fft_burst_tagger.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(54acc319ff30a21e4e2007a188954c8d) */ 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_fft_burst_tagger(py::module& m) 31 | { 32 | 33 | using fft_burst_tagger = ::gr::fhss_utils::fft_burst_tagger; 34 | 35 | 36 | py::class_>( 40 | m, "fft_burst_tagger", D(fft_burst_tagger)) 41 | 42 | .def(py::init(&fft_burst_tagger::make), 43 | py::arg("center_freq"), 44 | py::arg("fft_size"), 45 | py::arg("sample_rate"), 46 | py::arg("burst_pre_len"), 47 | py::arg("burst_post_len"), 48 | py::arg("burst_width"), 49 | py::arg("max_bursts") = 0, 50 | py::arg("max_burst_len") = 0, 51 | py::arg("threshold") = 7, 52 | py::arg("history_size") = 512, 53 | py::arg("lookahead") = 10, 54 | py::arg("debug") = false, 55 | D(fft_burst_tagger, make)) 56 | 57 | 58 | .def("get_n_tagged_bursts", 59 | &fft_burst_tagger::get_n_tagged_bursts, 60 | D(fft_burst_tagger, get_n_tagged_bursts)) 61 | 62 | 63 | .def("reset", &fft_burst_tagger::reset, D(fft_burst_tagger, reset)) 64 | 65 | 66 | .def("set_max_burst_bandwidth", 67 | &fft_burst_tagger::set_max_burst_bandwidth, 68 | py::arg("bw"), 69 | D(fft_burst_tagger, set_max_burst_bandwidth)) 70 | 71 | 72 | .def("preload_noise_floor", 73 | &fft_burst_tagger::preload_noise_floor, 74 | py::arg("noise_density"), 75 | py::arg("preload"), 76 | D(fft_burst_tagger, preload_noise_floor)) 77 | 78 | ; 79 | } 80 | -------------------------------------------------------------------------------- /python/fhss_utils/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 | def argParse(): 53 | """Parses commandline args.""" 54 | desc = 'Reads the parameters from the comment block in the pybind files' 55 | parser = ArgumentParser(description=desc) 56 | 57 | parser.add_argument("function", help="Operation to perform on comment block of pybind file", choices=[ 58 | "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 | 64 | if __name__ == "__main__": 65 | # Parse command line options and set up doxyxml. 66 | args = argParse() 67 | 68 | pbhp = PybindHeaderParser(args.pathname) 69 | 70 | if args.function == "flag_auto": 71 | print(pbhp.get_flag_automatic()) 72 | elif args.function == "flag_pygccxml": 73 | print(pbhp.get_flag_pygccxml()) 74 | elif args.function == "header_filename": 75 | print(pbhp.get_header_filename()) 76 | elif args.function == "header_file_hash": 77 | print(pbhp.get_header_file_hash()) 78 | elif args.function == "all": 79 | print(pbhp.get_flags()) 80 | -------------------------------------------------------------------------------- /python/fhss_utils/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_constants(py::module& m); 25 | void bind_tagged_burst_to_pdu(py::module& m); 26 | void bind_fft_burst_tagger(py::module& m); 27 | void bind_cf_estimate(py::module& m); 28 | // ) END BINDING_FUNCTION_PROTOTYPES 29 | 30 | 31 | // We need this hack because import_array() returns NULL 32 | // for newer Python versions. 33 | // This function is also necessary because it ensures access to the C API 34 | // and removes a warning. 35 | void* init_numpy() 36 | { 37 | import_array(); 38 | return NULL; 39 | } 40 | 41 | PYBIND11_MODULE(fhss_utils_python, m) 42 | { 43 | // Initialize the numpy C API 44 | // (otherwise we will see segmentation faults) 45 | init_numpy(); 46 | 47 | // Allow access to base block methods 48 | py::module::import("gnuradio.gr"); 49 | 50 | /**************************************/ 51 | // The following comment block is used for 52 | // gr_modtool to insert binding function calls 53 | // Please do not delete 54 | /**************************************/ 55 | // BINDING_FUNCTION_CALLS( 56 | bind_constants(m); 57 | bind_tagged_burst_to_pdu(m); 58 | bind_fft_burst_tagger(m); 59 | bind_cf_estimate(m); 60 | // ) END BINDING_FUNCTION_CALLS 61 | } 62 | -------------------------------------------------------------------------------- /python/fhss_utils/bindings/tagged_burst_to_pdu_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(tagged_burst_to_pdu.h) */ 17 | /* BINDTOOL_HEADER_FILE_HASH(1980177101ed46f9da7357bf412c1898) */ 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_tagged_burst_to_pdu(py::module& m) 31 | { 32 | 33 | using tagged_burst_to_pdu = ::gr::fhss_utils::tagged_burst_to_pdu; 34 | 35 | 36 | py::class_>( 41 | m, "tagged_burst_to_pdu", D(tagged_burst_to_pdu)) 42 | 43 | .def(py::init(&tagged_burst_to_pdu::make), 44 | py::arg("decimation"), 45 | py::arg("taps"), 46 | py::arg("min_burst_time"), 47 | py::arg("max_burst_time"), 48 | py::arg("relative_center_frequency"), 49 | py::arg("relative_span"), 50 | py::arg("relative_sample_rate"), 51 | py::arg("sample_rate"), 52 | py::arg("num_threads"), 53 | D(tagged_burst_to_pdu, make)) 54 | 55 | 56 | ; 57 | } 58 | -------------------------------------------------------------------------------- /python/fhss_utils/coarse_dehopper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2018, 2019, 2020 National Technology & Engineering Solutions of Sandia, LLC 5 | # (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 6 | # retains certain rights in this software. 7 | # 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | # 10 | 11 | from gnuradio import gr 12 | from gnuradio import blocks 13 | from gnuradio import filter 14 | from gnuradio.filter import firdes 15 | from gnuradio.filter import pfb 16 | from math import pi 17 | from .fft_peak import fft_peak # hier_block 18 | from .s_and_h_detector import s_and_h_detector # hier_block 19 | 20 | 21 | class coarse_dehopper(gr.hier_block2): 22 | """ 23 | Coarse FFT based dehopper 24 | """ 25 | 26 | def __init__(self, fft_len, freq_sample_delay_samps, freq_samps_to_avg, mag_samps_to_avg, thresh): 27 | gr.hier_block2.__init__(self, 28 | "Coarse Dehopper", 29 | gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), 30 | gr.io_signature(1, 1, gr.sizeof_gr_complex * 1)) 31 | ''' 32 | Constructor 33 | 34 | @param fft_len - 35 | @param freq_sample_delay_samps - 36 | @param freq_samps_to_avg - 37 | @param mag_samps_to_avg - 38 | @param thresh - 39 | ''' 40 | 41 | ################################################## 42 | # Parameters 43 | ################################################## 44 | self.fft_len = fft_len 45 | self.freq_sample_delay_samps = freq_sample_delay_samps 46 | self.freq_samps_to_avg = freq_samps_to_avg 47 | self.mag_samps_to_avg = mag_samps_to_avg 48 | self.thresh = thresh 49 | 50 | ################################################## 51 | # Blocks 52 | ################################################## 53 | self.s_and_h_detector = s_and_h_detector( 54 | freq_sample_delay_samps=freq_sample_delay_samps, 55 | freq_samps_to_avg=freq_samps_to_avg, 56 | mag_samps_to_avg=mag_samps_to_avg, 57 | thresh=thresh, 58 | ) 59 | self.resamp = pfb.arb_resampler_ccf(1.0 / (fft_len / 4.0), taps=None, flt_size=32) 60 | self.resamp.declare_sample_delay(0) 61 | self.fir = filter.fir_filter_ccc(2, (firdes.low_pass_2(1, 1, .30, .05, 60))) 62 | self.fir.declare_sample_delay(0) 63 | self.fft_peak = fft_peak(fft_len=fft_len) 64 | self.vco = blocks.vco_c(1, 2.0 * pi / fft_len, 1) 65 | self.mult_conj = blocks.multiply_conjugate_cc(1) 66 | self.delay = blocks.delay(gr.sizeof_gr_complex * 1, int(freq_samps_to_avg) + freq_sample_delay_samps) 67 | self.c2mag = blocks.complex_to_mag(1) 68 | 69 | ################################################## 70 | # Connections 71 | ################################################## 72 | self.connect((self.c2mag, 0), (self.s_and_h_detector, 0)) 73 | self.connect((self.delay, 0), (self.mult_conj, 0)) 74 | self.connect((self.mult_conj, 0), (self.fir, 0)) 75 | self.connect((self.vco, 0), (self.mult_conj, 1)) 76 | self.connect((self.fft_peak, 0), (self.s_and_h_detector, 1)) 77 | self.connect((self.fir, 0), (self.resamp, 0)) 78 | self.connect((self, 0), (self.c2mag, 0)) 79 | self.connect((self, 0), (self.delay, 0)) 80 | self.connect((self, 0), (self.fft_peak, 0)) 81 | self.connect((self.resamp, 0), (self, 0)) 82 | self.connect((self.s_and_h_detector, 0), (self.vco, 0)) 83 | 84 | def get_fft_len(self): 85 | return self.fft_len 86 | 87 | def set_fft_len(self, fft_len): 88 | self.fft_len = fft_len 89 | self.resamp.set_rate(1.0 / (self.fft_len / 4.0)) 90 | self.fft_peak.set_fft_len(self.fft_len) 91 | 92 | def get_freq_sample_delay_samps(self): 93 | return self.freq_sample_delay_samps 94 | 95 | def set_freq_sample_delay_samps(self, freq_sample_delay_samps): 96 | self.freq_sample_delay_samps = freq_sample_delay_samps 97 | self.s_and_h_detector.set_freq_sample_delay_samps(self.freq_sample_delay_samps) 98 | self.delay.set_dly(int(self.freq_samps_to_avg) + self.freq_sample_delay_samps) 99 | 100 | def get_freq_samps_to_avg(self): 101 | return self.freq_samps_to_avg 102 | 103 | def set_freq_samps_to_avg(self, freq_samps_to_avg): 104 | self.freq_samps_to_avg = freq_samps_to_avg 105 | self.s_and_h_detector.set_freq_samps_to_avg(self.freq_samps_to_avg) 106 | self.delay.set_dly(int(self.freq_samps_to_avg) + self.freq_sample_delay_samps) 107 | 108 | def get_mag_samps_to_avg(self): 109 | return self.mag_samps_to_avg 110 | 111 | def set_mag_samps_to_avg(self, mag_samps_to_avg): 112 | self.mag_samps_to_avg = mag_samps_to_avg 113 | self.s_and_h_detector.set_mag_samps_to_avg(self.mag_samps_to_avg) 114 | 115 | def get_thresh(self): 116 | return self.thresh 117 | 118 | def set_thresh(self, thresh): 119 | self.thresh = thresh 120 | self.s_and_h_detector.set_thresh(self.thresh) 121 | -------------------------------------------------------------------------------- /python/fhss_utils/fft_peak.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2018, 2019, 2020 National Technology & Engineering Solutions of Sandia, LLC 5 | # (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 6 | # retains certain rights in this software. 7 | # 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | # 10 | 11 | from gnuradio import gr 12 | from gnuradio import blocks 13 | from gnuradio import fft 14 | from gnuradio.fft import window 15 | from gnuradio.filter import firdes 16 | 17 | 18 | class fft_peak(gr.hier_block2): 19 | """ 20 | This block returns the peak magnitude FFT index 21 | """ 22 | 23 | def __init__(self, fft_len): 24 | gr.hier_block2.__init__(self, 25 | "FFT Peak", 26 | gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), 27 | gr.io_signaturev(2, 2, [gr.sizeof_float * 1, gr.sizeof_float * 1])) 28 | ''' 29 | Constructor 30 | 31 | @param fft_len - 32 | ''' 33 | 34 | ################################################## 35 | # Parameters 36 | ################################################## 37 | self.fft_len = fft_len 38 | 39 | ################################################## 40 | # Blocks 41 | ################################################## 42 | self.fft = fft.fft_vcc(fft_len, True, (window.blackmanharris(fft_len)), False, 1) 43 | self.v2s = blocks.vector_to_stream(gr.sizeof_float * 1, fft_len) 44 | self.s2v = blocks.stream_to_vector(gr.sizeof_gr_complex * 1, fft_len) 45 | self.s2f = blocks.short_to_float(1, 1) 46 | self.repeat = blocks.repeat(gr.sizeof_short * 1, fft_len) 47 | self.null_0 = blocks.null_sink(gr.sizeof_float * 1) 48 | self.null_1 = blocks.null_sink(gr.sizeof_short * 1) 49 | self.c2mag = blocks.complex_to_mag(fft_len) 50 | self.argmax = blocks.argmax_fs(fft_len) 51 | 52 | ################################################## 53 | # Connections 54 | ################################################## 55 | self.connect((self.argmax, 1), (self.null_1, 0)) 56 | self.connect((self.argmax, 0), (self.repeat, 0)) 57 | self.connect((self.c2mag, 0), (self.argmax, 0)) 58 | self.connect((self.c2mag, 0), (self.v2s, 0)) 59 | self.connect((self.repeat, 0), (self.s2f, 0)) 60 | self.connect((self.s2f, 0), (self, 0)) 61 | self.connect((self.s2v, 0), (self.fft, 0)) 62 | self.connect((self.v2s, 0), (self.null_0, 0)) 63 | self.connect((self.v2s, 0), (self, 1)) 64 | self.connect((self.fft, 0), (self.c2mag, 0)) 65 | self.connect((self, 0), (self.s2v, 0)) 66 | 67 | def get_fft_len(self): 68 | return self.fft_len 69 | 70 | def set_fft_len(self, fft_len): 71 | self.fft_len = fft_len 72 | self.repeat.set_interpolation(self.fft_len) 73 | -------------------------------------------------------------------------------- /python/fhss_utils/fine_dehopper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2018, 2019, 2020 National Technology & Engineering Solutions of Sandia, LLC 5 | # (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 6 | # retains certain rights in this software. 7 | # 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | # 10 | 11 | from gnuradio import gr 12 | from gnuradio import analog 13 | from gnuradio import blocks 14 | from gnuradio import filter 15 | from gnuradio.filter import firdes 16 | from gnuradio.filter import pfb 17 | from math import pi 18 | import math 19 | from .s_and_h_detector import s_and_h_detector # hier_block 20 | 21 | 22 | class fine_dehopper(gr.hier_block2): 23 | """ 24 | Fine Dehopper module 25 | """ 26 | 27 | def __init__(self, bias, freq_sample_delay_samps, freq_samps_to_avg, mag_samps_to_avg, resamp_rate, thresh): 28 | gr.hier_block2.__init__(self, 29 | "Fine Dehopper", 30 | gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), 31 | gr.io_signature(1, 1, gr.sizeof_gr_complex * 1)) 32 | ''' 33 | Constructor 34 | 35 | @param bias - 36 | @param freq_sample_delay_samps - 37 | @param freq_samps_to_avg - 38 | @param mag_samps_to_avg - 39 | @param resamp_rate - 40 | @param thresh - 41 | ''' 42 | 43 | ################################################## 44 | # Parameters 45 | ################################################## 46 | self.bias = bias 47 | self.freq_sample_delay_samps = freq_sample_delay_samps 48 | self.freq_samps_to_avg = freq_samps_to_avg 49 | self.mag_samps_to_avg = mag_samps_to_avg 50 | self.resamp_rate = resamp_rate 51 | self.thresh = thresh 52 | 53 | ################################################## 54 | # Blocks 55 | ################################################## 56 | self.s_and_h_detector = s_and_h_detector( 57 | freq_sample_delay_samps=freq_sample_delay_samps, 58 | freq_samps_to_avg=freq_samps_to_avg, 59 | mag_samps_to_avg=mag_samps_to_avg, 60 | thresh=thresh, 61 | ) 62 | self.resamp = pfb.arb_resampler_ccf(resamp_rate * 2.0, taps=None, flt_size=32) 63 | self.resamp.declare_sample_delay(0) 64 | self.fir = filter.fir_filter_ccc(2, (firdes.low_pass_2(1, 1, .25, .05, 60))) 65 | self.fir.declare_sample_delay(0) 66 | self.vco = blocks.vco_c(1, 1, 1) 67 | self.mult_conj = blocks.multiply_conjugate_cc(1) 68 | self.delay = blocks.delay(gr.sizeof_gr_complex * 1, int(freq_samps_to_avg) + freq_sample_delay_samps) 69 | self.c2mag = blocks.complex_to_mag(1) 70 | self.add_const = blocks.add_const_vff((-1.0 * bias * (resamp_rate), )) 71 | self.demod = analog.quadrature_demod_cf(1) 72 | 73 | ################################################## 74 | # Connections 75 | ################################################## 76 | self.connect((self.demod, 0), (self.s_and_h_detector, 1)) 77 | self.connect((self.add_const, 0), (self.vco, 0)) 78 | self.connect((self.c2mag, 0), (self.s_and_h_detector, 0)) 79 | self.connect((self.delay, 0), (self.mult_conj, 0)) 80 | self.connect((self.mult_conj, 0), (self.fir, 0)) 81 | self.connect((self.vco, 0), (self.mult_conj, 1)) 82 | self.connect((self.fir, 0), (self.resamp, 0)) 83 | self.connect((self, 0), (self.demod, 0)) 84 | self.connect((self, 0), (self.c2mag, 0)) 85 | self.connect((self, 0), (self.delay, 0)) 86 | self.connect((self.resamp, 0), (self, 0)) 87 | self.connect((self.s_and_h_detector, 0), (self.add_const, 0)) 88 | 89 | def get_bias(self): 90 | return self.bias 91 | 92 | def set_bias(self, bias): 93 | self.bias = bias 94 | self.add_const.set_k((-1.0 * self.bias * (self.resamp_rate), )) 95 | 96 | def get_freq_sample_delay_samps(self): 97 | return self.freq_sample_delay_samps 98 | 99 | def set_freq_sample_delay_samps(self, freq_sample_delay_samps): 100 | self.freq_sample_delay_samps = freq_sample_delay_samps 101 | self.s_and_h_detector.set_freq_sample_delay_samps(self.freq_sample_delay_samps) 102 | self.delay.set_dly(int(self.freq_samps_to_avg) + self.freq_sample_delay_samps) 103 | 104 | def get_freq_samps_to_avg(self): 105 | return self.freq_samps_to_avg 106 | 107 | def set_freq_samps_to_avg(self, freq_samps_to_avg): 108 | self.freq_samps_to_avg = freq_samps_to_avg 109 | self.s_and_h_detector.set_freq_samps_to_avg(self.freq_samps_to_avg) 110 | self.delay.set_dly(int(self.freq_samps_to_avg) + self.freq_sample_delay_samps) 111 | 112 | def get_mag_samps_to_avg(self): 113 | return self.mag_samps_to_avg 114 | 115 | def set_mag_samps_to_avg(self, mag_samps_to_avg): 116 | self.mag_samps_to_avg = mag_samps_to_avg 117 | self.s_and_h_detector.set_mag_samps_to_avg(self.mag_samps_to_avg) 118 | 119 | def get_resamp_rate(self): 120 | return self.resamp_rate 121 | 122 | def set_resamp_rate(self, resamp_rate): 123 | self.resamp_rate = resamp_rate 124 | self.resamp.set_rate(self.resamp_rate * 2.0) 125 | self.add_const.set_k((-1.0 * self.bias * (self.resamp_rate), )) 126 | 127 | def get_thresh(self): 128 | return self.thresh 129 | 130 | def set_thresh(self, thresh): 131 | self.thresh = thresh 132 | self.s_and_h_detector.set_thresh(self.thresh) 133 | -------------------------------------------------------------------------------- /python/fhss_utils/fsk_burst_extractor_hier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- # 3 | # 4 | # Copyright 2018, 2019, 2020 National Technology & Engineering Solutions of Sandia, LLC 5 | # (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 6 | # retains certain rights in this software. 7 | # 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | # 10 | 11 | from gnuradio import gr 12 | from gnuradio import blocks 13 | from gnuradio.filter import firdes 14 | from gnuradio import fhss_utils 15 | from gnuradio.fhss_utils import RMS, HALF_POWER, COERCE 16 | from gnuradio import pdu_utils 17 | 18 | 19 | class fsk_burst_extractor_hier(gr.hier_block2): 20 | """ 21 | The burst extractor block isolates bursts in time and frequency and generates 22 | individual bursts that have been basedbanded and decimated. The underlying 23 | block structure is: 24 | 25 | 26 | |-------| |--------| |--------| |--------| |---------| 27 | | FFT | | Tagged | | CF | | FIR | | Fine | 28 | In-->| Burst |-->|Burst To|-->|estimate|-->| Filter |-->| Time |--> Out 29 | | Tagger| | PDU | | | | | | Measure | 30 | |-------| |--------| |--------| |--------| |---------| 31 | 32 | 33 | The signal processing performed on each burst after a signal has been detected 34 | and a center frequency has been determined is 35 | 36 | |---------| |------------| |----------| |---------| 37 | | CORDIC | | LPF | | | | LPF | 38 | In---->| Tune |---->| fc =.45*fs |---->| Decimate |---->| fc = b |----> Out 39 | | | | | | | | | 40 | |---------| |------------| |----------| |---------| 41 | 42 | 43 | where 44 | fs = input sampling rate 45 | fc = single-sided filter cutoff bandwidth 46 | LPF = Low-pass filter 47 | N = decimation factor 48 | b = specified output cutoff (normalized to output sampling rate of fs / N) 49 | 50 | 51 | Frequency Estimation: 52 | The initial burst tagging stage does a coarse frequency estimation based on 53 | bin energy. The fine_burst_measure block is responsible for refining the 54 | signal center frequency estimate. If a set of channel center frequencies is 55 | not specified, the frequency estimation uses a frequency histogram-based method 56 | to determine the signal center frequency. 57 | 58 | Parameters: 59 | * CFO Samps To Average: Block processing size (no overlap) 60 | * CFO Bin Resolution: Bin frequency resolution (Hz) 61 | * Channel Center Frequencies: Specified channel center frequencies. If 62 | specified, each detected burst will be coerced to the nearest channel 63 | center frequency 64 | """ 65 | 66 | def __init__(self, burst_width=int(500e3), center_freq=915e6, decimation=32, 67 | fft_size=256, hist_time=0.004, lookahead_time=0.0005, 68 | max_burst_time=0.5, min_burst_time=0.001, 69 | output_attenuation=40, output_cutoff=0.25, 70 | output_trans_width=0.1, post_burst_time=0.00008, 71 | pre_burst_time=0.00008, samp_rate=int(16e6), 72 | threshold=10, 73 | cf_method=RMS, channel_freqs=[], 74 | n_threads=3): 75 | gr.hier_block2.__init__( 76 | self, "FSK Burst Extractor Hier", 77 | gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), 78 | gr.io_signature(0, 0, 0), 79 | ) 80 | ''' 81 | Constructor 82 | 83 | @param burst_width - max burst bandwidth, Hz 84 | @param center_freq - 85 | @param decimation - 86 | @param fft_size - 87 | @param hist_time - 88 | @param lookahead_time - 89 | @param max_burst_time - 90 | @param min_burst_time - 91 | @param output_attenuation - 92 | @param output_cutoff - 93 | @param output_trans_width - 94 | @param post_burst_time - 95 | @param pre_burst_time - 96 | @param samp_rate - 97 | @param threshold - 98 | @param cf_method - Center Frequency estimation method 99 | @param channel_freqs - CF Coerce freq list 100 | @param n_threads - 101 | ''' 102 | 103 | self.message_port_register_hier_out("pdu_out") 104 | 105 | ################################################## 106 | # Parameters 107 | ################################################## 108 | self.burst_width = burst_width 109 | self.center_freq = center_freq 110 | self.decimation = decimation 111 | self.fft_size = fft_size 112 | self.hist_time = hist_time 113 | self.lookahead_time = lookahead_time 114 | self.max_burst_time = max_burst_time 115 | self.min_burst_time = min_burst_time 116 | self.output_attenuation = output_attenuation 117 | self.output_cutoff = output_cutoff 118 | self.output_trans_width = output_trans_width 119 | self.post_burst_time = post_burst_time 120 | self.pre_burst_time = pre_burst_time 121 | self.samp_rate = samp_rate 122 | self.threshold = threshold 123 | self.channel_freqs = channel_freqs 124 | self.cf_method = cf_method 125 | self.n_threads = n_threads 126 | 127 | ################################################## 128 | # Blocks 129 | ################################################## 130 | # Low pass filter cutoff to half band. 131 | sig_taps = firdes.low_pass_2(1, 1, output_cutoff, output_trans_width, output_attenuation) 132 | self.pdu_utils_pdu_fir_filter_1 = pdu_utils.pdu_fir_filter(1, sig_taps) 133 | 134 | # This is a coarse filter, Allow for transition band to alias onto itself. 135 | taps = firdes.low_pass_2(1, 1, .45 / decimation, .1 / decimation, output_attenuation) 136 | self.fhss_utils_tagged_burst_to_pdu_0 = fhss_utils.tagged_burst_to_pdu( 137 | decimation, taps, min_burst_time, max_burst_time, 0.0, 1.0, 1.0, samp_rate, n_threads) 138 | self.fhss_utils_fft_burst_tagger_0 = fhss_utils.fft_burst_tagger(center_freq, fft_size, samp_rate, int(round((float(samp_rate) / fft_size) * pre_burst_time)), int(round((float( 139 | samp_rate) / fft_size) * post_burst_time)), burst_width, 0, 0, threshold, int(round((float(samp_rate) / fft_size) * hist_time)), int(round((float(samp_rate) / fft_size) * lookahead_time)), False) 140 | #(self.fhss_utils_fft_burst_tagger_0).set_min_output_buffer(102400) 141 | self.cf_estimate = fhss_utils.cf_estimate(self.cf_method, self.channel_freqs) 142 | 143 | self.fine_time_measure = pdu_utils.pdu_fine_time_measure(pre_burst_time, post_burst_time, 10, 15) 144 | 145 | ################################################## 146 | # Connections 147 | ################################################## 148 | #self.msg_connect((self.pdu_utils_pdu_fir_filter_1, 'pdu_out'), (self, 'pdu_out')) 149 | self.msg_connect((self.fine_time_measure, 'pdu_out'), (self, 'pdu_out')) 150 | self.msg_connect((self.pdu_utils_pdu_fir_filter_1, 'pdu_out'), (self.fine_time_measure, 'pdu_in')) 151 | self.msg_connect((self.cf_estimate, 'out'), (self.pdu_utils_pdu_fir_filter_1, 'pdu_in')) 152 | self.msg_connect((self.fhss_utils_tagged_burst_to_pdu_0, 'cpdus'), (self.cf_estimate, 'in')) 153 | self.connect((self.fhss_utils_fft_burst_tagger_0, 0), (self.fhss_utils_tagged_burst_to_pdu_0, 0)) 154 | self.connect((self, 0), (self.fhss_utils_fft_burst_tagger_0, 0)) 155 | 156 | def reset(self): 157 | self.fhss_utils_fft_burst_tagger_0.reset() 158 | 159 | def set_channel_freqs(self, freqs): 160 | self.cf_estimate.set_channel_freqs(freqs) 161 | 162 | def set_cf_method(self, cf_method): 163 | self.cf_estimate.set_method(cf_method) 164 | 165 | def set_threshold(self, threshold): 166 | self.fhss_utils_fft_burst_tagger_0.set_threshold(threshold) 167 | 168 | def get_burst_width(self): 169 | return self.burst_width 170 | 171 | def get_center_freq(self): 172 | return self.center_freq 173 | 174 | def get_decimation(self): 175 | return self.decimation 176 | 177 | def get_fft_size(self): 178 | return self.fft_size 179 | 180 | def get_hist_time(self): 181 | return self.hist_time 182 | 183 | def get_lookahead_time(self): 184 | return self.lookahead_time 185 | 186 | def get_max_burst_time(self): 187 | return self.max_burst_time 188 | 189 | def get_min_burst_time(self): 190 | return self.min_burst_time 191 | 192 | def get_output_attenuation(self): 193 | return self.output_attenuation 194 | 195 | def get_output_cutoff(self): 196 | return self.output_cutoff 197 | 198 | def get_output_trans_width(self): 199 | return self.output_trans_width 200 | 201 | def get_post_burst_time(self): 202 | return self.post_burst_time 203 | 204 | def get_pre_burst_time(self): 205 | return self.pre_burst_time 206 | 207 | def get_samp_rate(self): 208 | return self.samp_rate 209 | 210 | def get_threshold(self): 211 | return self.threshold 212 | -------------------------------------------------------------------------------- /python/fhss_utils/qa_cf_estimate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 5 | # (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 6 | # retains certain rights in this software. 7 | # 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | # 10 | 11 | from gnuradio import gr, gr_unittest 12 | from gnuradio import blocks 13 | from gnuradio.filter import firdes 14 | import numpy as np 15 | from gnuradio import pdu_utils 16 | import pmt 17 | import time 18 | from math import pi 19 | try: 20 | from gnuradio import fhss_utils 21 | except ImportError: 22 | import os 23 | import sys 24 | dirname, filename = os.path.split(os.path.abspath(__file__)) 25 | sys.path.append(os.path.join(dirname, "bindings")) 26 | from gnuradio import fhss_utils 27 | 28 | 29 | class qa_cf_estimate (gr_unittest.TestCase): 30 | 31 | class simple_modulator(gr.hier_block2): 32 | def __init__(self, sps=8, bt=0.5, mod_idx=0.68): 33 | gr.hier_block2.__init__(self, 34 | "simple_modulator", 35 | gr.io_signature(0, 0, 0), # Input signature 36 | gr.io_signature(0, 0, 0)) # Output signature 37 | 38 | # message ports 39 | self.message_port_register_hier_in("in") 40 | self.message_port_register_hier_out("out") 41 | 42 | # blocks 43 | self.pack = pdu_utils.pack_unpack(pdu_utils.MODE_UNPACK_BYTE, pdu_utils.BIT_ORDER_LSB_FIRST) 44 | self.preamble = pdu_utils.pdu_preamble([], [], sps, 0) 45 | modulation_index = mod_idx 46 | sensitivity = (pi * modulation_index) / sps 47 | gain = 1.0 48 | taps = firdes.gaussian(gain, sps, bt, 5) 49 | self.gmsk = pdu_utils.pdu_gmsk_fc(sensitivity, taps) 50 | 51 | # connections 52 | self.msg_connect(self, "in", self.pack, "pdu_in") 53 | self.msg_connect(self.pack, "pdu_out", self.preamble, "pdu_in") 54 | self.msg_connect(self.preamble, "pdu_out", self.gmsk, "pdu_in") 55 | self.msg_connect(self.gmsk, "pdu_out", self, "out") 56 | 57 | class pdu_rotate(gr.sync_block): 58 | def __init__(self, rotate): 59 | gr.sync_block.__init__(self, "pdu_rotate", in_sig=None, out_sig=None) 60 | self.rotate = rotate 61 | self.message_port_register_in(pmt.intern("in")) 62 | self.set_msg_handler(pmt.intern("in"), self.msg_handler) 63 | self.message_port_register_out(pmt.intern("out")) 64 | 65 | def msg_handler(self, pdu): 66 | meta = pmt.car(pdu) 67 | data = pmt.c32vector_elements(pmt.cdr(pdu)) 68 | data = [d * r for d, r in zip(data, np.exp(1j * np.linspace(0, 2 * np.pi * self.rotate * len(data), len(data))))] 69 | self.message_port_pub(pmt.intern("out"), pmt.cons(meta, pmt.init_c32vector(len(data), data))) 70 | 71 | def setUp(self): 72 | self.tb = gr.top_block() 73 | 74 | def tearDown(self): 75 | self.tb = None 76 | 77 | def test_input_sanitization(self): 78 | samp_rate = 1e6 79 | freq_offset = -400e3 80 | center_freq = 911e6 81 | 82 | # blocks 83 | self.emitter = pdu_utils.message_emitter() 84 | self.cf = fhss_utils.cf_estimate(fhss_utils.COERCE, [x * 1e6 for x in range(900, 930)]) 85 | self.debug = blocks.message_debug() 86 | 87 | # connections 88 | self.tb.msg_connect((self.emitter, 'msg'), (self.cf, 'in')) 89 | self.tb.msg_connect((self.cf, 'out'), (self.debug, 'store')) 90 | 91 | # data 92 | in_data = (1 + 0j,) * 2048 93 | i_vec = pmt.init_c32vector(len(in_data), in_data) 94 | 95 | meta = pmt.make_dict() 96 | meta = pmt.dict_add(meta, pmt.intern("sample_rate"), pmt.from_float(samp_rate)) 97 | meta = pmt.dict_add(meta, pmt.intern("center_freq"), pmt.from_float(center_freq + freq_offset)) 98 | in_pdu = pmt.cons(meta, i_vec) 99 | 100 | # flowgraph 101 | self.tb.start() 102 | time.sleep(.001) 103 | self.emitter.emit(pmt.PMT_T) 104 | time.sleep(.01) 105 | self.emitter.emit(pmt.cons(pmt.PMT_T, pmt.PMT_NIL)) 106 | time.sleep(.01) 107 | self.emitter.emit(meta) 108 | time.sleep(.01) 109 | self.emitter.emit(i_vec) 110 | time.sleep(.01) 111 | self.emitter.emit(in_pdu) 112 | time.sleep(.01) 113 | self.tb.stop() 114 | self.tb.wait() 115 | 116 | self.assertEqual(0, self.debug.num_messages()) 117 | 118 | def test_coerce(self): 119 | samp_rate = 1e6 120 | freq_offset = -400e3 121 | center_freq = 911e6 122 | 123 | # blocks 124 | self.emitter = pdu_utils.message_emitter() 125 | self.cf = fhss_utils.cf_estimate(fhss_utils.COERCE, [x * 1e6 for x in range(900, 930)]) 126 | self.debug = blocks.message_debug() 127 | 128 | # connections 129 | self.tb.msg_connect((self.emitter, 'msg'), (self.cf, 'in')) 130 | self.tb.msg_connect((self.cf, 'out'), (self.debug, 'store')) 131 | 132 | # data 133 | in_data = (1 + 0j,) * 2048 134 | i_vec = pmt.init_c32vector(len(in_data), in_data) 135 | out_data = np.exp(1j * np.arange(0, 2 * np.pi * (freq_offset / samp_rate * len(in_data)), 136 | 2 * np.pi * (freq_offset / samp_rate), dtype=np.complex64)) 137 | e_vec = pmt.init_c32vector(len(out_data), out_data.tolist()) # pmt doesn't play nice with numpy sometimes, convert to list 138 | 139 | meta = pmt.make_dict() 140 | meta = pmt.dict_add(meta, pmt.intern("sample_rate"), pmt.from_float(samp_rate)) 141 | meta = pmt.dict_add(meta, pmt.intern("center_frequency"), pmt.from_float(center_freq + freq_offset)) 142 | in_pdu = pmt.cons(meta, i_vec) 143 | e_pdu = pmt.cons(meta, e_vec) 144 | 145 | # flowgraph 146 | self.tb.start() 147 | time.sleep(.001) 148 | self.emitter.emit(in_pdu) 149 | time.sleep(.01) 150 | self.tb.stop() 151 | self.tb.wait() 152 | 153 | # parse output 154 | #print "got ", list(pmt.to_python(pmt.cdr(self.debug.get_message(0)))) 155 | #print "got ", self.debug.get_message(0) 156 | rcv = self.debug.get_message(0) 157 | rcv_meta = pmt.car(rcv) 158 | rcv_data = pmt.cdr(rcv) 159 | rcv_cf = pmt.dict_ref(rcv_meta, pmt.intern("center_frequency"), pmt.PMT_NIL) 160 | 161 | # asserts 162 | self.assertComplexTuplesAlmostEqual(tuple(pmt.c32vector_elements(rcv_data)), tuple(out_data), 2) 163 | self.assertTrue(pmt.equal(rcv_cf, pmt.from_float(911e6))) 164 | 165 | def test_rms_metadata(self): 166 | samp_rate = 1e6 167 | rot_offset = 1.0 / 16.0 168 | self.emitter = pdu_utils.message_emitter() 169 | self.cf = fhss_utils.cf_estimate(fhss_utils.RMS, []) 170 | self.sm = self.simple_modulator() 171 | self.rt = self.pdu_rotate(rot_offset) 172 | self.debug = blocks.message_debug() 173 | 174 | self.tb.msg_connect((self.emitter, 'msg'), (self.sm, 'in')) 175 | self.tb.msg_connect((self.sm, 'out'), (self.rt, 'in')) 176 | self.tb.msg_connect((self.rt, 'out'), (self.cf, 'in')) 177 | self.tb.msg_connect((self.cf, 'out'), (self.debug, 'store')) 178 | 179 | # original data 180 | in_data = [0xAA] * 10 + [0x69] * 10 + [0x55] * 10 # 30 bytes = 240 bits = 1920 samples 181 | i_vec = pmt.init_u8vector(len(in_data), in_data) 182 | fc = 100e6 183 | 184 | meta = pmt.make_dict() 185 | meta = pmt.dict_add(meta, pmt.intern("sample_rate"), pmt.from_float(1e6)) 186 | meta = pmt.dict_add(meta, pmt.intern("center_frequency"), pmt.from_float(fc)) 187 | in_pdu = pmt.cons(meta, i_vec) 188 | 189 | self.tb.start() 190 | time.sleep(.1) 191 | self.emitter.emit(in_pdu) 192 | time.sleep(.1) 193 | self.tb.stop() 194 | self.tb.wait() 195 | 196 | # parse output 197 | rcv = self.debug.get_message(0) 198 | rcv_meta = pmt.car(rcv) 199 | rcv_data = pmt.cdr(rcv) 200 | 201 | # asserts 202 | rcv_cf = pmt.to_double(pmt.dict_ref(rcv_meta, pmt.intern("center_frequency"), pmt.PMT_NIL)) 203 | expected_cf = 100e6 + samp_rate * rot_offset # we rotated the burst 62500 Hz off 204 | max_diff = samp_rate / 256 / 2 # half an FFT bin error allowed 205 | #print("RMS: got ", rcv_cf - fc, " , expected ", expected_cf-fc, "(diff ", rcv_cf-expected_cf, ")") 206 | self.assertTrue(abs(rcv_cf - expected_cf) < max_diff) 207 | 208 | def test_half_power_metadata(self): 209 | samp_rate = 1e6 210 | rot_offset = -4.0 / 16.0 211 | self.emitter = pdu_utils.message_emitter() 212 | self.cf = fhss_utils.cf_estimate(fhss_utils.HALF_POWER, []) 213 | self.sm = self.simple_modulator() 214 | self.rt = self.pdu_rotate(rot_offset) 215 | self.debug = blocks.message_debug() 216 | 217 | self.tb.msg_connect((self.emitter, 'msg'), (self.sm, 'in')) 218 | self.tb.msg_connect((self.sm, 'out'), (self.rt, 'in')) 219 | self.tb.msg_connect((self.rt, 'out'), (self.cf, 'in')) 220 | self.tb.msg_connect((self.cf, 'out'), (self.debug, 'store')) 221 | 222 | # original data 223 | in_data = [0xAA] * 10 + [0x69] * 10 + [0x55] * 10 # 30 bytes = 240 bits = 1920 samples 224 | i_vec = pmt.init_u8vector(len(in_data), in_data) 225 | fc = 100e6 226 | 227 | meta = pmt.make_dict() 228 | meta = pmt.dict_add(meta, pmt.intern("sample_rate"), pmt.from_float(1e6)) 229 | meta = pmt.dict_add(meta, pmt.intern("center_frequency"), pmt.from_float(fc)) 230 | in_pdu = pmt.cons(meta, i_vec) 231 | 232 | self.tb.start() 233 | time.sleep(.1) 234 | self.emitter.emit(in_pdu) 235 | time.sleep(.1) 236 | self.tb.stop() 237 | self.tb.wait() 238 | 239 | # parse output 240 | rcv = self.debug.get_message(0) 241 | rcv_meta = pmt.car(rcv) 242 | rcv_data = pmt.cdr(rcv) 243 | 244 | # asserts 245 | rcv_cf = pmt.to_double(pmt.dict_ref(rcv_meta, pmt.intern("center_frequency"), pmt.PMT_NIL)) 246 | expected_cf = 100e6 + samp_rate * rot_offset # we rotated the burst 62500 Hz off 247 | max_diff = samp_rate / 256 / 2 # half an FFT bin error allowed 248 | #print("H-P: got ", rcv_cf - fc, " , expected ", expected_cf-fc, "(diff ", rcv_cf-expected_cf, ")") 249 | self.assertTrue(abs(rcv_cf - expected_cf) < max_diff) 250 | 251 | def test_middle_out_metadata(self): 252 | samp_rate = 1e6 253 | rot_offset = 1.0 / 16.0 254 | self.emitter = pdu_utils.message_emitter() 255 | self.cf = fhss_utils.cf_estimate(fhss_utils.MIDDLE_OUT, []) 256 | self.sm = self.simple_modulator() 257 | self.rt = self.pdu_rotate(rot_offset) 258 | self.debug = blocks.message_debug() 259 | 260 | self.tb.msg_connect((self.emitter, 'msg'), (self.sm, 'in')) 261 | #self.tb.msg_connect((self.cf, 'debug'), (self.debug, 'print')) 262 | self.tb.msg_connect((self.sm, 'out'), (self.rt, 'in')) 263 | self.tb.msg_connect((self.rt, 'out'), (self.cf, 'in')) 264 | self.tb.msg_connect((self.cf, 'out'), (self.debug, 'store')) 265 | 266 | # original data 267 | in_data = [0xAA] * 10 + [0x69] * 10 + [0x55] * 10 # 30 bytes = 240 bits = 1920 samples 268 | i_vec = pmt.init_u8vector(len(in_data), in_data) 269 | fc = 100e6 270 | 271 | meta = pmt.make_dict() 272 | meta = pmt.dict_add(meta, pmt.intern("sample_rate"), pmt.from_float(1e6)) 273 | meta = pmt.dict_add(meta, pmt.intern("center_frequency"), pmt.from_float(fc)) 274 | meta = pmt.dict_add(meta, pmt.intern("noise_density"), pmt.from_float(-105.0)) 275 | in_pdu = pmt.cons(meta, i_vec) 276 | 277 | self.tb.start() 278 | time.sleep(.1) 279 | self.emitter.emit(in_pdu) 280 | time.sleep(.1) 281 | self.tb.stop() 282 | self.tb.wait() 283 | 284 | # parse output 285 | rcv = self.debug.get_message(0) 286 | rcv_meta = pmt.car(rcv) 287 | rcv_data = pmt.cdr(rcv) 288 | 289 | # asserts 290 | rcv_cf = pmt.to_double(pmt.dict_ref(rcv_meta, pmt.intern("center_frequency"), pmt.PMT_NIL)) 291 | expected_cf = 100e6 + samp_rate * rot_offset # we rotated the burst 62500 Hz off 292 | max_diff = samp_rate / 256 / 2 # half an FFT bin error allowed 293 | #print("M-O: got ", rcv_cf - fc, " expected ", expected_cf-fc, "(diff ", rcv_cf-expected_cf, ")") 294 | self.assertTrue(abs(rcv_cf - expected_cf) < max_diff) 295 | 296 | 297 | if __name__ == '__main__': 298 | gr_unittest.run(qa_cf_estimate) 299 | -------------------------------------------------------------------------------- /python/fhss_utils/qa_constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 5 | # (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 6 | # retains certain rights in this software. 7 | # 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | # 10 | 11 | try: 12 | from gnuradio import fhss_utils 13 | except ImportError: 14 | import os 15 | import sys 16 | dirname, filename = os.path.split(os.path.abspath(__file__)) 17 | sys.path.append(os.path.join(dirname, "bindings")) 18 | from gnuradio import fhss_utils 19 | 20 | from gnuradio import gr, gr_unittest 21 | from gnuradio import blocks 22 | import pmt 23 | 24 | 25 | class qa_constants (gr_unittest.TestCase): 26 | 27 | def setUp(self): 28 | self.tb = None 29 | 30 | def tearDown(self): 31 | self.tb = None 32 | 33 | def test_interned_string_constants(self): 34 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__in(), pmt.intern("in"))) 35 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__out(), pmt.intern("out"))) 36 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__center_frequency(), pmt.intern("center_frequency"))) 37 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__relative_frequency(), pmt.intern("relative_frequency"))) 38 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__sample_rate(), pmt.intern("sample_rate"))) 39 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__bandwidth(), pmt.intern("bandwidth"))) 40 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__pwr_db(), pmt.intern("pwr_db"))) 41 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__snr_db(), pmt.intern("snr_db"))) 42 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__debug(), pmt.intern("debug"))) 43 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__rx_freq(), pmt.intern("rx_freq"))) 44 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__burst_id(), pmt.intern("burst_id"))) 45 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__magnitude(), pmt.intern("magnitude"))) 46 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__noise_density(), pmt.intern("noise_density"))) 47 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__new_burst(), pmt.intern("new_burst"))) 48 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__gone_burst(), pmt.intern("gone_burst"))) 49 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__rx_time(), pmt.intern("rx_time"))) 50 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__start_time(), pmt.intern("start_time"))) 51 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__duration(), pmt.intern("duration"))) 52 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__cpdus(), pmt.intern("cpdus"))) 53 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__start_offset(), pmt.intern("start_offset"))) 54 | assert(pmt.eq(fhss_utils.PMTCONSTSTR__end_offset(), pmt.intern("end_offset"))) 55 | 56 | 57 | if __name__ == '__main__': 58 | gr_unittest.run(qa_constants) 59 | -------------------------------------------------------------------------------- /python/fhss_utils/qa_fft_burst_tagger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC 5 | # (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 6 | # retains certain rights in this software. 7 | # 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | # 10 | 11 | from gnuradio import gr, gr_unittest 12 | from gnuradio import blocks 13 | from gnuradio import pdu_utils 14 | import pmt 15 | import time 16 | try: 17 | from gnuradio import fhss_utils 18 | except ImportError: 19 | import os 20 | import sys 21 | dirname, filename = os.path.split(os.path.abspath(__file__)) 22 | sys.path.append(os.path.join(dirname, "bindings")) 23 | from gnuradio import fhss_utils 24 | 25 | 26 | class qa_fft_burst_tagger (gr_unittest.TestCase): 27 | 28 | def setUp(self): 29 | self.tb = gr.top_block() 30 | 31 | def tearDown(self): 32 | self.tb = None 33 | 34 | def test_001_instantiate(self): 35 | 36 | # data 37 | src_data = (1 + 1j, 2 + 2j, 3 + 3j) 38 | 39 | # blocks 40 | src = blocks.vector_source_c(src_data) 41 | dst = blocks.vector_sink_c() 42 | dut = fhss_utils.fft_burst_tagger(910.6e6, 1024, 1000000, 0, 0, 500000) 43 | 44 | # float center_freq, int fft_size, int sample_rate, int burst_pre_len, int burst_post_len, int burst_width, 45 | # int max_bursts, int max_burst_len, float threshold, int history_size, int lookahead, bool debug) 46 | 47 | self.tb.connect(src, dut) 48 | self.tb.connect(dut, dst) 49 | 50 | self.tb.start() 51 | 52 | 53 | if __name__ == '__main__': 54 | gr_unittest.run(qa_fft_burst_tagger, "qa_fft_burst_tagger.xml") 55 | -------------------------------------------------------------------------------- /python/fhss_utils/s_and_h_detector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2018, 2019, 2020 National Technology & Engineering Solutions of Sandia, LLC 5 | # (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government 6 | # retains certain rights in this software. 7 | # 8 | # SPDX-License-Identifier: GPL-3.0-or-later 9 | # 10 | 11 | from gnuradio import gr 12 | from gnuradio import blocks 13 | from gnuradio.filter import firdes 14 | from gnuradio import timing_utils 15 | 16 | 17 | class s_and_h_detector(gr.hier_block2): 18 | """ 19 | Sample and hold detector block. 20 | """ 21 | 22 | def __init__(self, freq_sample_delay_samps, freq_samps_to_avg, mag_samps_to_avg, thresh): 23 | gr.hier_block2.__init__(self, 24 | "Sample and Hold Detector", 25 | gr.io_signaturev(2, 2, [gr.sizeof_float * 1, gr.sizeof_float * 1]), 26 | gr.io_signaturev(4, 4, [gr.sizeof_float * 1, gr.sizeof_float * 1, gr.sizeof_float * 1, gr.sizeof_float * 1])) 27 | ''' 28 | Constructor 29 | 30 | @param freq_sample_delay_samps - 31 | @param freq_samps_to_avg - 32 | @param mag_samps_to_avg - 33 | @param thresh - 34 | 35 | ''' 36 | 37 | ################################################## 38 | # Parameters 39 | ################################################## 40 | self.freq_sample_delay_samps = freq_sample_delay_samps 41 | self.freq_samps_to_avg = freq_samps_to_avg 42 | self.mag_samps_to_avg = mag_samps_to_avg 43 | self.thresh = thresh 44 | 45 | ################################################## 46 | # Blocks 47 | ################################################## 48 | self.edge_detector = timing_utils.edge_detector_bb(timing_utils.RISING_EDGE) 49 | self.threshold = blocks.threshold_ff(thresh / 4.0, thresh, 0) 50 | self.samp_hold = blocks.sample_and_hold_ff() 51 | self.mag_avg = blocks.moving_average_ff(int(mag_samps_to_avg), 1.0 / (mag_samps_to_avg), 4000) 52 | self.freq_avg = blocks.moving_average_ff(int(freq_samps_to_avg), 1.0 / (freq_samps_to_avg), 4000) 53 | self.f2c = blocks.float_to_char(1, 1) 54 | self.delay = blocks.delay(gr.sizeof_float * 1, int(freq_samps_to_avg - mag_samps_to_avg + freq_sample_delay_samps)) 55 | 56 | ################################################## 57 | # Connections 58 | ################################################## 59 | self.connect((self.delay, 0), (self.mag_avg, 0)) 60 | self.connect((self.f2c, 0), (self.edge_detector, 0)) 61 | self.connect((self.freq_avg, 0), (self.samp_hold, 0)) 62 | self.connect((self.freq_avg, 0), (self, 1)) 63 | self.connect((self.mag_avg, 0), (self.threshold, 0)) 64 | self.connect((self.mag_avg, 0), (self, 3)) 65 | self.connect((self.samp_hold, 0), (self, 0)) 66 | self.connect((self.threshold, 0), (self.f2c, 0)) 67 | self.connect((self.threshold, 0), (self, 2)) 68 | self.connect((self, 0), (self.delay, 0)) 69 | self.connect((self, 1), (self.freq_avg, 0)) 70 | self.connect((self.edge_detector, 0), (self.samp_hold, 1)) 71 | 72 | def get_freq_sample_delay_samps(self): 73 | return self.freq_sample_delay_samps 74 | 75 | def set_freq_sample_delay_samps(self, freq_sample_delay_smps): 76 | self.freq_sample_delay_samps = freq_sample_delay_samps 77 | self.delay.set_dly(int(self.freq_samps_to_avg - self.mag_samps_to_avg + self.freq_sample_delay_samps)) 78 | 79 | def get_freq_samps_to_avg(self): 80 | return self.freq_samps_to_avg 81 | 82 | def set_freq_samps_to_avg(self, freq_samps_to_avg): 83 | self.freq_samps_to_avg = freq_samps_to_avg 84 | self.freq_avg.set_length_and_scale(int(self.freq_samps_to_avg), 1.0 / (self.freq_samps_to_avg)) 85 | self.delay.set_dly(int(self.freq_samps_to_avg - self.mag_samps_to_avg + self.freq_sample_delay_samps)) 86 | 87 | def get_mag_samps_to_avg(self): 88 | return self.mag_samps_to_avg 89 | 90 | def set_mag_samps_to_avg(self, mag_samps_to_avg): 91 | self.mag_samps_to_avg = mag_samps_to_avg 92 | self.mag_avg.set_length_and_scale(int(self.mag_samps_to_avg), 1.0 / (self.mag_samps_to_avg)) 93 | self.delay.set_dly(int(self.freq_samps_to_avg - self.mag_samps_to_avg + self.freq_sample_delay_samps)) 94 | 95 | def get_thresh(self): 96 | return self.thresh 97 | 98 | def set_thresh(self, thresh): 99 | self.thresh = thresh 100 | self.threshold.set_hi(self.thresh) 101 | self.threshold.set_lo(self.thresh / 4.0) 102 | -------------------------------------------------------------------------------- /python/fhss_utils/sigmf_meta_writer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2020 gr-fhss_utils author. 5 | # 6 | # This 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 | # This software 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 this software; 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 | import numpy 24 | import json 25 | import pmt 26 | from gnuradio import gr 27 | from os.path import splitext 28 | from math import isnan 29 | 30 | 31 | class sigmf_meta_writer(gr.basic_block): 32 | """ 33 | quick and dirty tool to convert detections to sigmf annotations 34 | """ 35 | 36 | def __init__(self, filename, freq, rate, label, dtype): 37 | gr.basic_block.__init__(self, 38 | name="sigmf_meta_writer", 39 | in_sig=None, 40 | out_sig=None) 41 | 42 | self.d_filename = filename 43 | if not filename.endswith('.sigmf-meta'): 44 | pre, ext = splitext(filename) 45 | self.d_filename = pre + '.sigmf-meta' 46 | gr.log.warn("SigMF metadata filename does not end with `sigmf-meta` - using " + self.d_filename) 47 | 48 | self.freq = freq 49 | self.rate = rate 50 | self.soo = 0 51 | self.bw_min = rate / 1000.0 52 | 53 | self.label = label 54 | 55 | self.initialize_sigmf_dict([{'core:sample_start': 0, 'core:frequency': freq}], 56 | {'core:datatype': dtype, 'core:sample_rate': rate, 'antenna:gain': 0}) 57 | 58 | self.message_port_register_in(pmt.intern("in")) 59 | self.set_msg_handler(pmt.intern("in"), self.handler) 60 | 61 | def stop(self): 62 | try: 63 | f = open(self.d_filename, 'w+') 64 | f.write(json.dumps(self.d_dict, indent=4)) 65 | f.close() 66 | except IOError as e: 67 | print("ERROR: could write to {}".format(self.d_filename), "because", e) 68 | quit() 69 | 70 | return True 71 | 72 | def initialize_sigmf_dict(self, sigmf_captures, sigmf_global, sigmf_annotations=[]): 73 | self.d_dict = {} 74 | self.d_dict['captures'] = sigmf_captures 75 | self.d_dict['global'] = sigmf_global 76 | self.d_dict['annotations'] = sigmf_annotations 77 | 78 | def handler(self, pdu): 79 | if not pmt.is_pdu(pdu): 80 | print('input is not a PDU!, dropping') 81 | 82 | # there are two basic modes here: tags_to_pdu or fft burst detector 83 | # in either case we need to extract the following fields for the annotation: 84 | # - sob: start sample of the burst 85 | # - eob: end sample of the burst 86 | # - freq: center frequency of the burst in hz 87 | # - bw: bandwidth of the burst in hz 88 | # - b_id: unique Identifier for the burst or `None` 89 | # - snr: signal to noise ratio of annotation or 'None' 90 | # 91 | # how these are obtained differs between the two modes. 92 | 93 | meta = pmt.car(pdu) 94 | 95 | time_pmt = pmt.dict_ref(meta, pmt.intern('burst_time'), pmt.PMT_NIL) 96 | if pmt.is_tuple(time_pmt): 97 | # tags_to_pdu mode 98 | try: 99 | burst_time = pmt.to_double(pmt.tuple_ref(time_pmt, 1)) + pmt.to_uint64(pmt.tuple_ref(time_pmt, 0)) 100 | pdu_rate = pmt.to_double(pmt.dict_ref(meta, pmt.intern('sample_rate'), pmt.from_double(self.rate))) 101 | freq = pmt.to_double(pmt.dict_ref(meta, pmt.intern('center_frequency'), pmt.from_double(self.freq))) 102 | bw = pmt.to_double(pmt.dict_ref(meta, pmt.intern('bandwidth'), pmt.from_double(self.bw_min))) 103 | 104 | if bw < self.bw_min: 105 | bw = self.bw_min 106 | 107 | # these can be `None` so use to_python() 108 | snr = pmt.to_python(pmt.dict_ref(meta, pmt.intern('snr_db'), pmt.PMT_NIL)) 109 | b_id = pmt.to_python(pmt.dict_ref(meta, pmt.intern('pdu_num'), pmt.PMT_NIL)) 110 | 111 | anno_len = int(pmt.length(pmt.cdr(pdu)) * (self.rate / pdu_rate)) 112 | sob = int(self.rate * burst_time) 113 | eob = sob + anno_len 114 | 115 | except Exception as e: 116 | print('could not parse required data from message', pmt.car(pdu), ':', e) 117 | return 118 | 119 | else: 120 | # fft burst detector mode 121 | try: 122 | sob = pmt.to_uint64(pmt.dict_ref(meta, pmt.intern('start_offset'), pmt.PMT_NIL)) 123 | eob = pmt.to_uint64(pmt.dict_ref(meta, pmt.intern('end_offset'), pmt.PMT_NIL)) 124 | freq = pmt.to_double(pmt.dict_ref(meta, pmt.intern('center_frequency'), pmt.PMT_NIL)) 125 | bw = pmt.to_double(pmt.dict_ref(meta, pmt.intern('bandwidth'), pmt.from_double(self.bw_min))) 126 | 127 | if bw < self.bw_min: 128 | bw = self.bw_min 129 | 130 | # these can be `None` so use to_python() 131 | snr = pmt.to_python(pmt.dict_ref(meta, pmt.intern('snr_db'), pmt.PMT_NIL)) 132 | b_id = pmt.to_python(pmt.dict_ref(meta, pmt.intern('burst_id'), pmt.PMT_NIL)) 133 | 134 | except Exception as e: 135 | print('could not parse required data from message', pmt.car(pdu), ':', e) 136 | return 137 | 138 | label = self.label 139 | if self.label == 'use_burst_id': 140 | if b_id is None: 141 | label = '' 142 | else: 143 | label = 'burst' + str(b_id) 144 | 145 | elif self.label == 'use_snr_db': 146 | # this probably isnt in here so it will end up blank... 147 | label = str(snr) + 'dB' 148 | 149 | # append the annotation 150 | try: 151 | if isnan(snr): 152 | print("Got illegal SNR value in", meta) 153 | self.d_dict['annotations'].append({'core:sample_start': sob - self.soo, 154 | 'core:sample_count': eob - sob, 'core:freq_upper_edge': int(freq + bw / 2), 155 | 'core:freq_lower_edge': int(freq - bw / 2), 'core:description': label}) 156 | else: 157 | self.d_dict['annotations'].append({'core:sample_start': sob - self.soo, 158 | 'core:sample_count': eob - sob, 'core:freq_upper_edge': int(freq + bw / 2), 159 | 'core:freq_lower_edge': int(freq - bw / 2), 'core:description': label, 160 | 'capture_details:SNRdB': snr}) 161 | except Exception as e: 162 | print('could not form annotation from message', pmt.car(pdu), ':', e) 163 | --------------------------------------------------------------------------------