├── .github └── workflows │ └── ci.yml ├── CMakeLists.txt ├── LICENSE_1_0.txt ├── MultiNameUtils.hpp ├── README.md ├── Registration.cpp ├── Settings.cpp ├── SoapyMultiSDR.hpp ├── Streaming.cpp └── TestMultiNameUtils.cpp /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | linux-ci: 5 | name: Linux 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | build_type: [Release, Debug] 10 | config: 11 | - cc: gcc-9 12 | cxx: g++-9 13 | os: ubuntu-20.04 14 | 15 | - cc: gcc-10 16 | cxx: g++-10 17 | os: ubuntu-20.04 18 | 19 | - cc: gcc-11 20 | cxx: g++-11 21 | os: ubuntu-22.04 22 | 23 | - cc: gcc-12 24 | cxx: g++-12 25 | os: ubuntu-22.04 26 | 27 | - cc: clang-10 28 | cxx: clang++-10 29 | os: ubuntu-20.04 30 | 31 | - cc: clang-11 32 | cxx: clang++-11 33 | os: ubuntu-20.04 34 | 35 | - cc: clang-12 36 | cxx: clang++-12 37 | os: ubuntu-20.04 38 | 39 | - cc: clang-13 40 | cxx: clang++-13 41 | os: ubuntu-22.04 42 | 43 | - cc: clang-14 44 | cxx: clang++-14 45 | os: ubuntu-22.04 46 | runs-on: ${{matrix.config.os}} 47 | env: 48 | CC: ${{matrix.config.cc}} 49 | CXX: ${{matrix.config.cxx}} 50 | INSTALL_PREFIX: /usr/local 51 | steps: 52 | - uses: actions/checkout@v2 53 | - name: Install dependencies 54 | run: | 55 | # Build against earliest supported SoapySDR 56 | git clone https://github.com/pothosware/SoapySDR -b soapy-sdr-0.8.1 57 | mkdir SoapySDR/build 58 | cd SoapySDR/build 59 | cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DENABLE_PYTHON=OFF -DENABLE_PYTHON3=OFF .. 60 | sudo make install 61 | sudo ldconfig 62 | - name: Build SoapyMultiSDR 63 | run: | 64 | mkdir ${{github.workspace}}/build 65 | cd ${{github.workspace}}/build 66 | cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{github.workspace}} 67 | sudo make install 68 | - name: Test module registration 69 | run: | 70 | SoapySDRUtil --check=multi 71 | osx-ci: 72 | name: OS X 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | build_type: [Release, Debug] 77 | config: 78 | - cc: gcc-9 79 | cxx: g++-9 80 | os: macos-11 81 | 82 | - cc: gcc-10 83 | cxx: g++-10 84 | os: macos-11 85 | 86 | - cc: gcc-11 87 | cxx: g++-11 88 | os: macos-11 89 | 90 | - cc: clang 91 | cxx: clang++ 92 | os: macos-11 93 | 94 | # TODO: re-enable after MacOS Monterey Python issue fixed 95 | #- cc: clang 96 | # cxx: clang++ 97 | # os: macos-12 98 | runs-on: ${{matrix.config.os}} 99 | env: 100 | CC: ${{matrix.config.cc}} 101 | CXX: ${{matrix.config.cxx}} 102 | INSTALL_PREFIX: /usr/local 103 | steps: 104 | - uses: actions/checkout@v2 105 | - name: Install dependencies 106 | run: | 107 | # Build SoapySDR that supports OS X GCC modules 108 | git clone https://github.com/pothosware/SoapySDR 109 | cd SoapySDR 110 | git checkout f8d57652d12f9d212f373a81e493eba1a0b058c5 111 | mkdir build 112 | cd build 113 | cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} .. 114 | sudo make install 115 | - name: Build SoapyMultiSDR 116 | run: | 117 | mkdir ${{github.workspace}}/build 118 | cd ${{github.workspace}}/build 119 | cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{github.workspace}} 120 | sudo make install 121 | - name: Test module registration 122 | run: | 123 | SoapySDRUtil --check=multi 124 | windows-ci: 125 | name: Windows 126 | strategy: 127 | fail-fast: false 128 | matrix: 129 | build_type: [Release, Debug] 130 | config: 131 | # 132 | # MSVC 133 | # 134 | 135 | - cmake_config: -G "Visual Studio 14 2015" -A "Win32" 136 | arch: win32 137 | os: windows-2019 138 | msvc: true 139 | 140 | - cmake_config: -G "Visual Studio 14 2015" -A "x64" 141 | arch: x64 142 | os: windows-2019 143 | msvc: true 144 | 145 | - cmake_config: -G "Visual Studio 16 2019" -A "Win32" 146 | arch: win32 147 | os: windows-2019 148 | msvc: true 149 | 150 | - cmake_config: -G "Visual Studio 16 2019" -A "x64" 151 | arch: x64 152 | os: windows-2019 153 | msvc: true 154 | 155 | - cmake_config: -G "Visual Studio 17 2022" -A "Win32" 156 | arch: win32 157 | os: windows-2022 158 | msvc: true 159 | 160 | - cmake_config: -G "Visual Studio 17 2022" -A "x64" 161 | arch: x64 162 | os: windows-2022 163 | msvc: true 164 | 165 | # 166 | # MinGW (TODO: re-enable after fix) 167 | # 168 | 169 | - cmake_config: -G "MinGW Makefiles" 170 | os: windows-2019 171 | msvc: false 172 | 173 | # - cmake_config: -G "MinGW Makefiles" 174 | # os: windows-2022 175 | # msvc: false 176 | runs-on: ${{matrix.config.os}} 177 | env: 178 | INSTALL_PREFIX: 'C:\Program Files\SoapySDR' 179 | steps: 180 | - uses: actions/checkout@v2 181 | - uses: ilammy/msvc-dev-cmd@v1 182 | if: 183 | ${{matrix.config.msvc == true}} 184 | with: 185 | arch: ${{matrix.config.arch}} 186 | - name: Install SoapySDR 187 | run: | 188 | cd ${{runner.workspace}} 189 | 190 | # Build SoapySDR that supports OS X GCC modules 191 | git clone https://github.com/pothosware/SoapySDR 192 | cd SoapySDR 193 | git checkout f8d57652d12f9d212f373a81e493eba1a0b058c5 194 | mkdir build 195 | cd build 196 | cmake ${{matrix.config.cmake_config}} -DENABLE_PYTHON=OFF -DCMAKE_INSTALL_PREFIX="$Env:INSTALL_PREFIX" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} .. 197 | cmake --build . --config ${{matrix.build_type}} 198 | cmake --install . --config ${{matrix.build_type}} 199 | - name: Build SoapyMultiSDR 200 | run: | 201 | $Env:INCLUDE += ";$Env:INSTALL_PREFIX\include" 202 | $Env:LIB += ";$Env:INSTALL_PREFIX\lib" 203 | mkdir build 204 | cd build 205 | cmake ${{matrix.config.cmake_config}} -DCMAKE_INSTALL_PREFIX="$Env:INSTALL_PREFIX" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} .. 206 | cmake --build . --config ${{matrix.build_type}} 207 | cmake --install . --config ${{matrix.build_type}} 208 | - name: Test module registration 209 | run: | 210 | $Env:PATH += ";$Env:INSTALL_PREFIX\bin" 211 | SoapySDRUtil --check=multi 212 | freebsd-ci: 213 | name: FreeBSD 214 | runs-on: macos-12 215 | strategy: 216 | fail-fast: false 217 | matrix: 218 | build_type: [Release, Debug] 219 | release: ["12.3", "13.0", "13.1"] 220 | steps: 221 | - uses: actions/checkout@v2 222 | - uses: vmactions/freebsd-vm@v0 223 | name: Test in FreeBSD 224 | with: 225 | release: ${{matrix.release}} 226 | copyback: false 227 | prepare: | 228 | pkg install -y cmake SoapySDR net/avahi 229 | run: | 230 | # We can't separate these steps, so add prints for clarity. 231 | 232 | echo 233 | echo "----------------------------------" 234 | echo "Building..." 235 | echo "----------------------------------" 236 | mkdir build 237 | cd build 238 | cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} .. 239 | make 240 | 241 | echo 242 | echo "----------------------------------" 243 | echo "Installing..." 244 | echo "----------------------------------" 245 | make install 246 | 247 | echo 248 | echo "----------------------------------" 249 | echo "Testing module registration..." 250 | echo "----------------------------------" 251 | SoapySDRUtil --check=multi 252 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # Build Multi Soapy SDR support module 3 | ######################################################################## 4 | cmake_minimum_required(VERSION 3.1.0) 5 | project(SoapyMultiSDR CXX) 6 | 7 | set(CMAKE_CXX_STANDARD 11) 8 | 9 | find_package(SoapySDR 0.8.1 CONFIG) 10 | if (NOT SoapySDR_FOUND) 11 | message(FATAL_ERROR "Soapy SDR development files not found...") 12 | endif () 13 | 14 | SOAPY_SDR_MODULE_UTIL( 15 | TARGET MultiSDRSupport 16 | SOURCES 17 | Registration.cpp 18 | Settings.cpp 19 | Streaming.cpp 20 | ) 21 | 22 | #unit test for string utils 23 | enable_testing() 24 | include_directories(${SoapySDR_INCLUDE_DIRS}) 25 | add_executable(TestMultiNameUtils TestMultiNameUtils.cpp) 26 | target_link_libraries(TestMultiNameUtils ${SoapySDR_LIBRARIES}) 27 | add_test(TestMultiNameUtils TestMultiNameUtils) 28 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /MultiNameUtils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //! transform an internal name to a indexed name in the format name[index] 12 | static inline std::string toIndexedName(const std::string &name, const size_t index) 13 | { 14 | return name + "[" + std::to_string(index) + "]"; 15 | } 16 | 17 | //! is the input string in the indexed name format? 18 | static inline bool isIndexedName(const std::string &inName) 19 | { 20 | const size_t openBracketPos = inName.find_last_of("["); 21 | const size_t closeBracketPos = inName.find_last_of("]"); 22 | if (openBracketPos == std::string::npos) return false; 23 | if (closeBracketPos == std::string::npos) return false; 24 | if (closeBracketPos < openBracketPos) return false; 25 | for (size_t i = openBracketPos+1; i < closeBracketPos; i++) 26 | { 27 | if (not std::isdigit(inName.at(i))) return false; 28 | } 29 | return true; 30 | } 31 | 32 | //! Split an indexed name into internal name and index 33 | static inline std::string splitIndexedName(const std::string &inName, size_t &index) 34 | { 35 | if (not isIndexedName(inName)) throw std::runtime_error("splitIndexedName("+inName+") not in name[index] format"); 36 | const size_t openBracketPos = inName.find_last_of("["); 37 | const size_t closeBracketPos = inName.find_last_of("]"); 38 | index = std::stoul(inName.substr(openBracketPos+1, closeBracketPos-openBracketPos-1)); 39 | return inName.substr(0, openBracketPos); 40 | } 41 | 42 | //! Split a comma-separated string into its components 43 | static inline std::vector csvSplit(const std::string &in) 44 | { 45 | std::vector out; 46 | std::string tmp; 47 | 48 | //accumulate into tmp until comma is seen 49 | for (const auto &ch : in) 50 | { 51 | if (ch == ',') 52 | { 53 | out.push_back(tmp); 54 | tmp.clear(); 55 | } 56 | else tmp += ch; 57 | } 58 | if (not tmp.empty()) out.push_back(tmp); 59 | 60 | //trim out leading and trailing space 61 | for (auto &s : out) 62 | { 63 | while (not s.empty() and std::isspace(s[0])) s = s.substr(1); 64 | while (not s.empty() and std::isspace(s[s.size()-1])) s = s.substr(0, s.size()-1); 65 | } 66 | 67 | return out; 68 | } 69 | 70 | //! Join multiple strings into comma separated format 71 | static inline std::string csvJoin(const std::vector &in) 72 | { 73 | std::string out; 74 | for (const auto &s : in) 75 | { 76 | if (not out.empty()) out += ", "; 77 | out += s; 78 | } 79 | return out; 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SoapyMultiSDR - multi-device support module 2 | 3 | Use multiple SoapySDR supported devices under a single device wrapper. 4 | 5 | ## Build Status 6 | 7 | ![Build Status](https://github.com/pothosware/SoapyMultiSDR/actions/workflows/ci.yml/badge.svg) 8 | 9 | ## Dependencies 10 | 11 | * SoapySDR - https://github.com/pothosware/SoapySDR/wiki 12 | 13 | ## Documentation 14 | 15 | * https://github.com/pothosware/SoapyMultiSDR/wiki 16 | 17 | ## Licensing information 18 | 19 | Use, modification and distribution is subject to the Boost Software 20 | License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 21 | http://www.boost.org/LICENSE_1_0.txt) 22 | -------------------------------------------------------------------------------- /Registration.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "SoapyMultiSDR.hpp" 5 | #include 6 | 7 | //! Use this magic stop key in the server to prevent infinite loops 8 | #define SOAPY_MULTI_KWARG_STOP "soapy_multi_no_deeper" 9 | 10 | //! Use this key prefix to pass in args that will become local 11 | #define SOAPY_MULTI_KWARG_PREFIX "multi:" 12 | 13 | /*********************************************************************** 14 | * Args translator for nested keywords 15 | **********************************************************************/ 16 | static SoapySDR::Kwargs translateArgs(const SoapySDR::Kwargs &args, const size_t index) 17 | { 18 | SoapySDR::Kwargs argsOut; 19 | 20 | //stop infinite loops with special keyword 21 | argsOut[SOAPY_MULTI_KWARG_STOP] = ""; 22 | 23 | //copy all non-multi keys 24 | for (auto &pair : args) 25 | { 26 | if (isIndexedName(pair.first)) continue; 27 | if (pair.first == "driver") continue; //don't propagate local driver filter 28 | if (pair.first == "type") continue; //don't propagate local sub-type filter 29 | if (pair.first.find(SOAPY_MULTI_KWARG_PREFIX) == std::string::npos) 30 | { 31 | argsOut[pair.first] = pair.second; 32 | } 33 | } 34 | 35 | //write all multi keys with prefix stripped 36 | for (auto &pair : args) 37 | { 38 | if (pair.first.find(SOAPY_MULTI_KWARG_PREFIX) == 0) 39 | { 40 | static const size_t offset = std::string(SOAPY_MULTI_KWARG_PREFIX).size(); 41 | argsOut[pair.first.substr(offset)] = pair.second; 42 | } 43 | } 44 | 45 | //when indexed, copy in by stripped key when index matches 46 | //drop when the index does not match current index 47 | for (auto &pair : args) 48 | { 49 | if (not isIndexedName(pair.first)) continue; 50 | 51 | size_t splitIndex = 0; 52 | const auto name = splitIndexedName(pair.first, splitIndex); 53 | if (splitIndex == index) argsOut[name] = pair.second; 54 | } 55 | 56 | return argsOut; 57 | } 58 | 59 | static std::vector translateArgs(const SoapySDR::Kwargs &args) 60 | { 61 | std::vector result; 62 | 63 | //find the maximum index 64 | int maxIndex = -1; 65 | for (const auto &pair : args) 66 | { 67 | if (not isIndexedName(pair.first)) continue; 68 | size_t index = 0; splitIndexedName(pair.first, index); 69 | if (int(index) > maxIndex) maxIndex = index; 70 | } 71 | 72 | //no indexed arguments specified 73 | if (maxIndex < 0) return result; 74 | 75 | //translate for each specific index 76 | for (size_t index = 0; index <= size_t(maxIndex); index++) 77 | { 78 | result.push_back(translateArgs(args, index)); 79 | } 80 | 81 | return result; 82 | } 83 | 84 | /*********************************************************************** 85 | * Discovery routine -- find acceptable multi-devices 86 | * Because single devices instances will be discoverable normally 87 | * this routine should only yield results when specifically invoked. 88 | **********************************************************************/ 89 | static std::vector findMultiSDR(const SoapySDR::Kwargs &args) 90 | { 91 | std::vector result; 92 | 93 | //split args into indexes for each device 94 | const auto &argses = translateArgs(args); 95 | if (argses.empty()) return result; 96 | 97 | //gather results at a specific device index 98 | //TODO handle multiple results 99 | SoapySDR::Kwargs result0; 100 | for (size_t index = 0; index < argses.size(); index++) 101 | { 102 | const auto args_i = argses.at(index); 103 | const auto results_i = SoapySDR::Device::enumerate(args_i); 104 | if (results_i.empty()) return result; //nothing for this index 105 | for (const auto &resultArgs : results_i.front()) 106 | { 107 | result0[toIndexedName(resultArgs.first, index)] = resultArgs.second; 108 | } 109 | } 110 | result.push_back(result0); 111 | 112 | //remove instances of the stop key from the result 113 | for (auto &resultArgs : result) 114 | { 115 | resultArgs.erase(SOAPY_MULTI_KWARG_STOP); 116 | if (resultArgs.count("driver") != 0) 117 | { 118 | resultArgs["multi:driver"] = resultArgs.at("driver"); 119 | resultArgs.erase("driver"); 120 | } 121 | if (resultArgs.count("type") != 0) 122 | { 123 | resultArgs["multi:type"] = resultArgs.at("type"); 124 | resultArgs.erase("type"); 125 | } 126 | } 127 | 128 | return result; 129 | } 130 | 131 | /*********************************************************************** 132 | * Factory routine -- create a device with multiple internal handles. 133 | **********************************************************************/ 134 | static SoapySDR::Device *makeMultiSDR(const SoapySDR::Kwargs &args) 135 | { 136 | if (args.count(SOAPY_MULTI_KWARG_STOP) != 0) //probably wont happen 137 | { 138 | throw std::runtime_error("makeMultiSDR() -- factory loop"); 139 | } 140 | 141 | //split args into indexes for each device 142 | const auto &argses = translateArgs(args); 143 | if (argses.empty()) throw std::runtime_error("makeMultiSDR() -- no indexed args"); 144 | 145 | return new SoapyMultiSDR(argses); 146 | } 147 | 148 | /*********************************************************************** 149 | * Registration 150 | **********************************************************************/ 151 | static SoapySDR::Registry registerRemote("multi", &findMultiSDR, &makeMultiSDR, SOAPY_SDR_ABI_VERSION); 152 | -------------------------------------------------------------------------------- /Settings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2017 Josh Blum 2 | // 2021-2022 Nicholas Corgan 3 | // SPDX-License-Identifier: BSL-1.0 4 | 5 | #include "SoapyMultiSDR.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | SoapyMultiSDR::SoapyMultiSDR(const std::vector &args) 12 | { 13 | _devices = SoapySDR::Device::make(args); 14 | 15 | //load the channels lookup 16 | this->reloadChanMaps(); 17 | } 18 | 19 | SoapyMultiSDR::~SoapyMultiSDR(void) 20 | { 21 | SoapySDR::Device::unmake(_devices); 22 | } 23 | 24 | void SoapyMultiSDR::reloadChanMaps(void) 25 | { 26 | _rxChanMap.clear(); 27 | _txChanMap.clear(); 28 | 29 | //map global channel index to local index with device 30 | for (auto device : _devices) 31 | { 32 | for (size_t ch = 0; ch < device->getNumChannels(SOAPY_SDR_RX); ch++) 33 | { 34 | _rxChanMap.push_back(std::make_pair(ch, device)); 35 | } 36 | for (size_t ch = 0; ch < device->getNumChannels(SOAPY_SDR_TX); ch++) 37 | { 38 | _txChanMap.push_back(std::make_pair(ch, device)); 39 | } 40 | } 41 | } 42 | 43 | /******************************************************************* 44 | * Identification API 45 | ******************************************************************/ 46 | 47 | std::string SoapyMultiSDR::getDriverKey(void) const 48 | { 49 | std::vector keys; 50 | for (auto device : _devices) 51 | { 52 | keys.push_back(device->getDriverKey()); 53 | } 54 | return csvJoin(keys); 55 | } 56 | 57 | std::string SoapyMultiSDR::getHardwareKey(void) const 58 | { 59 | std::vector keys; 60 | for (auto device : _devices) 61 | { 62 | keys.push_back(device->getHardwareKey()); 63 | } 64 | return csvJoin(keys); 65 | } 66 | 67 | SoapySDR::Kwargs SoapyMultiSDR::getHardwareInfo(void) const 68 | { 69 | SoapySDR::Kwargs result; 70 | for (size_t i = 0; i < _devices.size(); i++) 71 | { 72 | for (const auto &pair : _devices[i]->getHardwareInfo()) 73 | { 74 | result[toIndexedName(pair.first, i)] = pair.second; 75 | } 76 | } 77 | return result; 78 | } 79 | 80 | /******************************************************************* 81 | * Channels API 82 | ******************************************************************/ 83 | 84 | void SoapyMultiSDR::setFrontendMapping(const int direction, const std::string &mapping) 85 | { 86 | const auto maps = csvSplit(mapping); 87 | for (size_t i = 0; i < maps.size() and i < _devices.size(); i++) 88 | { 89 | _devices[i]->setFrontendMapping(direction, maps.at(i)); 90 | } 91 | this->reloadChanMaps(); 92 | } 93 | 94 | std::string SoapyMultiSDR::getFrontendMapping(const int direction) const 95 | { 96 | std::vector maps; 97 | for (auto device : _devices) 98 | { 99 | maps.push_back(device->getFrontendMapping(direction)); 100 | } 101 | return csvJoin(maps); 102 | } 103 | 104 | size_t SoapyMultiSDR::getNumChannels(const int direction) const 105 | { 106 | const auto &map = (direction == SOAPY_SDR_RX)?_rxChanMap:_txChanMap; 107 | return map.size(); 108 | } 109 | 110 | SoapySDR::Kwargs SoapyMultiSDR::getChannelInfo(const int direction, const size_t channel) const 111 | { 112 | SoapySDR::Kwargs result; 113 | for (size_t i = 0; i < _devices.size(); i++) 114 | { 115 | for (const auto &pair : _devices[i]->getChannelInfo(direction, channel)) 116 | { 117 | result[toIndexedName(pair.first, i)] = pair.second; 118 | } 119 | } 120 | return result; 121 | } 122 | 123 | bool SoapyMultiSDR::getFullDuplex(const int direction, const size_t channel) const 124 | { 125 | size_t localChannel = 0; 126 | auto device = this->getDevice(direction, channel, localChannel); 127 | return device->getFullDuplex(direction, localChannel); 128 | } 129 | 130 | /******************************************************************* 131 | * Antenna API 132 | ******************************************************************/ 133 | 134 | std::vector SoapyMultiSDR::listAntennas(const int direction, const size_t channel) const 135 | { 136 | size_t localChannel = 0; 137 | auto device = this->getDevice(direction, channel, localChannel); 138 | return device->listAntennas(direction, localChannel); 139 | } 140 | 141 | void SoapyMultiSDR::setAntenna(const int direction, const size_t channel, const std::string &name) 142 | { 143 | size_t localChannel = 0; 144 | auto device = this->getDevice(direction, channel, localChannel); 145 | return device->setAntenna(direction, localChannel, name); 146 | } 147 | 148 | std::string SoapyMultiSDR::getAntenna(const int direction, const size_t channel) const 149 | { 150 | size_t localChannel = 0; 151 | auto device = this->getDevice(direction, channel, localChannel); 152 | return device->getAntenna(direction, localChannel); 153 | } 154 | 155 | /******************************************************************* 156 | * Frontend corrections API 157 | ******************************************************************/ 158 | 159 | bool SoapyMultiSDR::hasDCOffsetMode(const int direction, const size_t channel) const 160 | { 161 | size_t localChannel = 0; 162 | auto device = this->getDevice(direction, channel, localChannel); 163 | return device->hasDCOffsetMode(direction, localChannel); 164 | } 165 | 166 | void SoapyMultiSDR::setDCOffsetMode(const int direction, const size_t channel, const bool automatic) 167 | { 168 | size_t localChannel = 0; 169 | auto device = this->getDevice(direction, channel, localChannel); 170 | return device->setDCOffsetMode(direction, localChannel, automatic); 171 | } 172 | 173 | bool SoapyMultiSDR::getDCOffsetMode(const int direction, const size_t channel) const 174 | { 175 | size_t localChannel = 0; 176 | auto device = this->getDevice(direction, channel, localChannel); 177 | return device->getDCOffsetMode(direction, localChannel); 178 | } 179 | 180 | bool SoapyMultiSDR::hasDCOffset(const int direction, const size_t channel) const 181 | { 182 | size_t localChannel = 0; 183 | auto device = this->getDevice(direction, channel, localChannel); 184 | return device->hasDCOffset(direction, localChannel); 185 | } 186 | 187 | void SoapyMultiSDR::setDCOffset(const int direction, const size_t channel, const std::complex &offset) 188 | { 189 | size_t localChannel = 0; 190 | auto device = this->getDevice(direction, channel, localChannel); 191 | return device->setDCOffset(direction, localChannel, offset); 192 | } 193 | 194 | std::complex SoapyMultiSDR::getDCOffset(const int direction, const size_t channel) const 195 | { 196 | size_t localChannel = 0; 197 | auto device = this->getDevice(direction, channel, localChannel); 198 | return device->getDCOffset(direction, localChannel); 199 | } 200 | 201 | bool SoapyMultiSDR::hasIQBalance(const int direction, const size_t channel) const 202 | { 203 | size_t localChannel = 0; 204 | auto device = this->getDevice(direction, channel, localChannel); 205 | return device->hasIQBalance(direction, localChannel); 206 | } 207 | 208 | void SoapyMultiSDR::setIQBalance(const int direction, const size_t channel, const std::complex &balance) 209 | { 210 | size_t localChannel = 0; 211 | auto device = this->getDevice(direction, channel, localChannel); 212 | return device->setIQBalance(direction, localChannel, balance); 213 | } 214 | 215 | std::complex SoapyMultiSDR::getIQBalance(const int direction, const size_t channel) const 216 | { 217 | size_t localChannel = 0; 218 | auto device = this->getDevice(direction, channel, localChannel); 219 | return device->getIQBalance(direction, localChannel); 220 | } 221 | 222 | bool SoapyMultiSDR::hasIQBalanceMode(const int direction, const size_t channel) const 223 | { 224 | size_t localChannel = 0; 225 | auto device = this->getDevice(direction, channel, localChannel); 226 | return device->hasIQBalanceMode(direction, localChannel); 227 | } 228 | 229 | void SoapyMultiSDR::setIQBalanceMode(const int direction, const size_t channel, const bool automatic) 230 | { 231 | size_t localChannel = 0; 232 | auto device = this->getDevice(direction, channel, localChannel); 233 | return device->setIQBalanceMode(direction, localChannel, automatic); 234 | } 235 | 236 | bool SoapyMultiSDR::getIQBalanceMode(const int direction, const size_t channel) const 237 | { 238 | size_t localChannel = 0; 239 | auto device = this->getDevice(direction, channel, localChannel); 240 | return device->getIQBalanceMode(direction, localChannel); 241 | } 242 | 243 | bool SoapyMultiSDR::hasFrequencyCorrection(const int direction, const size_t channel) const 244 | { 245 | size_t localChannel = 0; 246 | auto device = this->getDevice(direction, channel, localChannel); 247 | return device->hasFrequencyCorrection(direction, localChannel); 248 | } 249 | 250 | void SoapyMultiSDR::setFrequencyCorrection(const int direction, const size_t channel, const double value) 251 | { 252 | size_t localChannel = 0; 253 | auto device = this->getDevice(direction, channel, localChannel); 254 | return device->setFrequencyCorrection(direction, localChannel, value); 255 | } 256 | 257 | double SoapyMultiSDR::getFrequencyCorrection(const int direction, const size_t channel) const 258 | { 259 | size_t localChannel = 0; 260 | auto device = this->getDevice(direction, channel, localChannel); 261 | return device->getFrequencyCorrection(direction, localChannel); 262 | } 263 | 264 | /******************************************************************* 265 | * Gain API 266 | ******************************************************************/ 267 | 268 | std::vector SoapyMultiSDR::listGains(const int direction, const size_t channel) const 269 | { 270 | size_t localChannel = 0; 271 | auto device = this->getDevice(direction, channel, localChannel); 272 | return device->listGains(direction, localChannel); 273 | } 274 | 275 | bool SoapyMultiSDR::hasGainMode(const int direction, const size_t channel) const 276 | { 277 | size_t localChannel = 0; 278 | auto device = this->getDevice(direction, channel, localChannel); 279 | return device->hasGainMode(direction, localChannel); 280 | } 281 | 282 | void SoapyMultiSDR::setGainMode(const int direction, const size_t channel, const bool automatic) 283 | { 284 | size_t localChannel = 0; 285 | auto device = this->getDevice(direction, channel, localChannel); 286 | return device->setGainMode(direction, localChannel, automatic); 287 | } 288 | 289 | bool SoapyMultiSDR::getGainMode(const int direction, const size_t channel) const 290 | { 291 | size_t localChannel = 0; 292 | auto device = this->getDevice(direction, channel, localChannel); 293 | return device->getGainMode(direction, localChannel); 294 | } 295 | 296 | void SoapyMultiSDR::setGain(const int direction, const size_t channel, const double value) 297 | { 298 | size_t localChannel = 0; 299 | auto device = this->getDevice(direction, channel, localChannel); 300 | return device->setGain(direction, localChannel, value); 301 | } 302 | 303 | void SoapyMultiSDR::setGain(const int direction, const size_t channel, const std::string &name, const double value) 304 | { 305 | size_t localChannel = 0; 306 | auto device = this->getDevice(direction, channel, localChannel); 307 | return device->setGain(direction, localChannel, name, value); 308 | } 309 | 310 | double SoapyMultiSDR::getGain(const int direction, const size_t channel) const 311 | { 312 | size_t localChannel = 0; 313 | auto device = this->getDevice(direction, channel, localChannel); 314 | return device->getGain(direction, localChannel); 315 | } 316 | 317 | double SoapyMultiSDR::getGain(const int direction, const size_t channel, const std::string &name) const 318 | { 319 | size_t localChannel = 0; 320 | auto device = this->getDevice(direction, channel, localChannel); 321 | return device->getGain(direction, localChannel, name); 322 | } 323 | 324 | SoapySDR::Range SoapyMultiSDR::getGainRange(const int direction, const size_t channel) const 325 | { 326 | size_t localChannel = 0; 327 | auto device = this->getDevice(direction, channel, localChannel); 328 | return device->getGainRange(direction, localChannel); 329 | } 330 | 331 | SoapySDR::Range SoapyMultiSDR::getGainRange(const int direction, const size_t channel, const std::string &name) const 332 | { 333 | size_t localChannel = 0; 334 | auto device = this->getDevice(direction, channel, localChannel); 335 | return device->getGainRange(direction, localChannel, name); 336 | } 337 | 338 | /******************************************************************* 339 | * Frequency API 340 | ******************************************************************/ 341 | 342 | void SoapyMultiSDR::setFrequency(const int direction, const size_t channel, const double frequency, const SoapySDR::Kwargs &args) 343 | { 344 | size_t localChannel = 0; 345 | auto device = this->getDevice(direction, channel, localChannel); 346 | return device->setFrequency(direction, localChannel, frequency, args); 347 | } 348 | 349 | void SoapyMultiSDR::setFrequency(const int direction, const size_t channel, const std::string &name, const double frequency, const SoapySDR::Kwargs &args) 350 | { 351 | size_t localChannel = 0; 352 | auto device = this->getDevice(direction, channel, localChannel); 353 | return device->setFrequency(direction, localChannel, name, frequency, args); 354 | } 355 | 356 | double SoapyMultiSDR::getFrequency(const int direction, const size_t channel) const 357 | { 358 | size_t localChannel = 0; 359 | auto device = this->getDevice(direction, channel, localChannel); 360 | return device->getFrequency(direction, localChannel); 361 | } 362 | 363 | double SoapyMultiSDR::getFrequency(const int direction, const size_t channel, const std::string &name) const 364 | { 365 | size_t localChannel = 0; 366 | auto device = this->getDevice(direction, channel, localChannel); 367 | return device->getFrequency(direction, localChannel, name); 368 | } 369 | 370 | std::vector SoapyMultiSDR::listFrequencies(const int direction, const size_t channel) const 371 | { 372 | size_t localChannel = 0; 373 | auto device = this->getDevice(direction, channel, localChannel); 374 | return device->listFrequencies(direction, localChannel); 375 | } 376 | 377 | SoapySDR::RangeList SoapyMultiSDR::getFrequencyRange(const int direction, const size_t channel) const 378 | { 379 | size_t localChannel = 0; 380 | auto device = this->getDevice(direction, channel, localChannel); 381 | return device->getFrequencyRange(direction, localChannel); 382 | } 383 | 384 | SoapySDR::RangeList SoapyMultiSDR::getFrequencyRange(const int direction, const size_t channel, const std::string &name) const 385 | { 386 | size_t localChannel = 0; 387 | auto device = this->getDevice(direction, channel, localChannel); 388 | return device->getFrequencyRange(direction, localChannel, name); 389 | } 390 | 391 | SoapySDR::ArgInfoList SoapyMultiSDR::getFrequencyArgsInfo(const int direction, const size_t channel) const 392 | { 393 | size_t localChannel = 0; 394 | auto device = this->getDevice(direction, channel, localChannel); 395 | return device->getFrequencyArgsInfo(direction, localChannel); 396 | } 397 | 398 | /******************************************************************* 399 | * Sample Rate API 400 | ******************************************************************/ 401 | 402 | void SoapyMultiSDR::setSampleRate(const int direction, const size_t channel, const double rate) 403 | { 404 | size_t localChannel = 0; 405 | auto device = this->getDevice(direction, channel, localChannel); 406 | return device->setSampleRate(direction, localChannel, rate); 407 | } 408 | 409 | double SoapyMultiSDR::getSampleRate(const int direction, const size_t channel) const 410 | { 411 | size_t localChannel = 0; 412 | auto device = this->getDevice(direction, channel, localChannel); 413 | return device->getSampleRate(direction, localChannel); 414 | } 415 | 416 | std::vector SoapyMultiSDR::listSampleRates(const int direction, const size_t channel) const 417 | { 418 | size_t localChannel = 0; 419 | auto device = this->getDevice(direction, channel, localChannel); 420 | return device->listSampleRates(direction, localChannel); 421 | } 422 | 423 | SoapySDR::RangeList SoapyMultiSDR::getSampleRateRange(const int direction, const size_t channel) const 424 | { 425 | size_t localChannel = 0; 426 | auto device = this->getDevice(direction, channel, localChannel); 427 | return device->getSampleRateRange(direction, localChannel); 428 | } 429 | 430 | /******************************************************************* 431 | * Bandwidth API 432 | ******************************************************************/ 433 | 434 | void SoapyMultiSDR::setBandwidth(const int direction, const size_t channel, const double bw) 435 | { 436 | size_t localChannel = 0; 437 | auto device = this->getDevice(direction, channel, localChannel); 438 | return device->setBandwidth(direction, localChannel, bw); 439 | } 440 | 441 | double SoapyMultiSDR::getBandwidth(const int direction, const size_t channel) const 442 | { 443 | size_t localChannel = 0; 444 | auto device = this->getDevice(direction, channel, localChannel); 445 | return device->getBandwidth(direction, localChannel); 446 | } 447 | 448 | std::vector SoapyMultiSDR::listBandwidths(const int direction, const size_t channel) const 449 | { 450 | size_t localChannel = 0; 451 | auto device = this->getDevice(direction, channel, localChannel); 452 | return device->listBandwidths(direction, localChannel); 453 | } 454 | 455 | SoapySDR::RangeList SoapyMultiSDR::getBandwidthRange(const int direction, const size_t channel) const 456 | { 457 | size_t localChannel = 0; 458 | auto device = this->getDevice(direction, channel, localChannel); 459 | return device->getBandwidthRange(direction, localChannel); 460 | } 461 | 462 | /******************************************************************* 463 | * Clocking API 464 | ******************************************************************/ 465 | 466 | void SoapyMultiSDR::setMasterClockRate(const double rate) 467 | { 468 | for (auto device : _devices) 469 | { 470 | device->setMasterClockRate(rate); 471 | } 472 | } 473 | 474 | double SoapyMultiSDR::getMasterClockRate(void) const 475 | { 476 | return _devices[0]->getMasterClockRate(); 477 | } 478 | 479 | SoapySDR::RangeList SoapyMultiSDR::getMasterClockRates(void) const 480 | { 481 | return _devices[0]->getMasterClockRates(); 482 | } 483 | 484 | void SoapyMultiSDR::setReferenceClockRate(const double rate) 485 | { 486 | for (auto device: _devices) 487 | { 488 | device->setReferenceClockRate(rate); 489 | } 490 | } 491 | 492 | double SoapyMultiSDR::getReferenceClockRate(void) const 493 | { 494 | return _devices[0]->getReferenceClockRate(); 495 | } 496 | 497 | SoapySDR::RangeList SoapyMultiSDR::getReferenceClockRates(void) const 498 | { 499 | return _devices[0]->getReferenceClockRates(); 500 | } 501 | 502 | std::vector SoapyMultiSDR::listClockSources(void) const 503 | { 504 | return _devices[0]->listClockSources(); 505 | } 506 | 507 | void SoapyMultiSDR::setClockSource(const std::string &source) 508 | { 509 | const auto sources = csvSplit(source); 510 | for (size_t i = 0; i < sources.size() and i < _devices.size(); i++) 511 | { 512 | _devices[i]->setClockSource(sources.at(i)); 513 | } 514 | } 515 | 516 | std::string SoapyMultiSDR::getClockSource(void) const 517 | { 518 | std::vector sources; 519 | for (auto device : _devices) 520 | { 521 | sources.push_back(device->getClockSource()); 522 | } 523 | return csvJoin(sources); 524 | } 525 | 526 | /******************************************************************* 527 | * Time API 528 | ******************************************************************/ 529 | 530 | std::vector SoapyMultiSDR::listTimeSources(void) const 531 | { 532 | return _devices[0]->listTimeSources(); 533 | } 534 | 535 | void SoapyMultiSDR::setTimeSource(const std::string &source) 536 | { 537 | const auto sources = csvSplit(source); 538 | for (size_t i = 0; i < sources.size() and i < _devices.size(); i++) 539 | { 540 | _devices[i]->setTimeSource(sources.at(i)); 541 | } 542 | } 543 | 544 | std::string SoapyMultiSDR::getTimeSource(void) const 545 | { 546 | std::vector sources; 547 | for (auto device : _devices) 548 | { 549 | sources.push_back(device->getTimeSource()); 550 | } 551 | return csvJoin(sources); 552 | } 553 | 554 | bool SoapyMultiSDR::hasHardwareTime(const std::string &what) const 555 | { 556 | return _devices[0]->hasHardwareTime(what); 557 | } 558 | 559 | long long SoapyMultiSDR::getHardwareTime(const std::string &what) const 560 | { 561 | return _devices[0]->getHardwareTime(what); 562 | } 563 | 564 | void SoapyMultiSDR::setHardwareTime(const long long timeNs, const std::string &what) 565 | { 566 | for (auto device : _devices) 567 | { 568 | device->setHardwareTime(timeNs, what); 569 | } 570 | } 571 | 572 | void SoapyMultiSDR::setCommandTime(const long long timeNs, const std::string &what) 573 | { 574 | for (auto device : _devices) 575 | { 576 | device->setCommandTime(timeNs, what); 577 | } 578 | } 579 | 580 | /******************************************************************* 581 | * Sensor API 582 | ******************************************************************/ 583 | 584 | std::vector SoapyMultiSDR::listSensors(void) const 585 | { 586 | std::vector result; 587 | for (size_t i = 0; i < _devices.size(); i++) 588 | { 589 | for (const auto &name : _devices[i]->listSensors()) 590 | { 591 | result.push_back(toIndexedName(name, i)); 592 | } 593 | } 594 | return result; 595 | } 596 | 597 | SoapySDR::ArgInfo SoapyMultiSDR::getSensorInfo(const std::string &name) const 598 | { 599 | size_t index = 0; 600 | const auto localName = splitIndexedName(name, index); 601 | return _devices[index]->getSensorInfo(localName); 602 | } 603 | 604 | std::string SoapyMultiSDR::readSensor(const std::string &name) const 605 | { 606 | size_t index = 0; 607 | const auto localName = splitIndexedName(name, index); 608 | return _devices[index]->readSensor(localName); 609 | } 610 | 611 | std::vector SoapyMultiSDR::listSensors(const int direction, const size_t channel) const 612 | { 613 | size_t localChannel = 0; 614 | auto device = this->getDevice(direction, channel, localChannel); 615 | return device->listSensors(direction, localChannel); 616 | } 617 | 618 | SoapySDR::ArgInfo SoapyMultiSDR::getSensorInfo(const int direction, const size_t channel, const std::string &name) const 619 | { 620 | size_t localChannel = 0; 621 | auto device = this->getDevice(direction, channel, localChannel); 622 | return device->getSensorInfo(direction, localChannel, name); 623 | } 624 | 625 | std::string SoapyMultiSDR::readSensor(const int direction, const size_t channel, const std::string &name) const 626 | { 627 | size_t localChannel = 0; 628 | auto device = this->getDevice(direction, channel, localChannel); 629 | return device->readSensor(direction, localChannel, name); 630 | } 631 | 632 | /******************************************************************* 633 | * Register API 634 | ******************************************************************/ 635 | 636 | std::vector SoapyMultiSDR::listRegisterInterfaces(void) const 637 | { 638 | std::vector result; 639 | for (size_t i = 0; i < _devices.size(); i++) 640 | { 641 | for (const auto &name : _devices[i]->listRegisterInterfaces()) 642 | { 643 | result.push_back(toIndexedName(name, i)); 644 | } 645 | } 646 | return result; 647 | } 648 | 649 | void SoapyMultiSDR::writeRegister(const std::string &name, const unsigned addr, const unsigned value) 650 | { 651 | size_t index = 0; 652 | const auto localName = splitIndexedName(name, index); 653 | return _devices[index]->writeRegister(localName, addr, value); 654 | } 655 | 656 | unsigned SoapyMultiSDR::readRegister(const std::string &name, const unsigned addr) const 657 | { 658 | size_t index = 0; 659 | const auto localName = splitIndexedName(name, index); 660 | return _devices[index]->readRegister(localName, addr); 661 | } 662 | 663 | void SoapyMultiSDR::writeRegister(const unsigned addr, const unsigned value) 664 | { 665 | return _devices[0]->writeRegister(addr, value); 666 | } 667 | 668 | unsigned SoapyMultiSDR::readRegister(const unsigned addr) const 669 | { 670 | return _devices[0]->readRegister(addr); 671 | } 672 | 673 | void SoapyMultiSDR::writeRegisters(const std::string &name, const unsigned addr, const std::vector &value) 674 | { 675 | size_t index = 0; 676 | const auto localName = splitIndexedName(name, index); 677 | return _devices[index]->writeRegisters(localName, addr, value); 678 | } 679 | 680 | std::vector SoapyMultiSDR::readRegisters(const std::string &name, const unsigned addr, const size_t length) const 681 | { 682 | size_t index = 0; 683 | const auto localName = splitIndexedName(name, index); 684 | return _devices[index]->readRegisters(name, addr, length); 685 | } 686 | 687 | /******************************************************************* 688 | * Settings API 689 | ******************************************************************/ 690 | 691 | SoapySDR::ArgInfoList SoapyMultiSDR::getSettingInfo(void) const 692 | { 693 | SoapySDR::ArgInfoList result; 694 | for (size_t i = 0; i < _devices.size(); i++) 695 | { 696 | for (auto info : _devices[i]->getSettingInfo()) 697 | { 698 | info.key = toIndexedName(info.key, i); 699 | info.name += " - Device" + std::to_string(i); 700 | result.push_back(info); 701 | } 702 | } 703 | return result; 704 | } 705 | 706 | SoapySDR::ArgInfo SoapyMultiSDR::getSettingInfo(const std::string &key) const 707 | { 708 | #ifdef SOAPY_SDR_API_HAS_GET_SPECIFIC_SETTING_INFO 709 | size_t index = 0; 710 | const auto localKey = splitIndexedName(key, index); 711 | return _devices[index]->getSettingInfo(key); 712 | #else 713 | (void)key; 714 | throw std::runtime_error("Getting specific setting info is unsupported in this SoapySDR version."); 715 | #endif 716 | } 717 | 718 | void SoapyMultiSDR::writeSetting(const std::string &key, const std::string &value) 719 | { 720 | size_t index = 0; 721 | const auto localKey = splitIndexedName(key, index); 722 | return _devices[index]->writeSetting(localKey, value); 723 | } 724 | 725 | std::string SoapyMultiSDR::readSetting(const std::string &key) const 726 | { 727 | size_t index = 0; 728 | const auto localKey = splitIndexedName(key, index); 729 | return _devices[index]->readSetting(localKey); 730 | } 731 | 732 | SoapySDR::ArgInfoList SoapyMultiSDR::getSettingInfo(const int direction, const size_t channel) const 733 | { 734 | size_t localChannel = 0; 735 | auto device = this->getDevice(direction, channel, localChannel); 736 | return device->getSettingInfo(direction, localChannel); 737 | } 738 | 739 | SoapySDR::ArgInfo SoapyMultiSDR::getSettingInfo(const int direction, const size_t channel, const std::string &key) const 740 | { 741 | #ifdef SOAPY_SDR_API_HAS_GET_SPECIFIC_SETTING_INFO 742 | size_t localChannel = 0; 743 | auto device = this->getDevice(direction, channel, localChannel); 744 | return device->getSettingInfo(direction, localChannel, key); 745 | #else 746 | (void)direction; 747 | (void)channel; 748 | (void)key; 749 | throw std::runtime_error("Getting specific setting info is unsupported in this SoapySDR version."); 750 | #endif 751 | } 752 | 753 | void SoapyMultiSDR::writeSetting(const int direction, const size_t channel, const std::string &key, const std::string &value) 754 | { 755 | size_t localChannel = 0; 756 | auto device = this->getDevice(direction, channel, localChannel); 757 | return device->writeSetting(direction, localChannel, key, value); 758 | } 759 | 760 | std::string SoapyMultiSDR::readSetting(const int direction, const size_t channel, const std::string &key) const 761 | { 762 | size_t localChannel = 0; 763 | auto device = this->getDevice(direction, channel, localChannel); 764 | return device->readSetting(direction, localChannel, key); 765 | } 766 | 767 | /******************************************************************* 768 | * GPIO API 769 | ******************************************************************/ 770 | 771 | std::vector SoapyMultiSDR::listGPIOBanks(void) const 772 | { 773 | std::vector result; 774 | for (size_t i = 0; i < _devices.size(); i++) 775 | { 776 | for (const auto &name : _devices[i]->listGPIOBanks()) 777 | { 778 | result.push_back(toIndexedName(name, i)); 779 | } 780 | } 781 | return result; 782 | } 783 | 784 | void SoapyMultiSDR::writeGPIO(const std::string &bank, const unsigned value) 785 | { 786 | size_t index = 0; 787 | const auto localBank = splitIndexedName(bank, index); 788 | return _devices[index]->writeGPIO(localBank, value); 789 | } 790 | 791 | void SoapyMultiSDR::writeGPIO(const std::string &bank, const unsigned value, const unsigned mask) 792 | { 793 | size_t index = 0; 794 | const auto localBank = splitIndexedName(bank, index); 795 | return _devices[index]->writeGPIO(localBank, value, mask); 796 | } 797 | 798 | unsigned SoapyMultiSDR::readGPIO(const std::string &bank) const 799 | { 800 | size_t index = 0; 801 | const auto localBank = splitIndexedName(bank, index); 802 | return _devices[index]->readGPIO(localBank); 803 | } 804 | 805 | void SoapyMultiSDR::writeGPIODir(const std::string &bank, const unsigned dir) 806 | { 807 | size_t index = 0; 808 | const auto localBank = splitIndexedName(bank, index); 809 | return _devices[index]->writeGPIODir(localBank, dir); 810 | } 811 | 812 | void SoapyMultiSDR::writeGPIODir(const std::string &bank, const unsigned dir, const unsigned mask) 813 | { 814 | size_t index = 0; 815 | const auto localBank = splitIndexedName(bank, index); 816 | return _devices[index]->writeGPIODir(localBank, dir, mask); 817 | } 818 | 819 | unsigned SoapyMultiSDR::readGPIODir(const std::string &bank) const 820 | { 821 | size_t index = 0; 822 | const auto localBank = splitIndexedName(bank, index); 823 | return _devices[index]->readGPIODir(localBank); 824 | } 825 | 826 | /******************************************************************* 827 | * I2C API 828 | ******************************************************************/ 829 | 830 | void SoapyMultiSDR::writeI2C(const int addr, const std::string &data) 831 | { 832 | return _devices[0]->writeI2C(addr, data); 833 | } 834 | 835 | std::string SoapyMultiSDR::readI2C(const int addr, const size_t numBytes) 836 | { 837 | return _devices[0]->readI2C(addr, numBytes); 838 | } 839 | 840 | /******************************************************************* 841 | * SPI API 842 | ******************************************************************/ 843 | 844 | unsigned SoapyMultiSDR::transactSPI(const int addr, const unsigned data, const size_t numBits) 845 | { 846 | return _devices[0]->transactSPI(addr, data, numBits); 847 | } 848 | 849 | /******************************************************************* 850 | * UART API 851 | ******************************************************************/ 852 | 853 | std::vector SoapyMultiSDR::listUARTs(void) const 854 | { 855 | std::vector result; 856 | for (size_t i = 0; i < _devices.size(); i++) 857 | { 858 | for (const auto &name : _devices[i]->listUARTs()) 859 | { 860 | result.push_back(toIndexedName(name, i)); 861 | } 862 | } 863 | return result; 864 | } 865 | 866 | void SoapyMultiSDR::writeUART(const std::string &which, const std::string &data) 867 | { 868 | size_t index = 0; 869 | const auto localUART = splitIndexedName(which, index); 870 | return _devices[index]->writeUART(localUART, data); 871 | } 872 | 873 | std::string SoapyMultiSDR::readUART(const std::string &which, const long timeoutUs) const 874 | { 875 | size_t index = 0; 876 | const auto localUART = splitIndexedName(which, index); 877 | return _devices[index]->readUART(localUART, timeoutUs); 878 | } 879 | -------------------------------------------------------------------------------- /SoapyMultiSDR.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2017 Josh Blum 2 | // 2021-2022 Nicholas Corgan 3 | // SPDX-License-Identifier: BSL-1.0 4 | 5 | #pragma once 6 | #include "MultiNameUtils.hpp" 7 | #include 8 | #include //pair 9 | #include 10 | 11 | class SoapyMultiSDR : public SoapySDR::Device 12 | { 13 | public: 14 | SoapyMultiSDR(const std::vector &args); 15 | ~SoapyMultiSDR(void); 16 | 17 | /******************************************************************* 18 | * Identification API 19 | ******************************************************************/ 20 | 21 | std::string getDriverKey(void) const; 22 | 23 | std::string getHardwareKey(void) const; 24 | 25 | SoapySDR::Kwargs getHardwareInfo(void) const; 26 | 27 | /******************************************************************* 28 | * Channels API 29 | ******************************************************************/ 30 | 31 | void setFrontendMapping(const int direction, const std::string &mapping); 32 | 33 | std::string getFrontendMapping(const int direction) const; 34 | 35 | size_t getNumChannels(const int direction) const; 36 | 37 | SoapySDR::Kwargs getChannelInfo(const int direction, const size_t channel) const; 38 | 39 | bool getFullDuplex(const int direction, const size_t channel) const; 40 | 41 | /******************************************************************* 42 | * Stream API 43 | ******************************************************************/ 44 | 45 | std::vector getStreamFormats(const int direction, const size_t channel) const; 46 | 47 | std::string getNativeStreamFormat(const int direction, const size_t channel, double &fullScale) const; 48 | 49 | SoapySDR::ArgInfoList getStreamArgsInfo(const int direction, const size_t channel) const; 50 | 51 | SoapySDR::Stream *setupStream( 52 | const int direction, 53 | const std::string &format, 54 | const std::vector &channels, 55 | const SoapySDR::Kwargs &args); 56 | 57 | void closeStream(SoapySDR::Stream *stream); 58 | 59 | size_t getStreamMTU(SoapySDR::Stream *stream) const; 60 | 61 | int activateStream( 62 | SoapySDR::Stream *stream, 63 | const int flags, 64 | const long long timeNs, 65 | const size_t numElems); 66 | 67 | int deactivateStream( 68 | SoapySDR::Stream *stream, 69 | const int flags, 70 | const long long timeNs); 71 | 72 | int readStream( 73 | SoapySDR::Stream *stream, 74 | void * const *buffs, 75 | const size_t numElems, 76 | int &flags, 77 | long long &timeNs, 78 | const long timeoutUs); 79 | 80 | int writeStream( 81 | SoapySDR::Stream *stream, 82 | const void * const *buffs, 83 | const size_t numElems, 84 | int &flags, 85 | const long long timeNs, 86 | const long timeoutUs); 87 | 88 | int readStreamStatus( 89 | SoapySDR::Stream *stream, 90 | size_t &chanMask, 91 | int &flags, 92 | long long &timeNs, 93 | const long timeoutUs); 94 | 95 | /******************************************************************* 96 | * Direct buffer access API 97 | ******************************************************************/ 98 | 99 | size_t getNumDirectAccessBuffers(SoapySDR::Stream *stream); 100 | 101 | int getDirectAccessBufferAddrs(SoapySDR::Stream *stream, const size_t handle, void **buffs); 102 | 103 | int acquireReadBuffer( 104 | SoapySDR::Stream *stream, 105 | size_t &handle, 106 | const void **buffs, 107 | int &flags, 108 | long long &timeNs, 109 | const long timeoutUs); 110 | 111 | void releaseReadBuffer( 112 | SoapySDR::Stream *stream, 113 | const size_t handle); 114 | 115 | int acquireWriteBuffer( 116 | SoapySDR::Stream *stream, 117 | size_t &handle, 118 | void **buffs, 119 | const long timeoutUs); 120 | 121 | void releaseWriteBuffer( 122 | SoapySDR::Stream *stream, 123 | const size_t handle, 124 | const size_t numElems, 125 | int &flags, 126 | const long long timeNs); 127 | 128 | /******************************************************************* 129 | * Antenna API 130 | ******************************************************************/ 131 | 132 | std::vector listAntennas(const int direction, const size_t channel) const; 133 | 134 | void setAntenna(const int direction, const size_t channel, const std::string &name); 135 | 136 | std::string getAntenna(const int direction, const size_t channel) const; 137 | 138 | /******************************************************************* 139 | * Frontend corrections API 140 | ******************************************************************/ 141 | 142 | bool hasDCOffsetMode(const int direction, const size_t channel) const; 143 | 144 | void setDCOffsetMode(const int direction, const size_t channel, const bool automatic); 145 | 146 | bool getDCOffsetMode(const int direction, const size_t channel) const; 147 | 148 | bool hasDCOffset(const int direction, const size_t channel) const; 149 | 150 | void setDCOffset(const int direction, const size_t channel, const std::complex &offset); 151 | 152 | std::complex getDCOffset(const int direction, const size_t channel) const; 153 | 154 | bool hasIQBalance(const int direction, const size_t channel) const; 155 | 156 | void setIQBalance(const int direction, const size_t channel, const std::complex &balance); 157 | 158 | std::complex getIQBalance(const int direction, const size_t channel) const; 159 | 160 | bool hasIQBalanceMode(const int direction, const size_t channel) const; 161 | 162 | void setIQBalanceMode(const int direction, const size_t channel, const bool automatic); 163 | 164 | bool getIQBalanceMode(const int direction, const size_t channel) const; 165 | 166 | bool hasFrequencyCorrection(const int direction, const size_t channel) const; 167 | 168 | void setFrequencyCorrection(const int direction, const size_t channel, const double value); 169 | 170 | double getFrequencyCorrection(const int direction, const size_t channel) const; 171 | 172 | /******************************************************************* 173 | * Gain API 174 | ******************************************************************/ 175 | 176 | std::vector listGains(const int direction, const size_t channel) const; 177 | 178 | bool hasGainMode(const int direction, const size_t channel) const; 179 | 180 | void setGainMode(const int direction, const size_t channel, const bool automatic); 181 | 182 | bool getGainMode(const int direction, const size_t channel) const; 183 | 184 | void setGain(const int direction, const size_t channel, const double value); 185 | 186 | void setGain(const int direction, const size_t channel, const std::string &name, const double value); 187 | 188 | double getGain(const int direction, const size_t channel) const; 189 | 190 | double getGain(const int direction, const size_t channel, const std::string &name) const; 191 | 192 | SoapySDR::Range getGainRange(const int direction, const size_t channel) const; 193 | 194 | SoapySDR::Range getGainRange(const int direction, const size_t channel, const std::string &name) const; 195 | 196 | /******************************************************************* 197 | * Frequency API 198 | ******************************************************************/ 199 | 200 | void setFrequency(const int direction, const size_t channel, const double frequency, const SoapySDR::Kwargs &args); 201 | 202 | void setFrequency(const int direction, const size_t channel, const std::string &name, const double frequency, const SoapySDR::Kwargs &args); 203 | 204 | double getFrequency(const int direction, const size_t channel) const; 205 | 206 | double getFrequency(const int direction, const size_t channel, const std::string &name) const; 207 | 208 | std::vector listFrequencies(const int direction, const size_t channel) const; 209 | 210 | SoapySDR::RangeList getFrequencyRange(const int direction, const size_t channel) const; 211 | 212 | SoapySDR::RangeList getFrequencyRange(const int direction, const size_t channel, const std::string &name) const; 213 | 214 | SoapySDR::ArgInfoList getFrequencyArgsInfo(const int direction, const size_t channel) const; 215 | 216 | /******************************************************************* 217 | * Sample Rate API 218 | ******************************************************************/ 219 | 220 | void setSampleRate(const int direction, const size_t channel, const double rate); 221 | 222 | double getSampleRate(const int direction, const size_t channel) const; 223 | 224 | std::vector listSampleRates(const int direction, const size_t channel) const; 225 | 226 | SoapySDR::RangeList getSampleRateRange(const int direction, const size_t channel) const; 227 | 228 | /******************************************************************* 229 | * Bandwidth API 230 | ******************************************************************/ 231 | 232 | void setBandwidth(const int direction, const size_t channel, const double bw); 233 | 234 | double getBandwidth(const int direction, const size_t channel) const; 235 | 236 | std::vector listBandwidths(const int direction, const size_t channel) const; 237 | 238 | SoapySDR::RangeList getBandwidthRange(const int direction, const size_t channel) const; 239 | 240 | /******************************************************************* 241 | * Clocking API 242 | ******************************************************************/ 243 | 244 | void setMasterClockRate(const double rate); 245 | 246 | double getMasterClockRate(void) const; 247 | 248 | SoapySDR::RangeList getMasterClockRates(void) const; 249 | 250 | void setReferenceClockRate(const double rate); 251 | 252 | double getReferenceClockRate(void) const; 253 | 254 | SoapySDR::RangeList getReferenceClockRates(void) const; 255 | 256 | std::vector listClockSources(void) const; 257 | 258 | void setClockSource(const std::string &source); 259 | 260 | std::string getClockSource(void) const; 261 | 262 | /******************************************************************* 263 | * Time API 264 | ******************************************************************/ 265 | 266 | std::vector listTimeSources(void) const; 267 | 268 | void setTimeSource(const std::string &source); 269 | 270 | std::string getTimeSource(void) const; 271 | 272 | bool hasHardwareTime(const std::string &what) const; 273 | 274 | long long getHardwareTime(const std::string &what) const; 275 | 276 | void setHardwareTime(const long long timeNs, const std::string &what); 277 | 278 | void setCommandTime(const long long timeNs, const std::string &what); 279 | 280 | /******************************************************************* 281 | * Sensor API 282 | ******************************************************************/ 283 | 284 | std::vector listSensors(void) const; 285 | 286 | SoapySDR::ArgInfo getSensorInfo(const std::string &name) const; 287 | 288 | std::string readSensor(const std::string &name) const; 289 | 290 | std::vector listSensors(const int direction, const size_t channel) const; 291 | 292 | SoapySDR::ArgInfo getSensorInfo(const int direction, const size_t channel, const std::string &name) const; 293 | 294 | std::string readSensor(const int direction, const size_t channel, const std::string &name) const; 295 | 296 | /******************************************************************* 297 | * Register API 298 | ******************************************************************/ 299 | 300 | std::vector listRegisterInterfaces(void) const; 301 | 302 | void writeRegister(const std::string &name, const unsigned addr, const unsigned value); 303 | 304 | unsigned readRegister(const std::string &name, const unsigned addr) const; 305 | 306 | void writeRegister(const unsigned addr, const unsigned value); 307 | 308 | unsigned readRegister(const unsigned addr) const; 309 | 310 | void writeRegisters(const std::string &name, const unsigned addr, const std::vector &value); 311 | 312 | std::vector readRegisters(const std::string &name, const unsigned addr, const size_t length) const; 313 | 314 | /******************************************************************* 315 | * Settings API 316 | ******************************************************************/ 317 | 318 | SoapySDR::ArgInfoList getSettingInfo(void) const; 319 | 320 | SoapySDR::ArgInfo getSettingInfo(const std::string &key) const; 321 | 322 | void writeSetting(const std::string &key, const std::string &value); 323 | 324 | std::string readSetting(const std::string &key) const; 325 | 326 | SoapySDR::ArgInfoList getSettingInfo(const int direction, const size_t channel) const; 327 | 328 | SoapySDR::ArgInfo getSettingInfo(const int direction, const size_t channel, const std::string &key) const; 329 | 330 | void writeSetting(const int direction, const size_t channel, const std::string &key, const std::string &value); 331 | 332 | std::string readSetting(const int direction, const size_t channel, const std::string &key) const; 333 | 334 | /******************************************************************* 335 | * GPIO API 336 | ******************************************************************/ 337 | 338 | std::vector listGPIOBanks(void) const; 339 | 340 | void writeGPIO(const std::string &bank, const unsigned value); 341 | 342 | void writeGPIO(const std::string &bank, const unsigned value, const unsigned mask); 343 | 344 | unsigned readGPIO(const std::string &bank) const; 345 | 346 | void writeGPIODir(const std::string &bank, const unsigned dir); 347 | 348 | void writeGPIODir(const std::string &bank, const unsigned dir, const unsigned mask); 349 | 350 | unsigned readGPIODir(const std::string &bank) const; 351 | 352 | /******************************************************************* 353 | * I2C API 354 | ******************************************************************/ 355 | 356 | void writeI2C(const int addr, const std::string &data); 357 | 358 | std::string readI2C(const int addr, const size_t numBytes); 359 | 360 | /******************************************************************* 361 | * SPI API 362 | ******************************************************************/ 363 | 364 | unsigned transactSPI(const int addr, const unsigned data, const size_t numBits); 365 | 366 | /******************************************************************* 367 | * UART API 368 | ******************************************************************/ 369 | 370 | std::vector listUARTs(void) const; 371 | 372 | void writeUART(const std::string &which, const std::string &data); 373 | 374 | std::string readUART(const std::string &which, const long timeoutUs) const; 375 | 376 | private: 377 | 378 | //! Get the internal device pointer given the channel and direction 379 | SoapySDR::Device *getDevice(const int direction, const size_t channel, size_t &localChannel) const 380 | { 381 | const auto &map = (direction == SOAPY_SDR_RX)?_rxChanMap:_txChanMap; 382 | const auto &pair = map.at(channel); 383 | localChannel = pair.first; 384 | return pair.second; 385 | } 386 | 387 | //internal devices mapped by device index 388 | std::vector _devices; 389 | 390 | //mapping of channel index to internal device pointer 391 | void reloadChanMaps(void); 392 | std::vector> _rxChanMap; 393 | std::vector> _txChanMap; 394 | }; 395 | -------------------------------------------------------------------------------- /Streaming.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "SoapyMultiSDR.hpp" 5 | 6 | struct SoapyMultiStreamData 7 | { 8 | SoapySDR::Device *device; 9 | SoapySDR::Stream *stream; 10 | std::vector channels; 11 | }; 12 | 13 | struct SoapyMultiStreamsData : std::vector 14 | { 15 | //placeholder for book-keeping common to all streams 16 | }; 17 | 18 | /******************************************************************* 19 | * Stream API 20 | ******************************************************************/ 21 | 22 | std::vector SoapyMultiSDR::getStreamFormats(const int direction, const size_t channel) const 23 | { 24 | size_t localChannel = 0; 25 | auto device = this->getDevice(direction, channel, localChannel); 26 | return device->getStreamFormats(direction, localChannel); 27 | } 28 | 29 | std::string SoapyMultiSDR::getNativeStreamFormat(const int direction, const size_t channel, double &fullScale) const 30 | { 31 | size_t localChannel = 0; 32 | auto device = this->getDevice(direction, channel, localChannel); 33 | return device->getNativeStreamFormat(direction, localChannel, fullScale); 34 | } 35 | 36 | SoapySDR::ArgInfoList SoapyMultiSDR::getStreamArgsInfo(const int direction, const size_t channel) const 37 | { 38 | size_t localChannel = 0; 39 | auto device = this->getDevice(direction, channel, localChannel); 40 | return device->getStreamArgsInfo(direction, localChannel); 41 | } 42 | 43 | SoapySDR::Stream *SoapyMultiSDR::setupStream( 44 | const int direction, 45 | const std::string &format, 46 | const std::vector &channels_, 47 | const SoapySDR::Kwargs &args) 48 | { 49 | //ensure channels is at least size 1 50 | std::vector channels(channels_); 51 | if (channels.empty()) channels.push_back(0); 52 | 53 | //stream the data structure 54 | auto multiStreams = new SoapyMultiStreamsData(); 55 | 56 | //iterate through the channels to fill the data structure 57 | for (const auto &channel : channels) 58 | { 59 | size_t localChannel = 0; 60 | auto device = this->getDevice(direction, channel, localChannel); 61 | if (multiStreams->empty() or multiStreams->back().device != device) 62 | { 63 | multiStreams->resize(multiStreams->size()+1); 64 | } 65 | multiStreams->back().device = device; 66 | multiStreams->back().channels.push_back(localChannel); 67 | } 68 | 69 | //create the streams 70 | for (auto &multiStream : *multiStreams) 71 | { 72 | multiStream.stream = multiStream.device->setupStream( 73 | direction, format, multiStream.channels, args); 74 | } 75 | 76 | return reinterpret_cast(multiStreams); 77 | } 78 | 79 | void SoapyMultiSDR::closeStream(SoapySDR::Stream *stream) 80 | { 81 | auto multiStreams = reinterpret_cast(stream); 82 | for (auto &multiStream : *multiStreams) 83 | { 84 | multiStream.device->closeStream(multiStream.stream); 85 | } 86 | delete multiStreams; 87 | } 88 | 89 | size_t SoapyMultiSDR::getStreamMTU(SoapySDR::Stream *stream) const 90 | { 91 | auto multiStreams = reinterpret_cast(stream); 92 | const auto &stream0 = multiStreams->front(); 93 | return stream0.device->getStreamMTU(stream0.stream); 94 | } 95 | 96 | int SoapyMultiSDR::activateStream( 97 | SoapySDR::Stream *stream, 98 | const int flags, 99 | const long long timeNs, 100 | const size_t numElems) 101 | { 102 | auto multiStreams = reinterpret_cast(stream); 103 | for (auto &multiStream : *multiStreams) 104 | { 105 | int ret = multiStream.device->activateStream(multiStream.stream, flags, timeNs, numElems); 106 | if (ret != 0) return ret; 107 | } 108 | return 0; 109 | } 110 | 111 | int SoapyMultiSDR::deactivateStream( 112 | SoapySDR::Stream *stream, 113 | const int flags, 114 | const long long timeNs) 115 | { 116 | auto multiStreams = reinterpret_cast(stream); 117 | for (auto &multiStream : *multiStreams) 118 | { 119 | int ret = multiStream.device->deactivateStream(multiStream.stream, flags, timeNs); 120 | if (ret != 0) return ret; 121 | } 122 | return 0; 123 | } 124 | 125 | int SoapyMultiSDR::readStream( 126 | SoapySDR::Stream *stream, 127 | void * const *buffs, 128 | const size_t numElems, 129 | int &flags, 130 | long long &timeNs, 131 | const long timeoutUs) 132 | { 133 | auto multiStreams = reinterpret_cast(stream); 134 | 135 | int ret = 0; 136 | int offset = 0; 137 | int originalFlags = flags; 138 | int flagsOut = 0; 139 | long long timeNsOut = 0; 140 | 141 | for (auto &multiStream : *multiStreams) 142 | { 143 | flags = originalFlags; //restore flags before each call 144 | ret = multiStream.device->readStream(multiStream.stream, 145 | buffs+offset, numElems, flags, timeNs, timeoutUs); 146 | if (ret <= 0) return ret; 147 | 148 | //on the first readStream, store the output flags and time 149 | if (offset == 0) 150 | { 151 | flagsOut = flags; 152 | timeNsOut = timeNs; 153 | } 154 | 155 | offset += multiStream.channels.size(); 156 | } 157 | 158 | //Note: ret represents the last read number of elements 159 | //this should be homogeneous, but if not, this implementation 160 | //should be improved to handle different length reads 161 | //by saving the remainder and writing it into the buffer 162 | //on subsequent calls and performing other book-keeping tasks. 163 | 164 | //setup the result 165 | flags = flagsOut; 166 | timeNs = timeNsOut; 167 | return ret; 168 | } 169 | 170 | int SoapyMultiSDR::writeStream( 171 | SoapySDR::Stream *stream, 172 | const void * const *buffs, 173 | const size_t numElems, 174 | int &flags, 175 | const long long timeNs, 176 | const long timeoutUs) 177 | { 178 | auto multiStreams = reinterpret_cast(stream); 179 | 180 | int ret = 0; 181 | int offset = 0; 182 | int originalFlags = flags; 183 | int flagsOut = 0; 184 | 185 | for (auto &multiStream : *multiStreams) 186 | { 187 | flags = originalFlags; //restore flags before each call 188 | ret = multiStream.device->writeStream(multiStream.stream, 189 | buffs+offset, numElems, flags, timeNs, timeoutUs); 190 | if (ret <= 0) return ret; 191 | 192 | //on the first writeStream, store the output flags 193 | if (offset == 0) flagsOut = flags; 194 | 195 | offset += multiStream.channels.size(); 196 | } 197 | 198 | //Note: ret represents the last written number of elements 199 | //this should be homogeneous, but if not, this implementation 200 | //should be improved to handle different length writes 201 | //by saving the remainder and reading it from the buffer 202 | //on subsequent calls and performing other book-keeping tasks. 203 | 204 | //setup the result 205 | flags = flagsOut; 206 | return ret; 207 | } 208 | 209 | int SoapyMultiSDR::readStreamStatus( 210 | SoapySDR::Stream *stream, 211 | size_t &chanMask, 212 | int &flags, 213 | long long &timeNs, 214 | const long timeoutUs) 215 | { 216 | auto multiStreams = reinterpret_cast(stream); 217 | 218 | int ret = 0; 219 | size_t offset = 0; 220 | for (auto &multiStream : *multiStreams) 221 | { 222 | ret = multiStream.device->readStreamStatus(multiStream.stream, 223 | chanMask, flags, timeNs, timeoutUs); 224 | 225 | chanMask <<= offset; //mask bits shifted up for global channel mapping 226 | 227 | if (ret == 0) return ret; //status message found 228 | 229 | offset += multiStream.channels.size(); 230 | } 231 | 232 | //return last-seen error code (probably timeout or not supported) 233 | return ret; 234 | } 235 | 236 | /******************************************************************* 237 | * Direct buffer access API 238 | ******************************************************************/ 239 | 240 | size_t SoapyMultiSDR::getNumDirectAccessBuffers(SoapySDR::Stream *stream) 241 | { 242 | auto multiStreams = reinterpret_cast(stream); 243 | auto &multiStream0 = multiStreams->front(); 244 | return multiStream0.device->getNumDirectAccessBuffers(multiStream0.stream); 245 | } 246 | 247 | int SoapyMultiSDR::getDirectAccessBufferAddrs(SoapySDR::Stream *stream, const size_t handle, void **buffs) 248 | { 249 | auto multiStreams = reinterpret_cast(stream); 250 | 251 | size_t offset = 0; 252 | for (auto &multiStream : *multiStreams) 253 | { 254 | int ret = multiStream.device->getDirectAccessBufferAddrs(multiStream.stream, handle, buffs+offset); 255 | if (ret != 0) return ret; 256 | offset += multiStream.channels.size(); 257 | } 258 | 259 | return 0; 260 | } 261 | 262 | int SoapyMultiSDR::acquireReadBuffer( 263 | SoapySDR::Stream *stream, 264 | size_t &handle, 265 | const void **buffs, 266 | int &flags, 267 | long long &timeNs, 268 | const long timeoutUs) 269 | { 270 | auto multiStreams = reinterpret_cast(stream); 271 | 272 | int ret = 0; 273 | int offset = 0; 274 | int originalFlags = flags; 275 | int flagsOut = 0; 276 | long long timeNsOut = 0; 277 | 278 | for (auto &multiStream : *multiStreams) 279 | { 280 | flags = originalFlags; //restore flags before each call 281 | ret = multiStream.device->acquireReadBuffer(multiStream.stream, 282 | handle, buffs+offset, flags, timeNs, timeoutUs); 283 | if (ret <= 0) return ret; 284 | 285 | //on the first readStream, store the output flags and time 286 | if (offset == 0) 287 | { 288 | flagsOut = flags; 289 | timeNsOut = timeNs; 290 | } 291 | 292 | offset += multiStream.channels.size(); 293 | } 294 | 295 | //setup the result 296 | flags = flagsOut; 297 | timeNs = timeNsOut; 298 | return ret; 299 | } 300 | 301 | void SoapyMultiSDR::releaseReadBuffer( 302 | SoapySDR::Stream *stream, 303 | const size_t handle) 304 | { 305 | auto multiStreams = reinterpret_cast(stream); 306 | 307 | for (auto &multiStream : *multiStreams) 308 | { 309 | multiStream.device->releaseReadBuffer(multiStream.stream, handle); 310 | } 311 | } 312 | 313 | int SoapyMultiSDR::acquireWriteBuffer( 314 | SoapySDR::Stream *stream, 315 | size_t &handle, 316 | void **buffs, 317 | const long timeoutUs) 318 | { 319 | auto multiStreams = reinterpret_cast(stream); 320 | 321 | int ret = 0; 322 | int offset = 0; 323 | 324 | for (auto &multiStream : *multiStreams) 325 | { 326 | multiStream.device->acquireWriteBuffer(multiStream.stream, handle, buffs+offset, timeoutUs); 327 | if (ret <= 0) return ret; 328 | offset += multiStream.channels.size(); 329 | } 330 | 331 | return ret; 332 | } 333 | 334 | void SoapyMultiSDR::releaseWriteBuffer( 335 | SoapySDR::Stream *stream, 336 | const size_t handle, 337 | const size_t numElems, 338 | int &flags, 339 | const long long timeNs) 340 | { 341 | auto multiStreams = reinterpret_cast(stream); 342 | 343 | int offset = 0; 344 | int originalFlags = flags; 345 | int flagsOut = 0; 346 | 347 | for (auto &multiStream : *multiStreams) 348 | { 349 | flags = originalFlags; //restore flags before each call 350 | multiStream.device->releaseWriteBuffer(multiStream.stream, handle, numElems, flags, timeNs); 351 | 352 | //on the first writeStream, store the output flags 353 | if (offset == 0) flagsOut = flags; 354 | 355 | offset += multiStream.channels.size(); 356 | } 357 | 358 | //setup the result 359 | flags = flagsOut; 360 | } 361 | -------------------------------------------------------------------------------- /TestMultiNameUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | #include "MultiNameUtils.hpp" 6 | #include 7 | #include 8 | 9 | int main(void) 10 | { 11 | std::cout << "test toIndexedName()..." << std::endl; 12 | if (toIndexedName("test", 123) != "test[123]") return EXIT_FAILURE; 13 | 14 | std::cout << "test isIndexedName()..." << std::endl; 15 | if (isIndexedName("test")) return EXIT_FAILURE; 16 | if (isIndexedName("test 123")) return EXIT_FAILURE; 17 | if (not isIndexedName("test[123]")) return EXIT_FAILURE; 18 | 19 | std::cout << "test splitIndexedName()..." << std::endl; 20 | size_t index; 21 | if (splitIndexedName("test[123]", index) != "test") return EXIT_FAILURE; 22 | if (index != 123) return EXIT_FAILURE; 23 | try 24 | { 25 | splitIndexedName("test 123", index); //should throw 26 | return EXIT_FAILURE; 27 | } 28 | catch (const std::exception &ex){} 29 | 30 | std::cout << "test csvSplit()..." << std::endl; 31 | const auto split = csvSplit("foo1, bar2, baz3"); 32 | if (split.size() != 3) return EXIT_FAILURE; 33 | if (split.at(0) != "foo1") return EXIT_FAILURE; 34 | if (split.at(1) != "bar2") return EXIT_FAILURE; 35 | if (split.at(2) != "baz3") return EXIT_FAILURE; 36 | 37 | std::cout << "test csvJoin()..." << std::endl; 38 | if (csvJoin(split) != "foo1, bar2, baz3") return EXIT_FAILURE; 39 | 40 | return EXIT_SUCCESS; 41 | } 42 | --------------------------------------------------------------------------------