[hamradio-commits] [soapysdr] 01/11: New upstream version 0.6.0

Andreas E. Bombe aeb at moszumanska.debian.org
Tue Jun 27 01:08:58 UTC 2017


This is an automated email from the git hooks/post-receive script.

aeb pushed a commit to branch master
in repository soapysdr.

commit 9674426b3c685234d23494e812b054c2a6b01cb5
Author: Andreas Bombe <aeb at debian.org>
Date:   Tue Jun 27 01:05:34 2017 +0200

    New upstream version 0.6.0
---
 .travis.yml                                        |  41 ++---
 CMakeLists.txt                                     |   2 +-
 Changelog.txt                                      |  28 +++
 README.md                                          |   4 +-
 apps/CMakeLists.txt                                |   1 +
 apps/SoapyRateTest.cpp                             | 149 ++++++++++++++++
 apps/SoapySDRProbe.cpp                             |  19 ++-
 apps/SoapySDRUtil.cpp                              |  43 +++++
 cmake/SoapySDRConfig.cmake                         |   2 +-
 debian/changelog                                   |  12 ++
 debian/control                                     |  10 +-
 debian/copyright                                   |   2 +-
 ...oapysdr0.5-2.install => libsoapysdr0.6.install} |   0
 include/SoapySDR/Device.h                          | 190 +++++++++++++++------
 include/SoapySDR/Device.hpp                        | 118 ++++++++++---
 include/SoapySDR/Modules.h                         |   7 +
 include/SoapySDR/Modules.hpp                       |   6 +
 include/SoapySDR/Types.h                           |  15 +-
 include/SoapySDR/Types.hpp                         |  26 ++-
 include/SoapySDR/Version.h                         |  32 +++-
 lib/Device.cpp                                     |  53 +++++-
 lib/DeviceC.cpp                                    | 134 +++++++++------
 lib/Factory.cpp                                    | 110 +++++-------
 lib/FactoryC.cpp                                   |   6 +-
 lib/LoggerC.cpp                                    |   3 +-
 lib/Modules.in.cpp                                 |  11 +-
 lib/ModulesC.cpp                                   |   7 +-
 lib/TypeHelpers.hpp                                |  19 ++-
 lib/Types.cpp                                      |  62 ++++++-
 lib/TypesC.cpp                                     |  13 +-
 python/CMakeLists.txt                              |  25 ++-
 python/SoapySDR.i                                  |  26 ++-
 python/apps/SimpleSiggen.py                        |   2 +-
 python3/CMakeLists.txt                             |   6 +-
 tests/CMakeLists.txt                               |   4 +
 tests/TestKwargsMarkup.cpp                         |  76 +++++++++
 36 files changed, 995 insertions(+), 269 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 527dc45..d0f335c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,17 +12,25 @@ sudo: required
 dist: trusty
 
 language: cpp
-compiler: gcc
+
+matrix:
+  include:
+    - os: linux
+      compiler: gcc
+      env:
+        global:
+          - PYTHON_EXECUTABLE=/usr/bin/python
+          - PYTHON3_EXECUTABLE=/usr/bin/python3
+    - os: osx
+      compiler: clang
+      env:
+        global:
+          - PYTHON_EXECUTABLE=/usr/local/bin/python
+          - PYTHON3_EXECUTABLE=/usr/local/bin/python3
 
 env:
   global:
     - INSTALL_PREFIX=/usr/local
-    - PYTHON_EXECUTABLE=/usr/bin/python
-    - PYTHON_INSTALL_DIR=lib/python2.7/dist-packages
-    - PYTHON3_EXECUTABLE=/usr/bin/python3
-    - PYTHON3_INSTALL_DIR=lib/python3/dist-packages
-  matrix:
-    - BUILD_TYPE=Debug
     - BUILD_TYPE=Release
 
 # whitelist
@@ -31,26 +39,19 @@ branches:
     - master
     - maint
 
-before_install:
-  # regular ubuntu packages
-  - sudo add-apt-repository main
-  - sudo add-apt-repository universe
-
-  # update after package changes
-  - sudo apt-get update
-
 install:
 
   # install python support dependencies
-  - sudo apt-get install -qq python python-dev python-numpy swig
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -qq python python-dev python-numpy swig; fi;
 
   # install python3 support dependencies
-  - sudo apt-get install -qq python3 python3-dev python3-numpy swig
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -qq python3 python3-dev python3-numpy swig; fi;
+  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install swig python3 ; fi
 
 script:
   - mkdir build
   - cd build
-  - cmake ../ -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -DPYTHON3_EXECUTABLE=${PYTHON3_EXECUTABLE}
+  - cmake ../ -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DUSE_PYTHON_CONFIG=ON -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -DPYTHON3_EXECUTABLE=${PYTHON3_EXECUTABLE}
   - make
   - sudo make install
 
@@ -64,7 +65,7 @@ script:
   - SoapySDRUtil --make="driver=null"
 
   # basic test for python bindings
-  - export PYTHONPATH=${INSTALL_PREFIX}/${PYTHON_INSTALL_DIR}:/usr/${PYTHON_INSTALL_DIR}
+  - export PYTHONPATH=$(${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True, prefix='${INSTALL_PREFIX}'))")
   - echo ${PYTHONPATH}
   - ${PYTHON_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.getAPIVersion())"
   - ${PYTHON_EXECUTABLE} -c "from SoapySDR import *; print(SOAPY_SDR_ABI_VERSION)"
@@ -73,7 +74,7 @@ script:
   - ${PYTHON_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.Device.make('driver=null'))"
 
   # basic test for python3 bindings
-  - export PYTHONPATH=${INSTALL_PREFIX}/${PYTHON3_INSTALL_DIR}:/usr/${PYTHON3_INSTALL_DIR}
+  - export PYTHONPATH=$(${PYTHON3_EXECUTABLE} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True, prefix='${INSTALL_PREFIX}'))")
   - echo ${PYTHONPATH}
   - ${PYTHON3_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.getAPIVersion())"
   - ${PYTHON3_EXECUTABLE} -c "from SoapySDR import *; print(SOAPY_SDR_ABI_VERSION)"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3f06553..c541a30 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -10,7 +10,7 @@ enable_testing()
 # gather version information
 # packagers may specify -DSOAPY_SDR_EXTVER="foo" to replace the git hash
 ########################################################################
-set(SOAPY_SDR_LIBVER "0.5.4")
+set(SOAPY_SDR_LIBVER "0.6.0")
 
 if (NOT SOAPY_SDR_EXTVER)
     include(${PROJECT_SOURCE_DIR}/cmake/GetGitRevisionDescription.cmake)
diff --git a/Changelog.txt b/Changelog.txt
index dc7cddd..1c9fe2a 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,5 +1,33 @@
 This this the changelog file for the SoapySDR project.
 
+Release 0.6.0 (2017-04-29)
+==========================
+
+Device C API changes:
+
+- Device readI2C - make numBytes an in/out argument
+- Device read/write registers switch to named variant
+- Status return for Device C API calls with void return
+
+General additions and changes:
+
+- Added frequency corrections API for fine adjustments
+- Added getSampleRateRange() API for continuous ranges
+- Device factory table keys based on enumeration results
+- Added optional step size to the range type
+- Added rate testing to SoapySDRUtil application
+- Added listSearchPaths() API and SoapySDRUtil print
+- Added Kwargs type to/from markup string API calls
+- Added read/writeRegisters() API for bulk register IO
+
+Release 0.5.5 (2017-04-28)
+==========================
+
+- Added logger API and constants to python SWIG bindings
+- Added missing time utils to the swig python support
+- Fixed missing INFO log level for SOAPY_SDR_LOG_LEVEL
+- Fix for cache overwrite of PYTHON_INSTALL_DIR variable
+
 Release 0.5.4 (2016-11-29)
 ==========================
 
diff --git a/README.md b/README.md
index 2494865..843e908 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
 # Soapy SDR - vendor and platform neutral SDR support library.
 
-##Build Status
+## Build Status
 
 - Travis: [![Travis Build Status](https://travis-ci.org/pothosware/SoapySDR.svg?branch=master)](https://travis-ci.org/pothosware/SoapySDR)
 - AppVeyor: [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/6chajdpy7uk5ax89)](https://ci.appveyor.com/project/guruofquality/soapysdr)
 
-##Documentation
+## Documentation
 
 * https://github.com/pothosware/SoapySDR/wiki
 
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index 124eca1..edbf3f2 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -4,6 +4,7 @@
 set(SOAPY_SDR_UTIL_SOURCES
     SoapySDRUtil.cpp
     SoapySDRProbe.cpp
+    SoapyRateTest.cpp
 )
 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include)
 if (MSVC)
diff --git a/apps/SoapyRateTest.cpp b/apps/SoapyRateTest.cpp
new file mode 100644
index 0000000..fc953b8
--- /dev/null
+++ b/apps/SoapyRateTest.cpp
@@ -0,0 +1,149 @@
+// Copyright (c) 2016-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Device.hpp>
+#include <SoapySDR/Formats.hpp>
+#include <SoapySDR/Errors.hpp>
+#include <string>
+#include <cstdlib>
+#include <iostream>
+#include <stdexcept>
+#include <csignal>
+#include <chrono>
+#include <cstdio>
+
+static sig_atomic_t loopDone = false;
+void sigIntHandler(const int)
+{
+    loopDone = true;
+}
+
+void runRateTestStreamLoop(
+    SoapySDR::Device *device,
+    SoapySDR::Stream *stream,
+    const int direction,
+    const size_t numChans,
+    const size_t elemSize)
+{
+    //allocate buffers for the stream read/write
+    const size_t numElems = device->getStreamMTU(stream);
+    std::vector<std::vector<char>> buffMem(numChans, std::vector<char>(elemSize*numElems));
+    std::vector<void *> buffs(numChans);
+    for (size_t i = 0; i < numChans; i++) buffs[i] = buffMem[i].data();
+
+    //state collected in this loop
+    unsigned int overflows(0);
+    unsigned int underflows(0);
+    unsigned long long totalSamples(0);
+    const auto startTime = std::chrono::high_resolution_clock::now();
+    auto timeLastPrint = std::chrono::high_resolution_clock::now();
+
+    std::cout << "Starting stream loop, press Ctrl+C to exit..." << std::endl;
+    device->activateStream(stream);
+    signal(SIGINT, sigIntHandler);
+    while (not loopDone)
+    {
+        int ret(0);
+        int flags(0);
+        long long timeNs(0);
+        switch(direction)
+        {
+        case SOAPY_SDR_RX:
+            ret = device->readStream(stream, buffs.data(), numElems, flags, timeNs);
+            break;
+        case SOAPY_SDR_TX:
+            ret = device->writeStream(stream, buffs.data(), numElems, flags, timeNs);
+            break;
+        }
+
+        if (ret == SOAPY_SDR_TIMEOUT) continue;
+        if (ret == SOAPY_SDR_OVERFLOW)
+        {
+            overflows++;
+            continue;
+        }
+        if (ret == SOAPY_SDR_UNDERFLOW)
+        {
+            underflows++;
+            continue;
+        }
+        if (ret < 0)
+        {
+            std::cerr << "Unexpected stream error " << SoapySDR::errToStr(ret) << std::endl;
+            break;
+        }
+        totalSamples += ret;
+
+        const auto now = std::chrono::high_resolution_clock::now();
+        if (timeLastPrint + std::chrono::seconds(5) < now)
+        {
+            timeLastPrint = now;
+            const auto timePassed = std::chrono::duration_cast<std::chrono::microseconds>(now - startTime);
+            const auto sampleRate = double(totalSamples)/timePassed.count();
+            printf("%g Msps\t%g Bps", sampleRate, sampleRate*numChans*elemSize);
+            if (overflows != 0) printf("\tOverflows %u", overflows);
+            if (underflows != 0) printf("\tUnderflows %u", underflows);
+            printf("\n");
+        }
+
+    }
+    device->deactivateStream(stream);
+}
+
+int SoapySDRRateTest(
+    const std::string &argStr,
+    const double sampleRate,
+    const std::string &channelStr,
+    const std::string &directionStr)
+{
+    SoapySDR::Device *device(nullptr);
+
+    try
+    {
+        device = SoapySDR::Device::make(argStr);
+
+        //parse the direction to the integer enum
+        int direction(-1);
+        if (directionStr == "RX" or directionStr == "rx") direction = SOAPY_SDR_RX;
+        if (directionStr == "TX" or directionStr == "tx") direction = SOAPY_SDR_TX;
+        if (direction == -1) throw std::invalid_argument("direction not in RX/TX: " + directionStr);
+
+        //build channels list, using KwargsFromString is a easy parsing hack
+        std::vector<size_t> channels;
+        for (const auto &pair : SoapySDR::KwargsFromString(channelStr))
+        {
+            channels.push_back(std::stoi(pair.first));
+        }
+        if (channels.empty()) channels.push_back(0);
+
+        //initialize the sample rate for all channels
+        for (const auto &chan : channels)
+        {
+            device->setSampleRate(direction, chan, sampleRate);
+        }
+
+        //create the stream, use the native format
+        double fullScale(0.0);
+        const auto format = device->getNativeStreamFormat(direction, channels.front(), fullScale);
+        const size_t elemSize = SoapySDR::formatToSize(format);
+        auto stream = device->setupStream(direction, format, channels);
+
+        //run the rate test one setup is complete
+        std::cout << "Stream format: " << format << std::endl;
+        std::cout << "Num channels: " << channels.size() << std::endl;
+        std::cout << "Element size: " << elemSize << " bytes" << std::endl;
+        std::cout << "Begin " << directionStr << " rate test at " << (sampleRate/1e6) << " Msps" << std::endl;
+        runRateTestStreamLoop(device, stream, direction, channels.size(), elemSize);
+
+        //cleanup stream and device
+        device->closeStream(stream);
+        SoapySDR::Device::unmake(device);
+    }
+    catch (const std::exception &ex)
+    {
+        std::cerr << "Error in rate test: " << ex.what() << std::endl;
+        SoapySDR::Device::unmake(device);
+        return EXIT_FAILURE;
+    }
+    return EXIT_FAILURE;
+}
diff --git a/apps/SoapySDRProbe.cpp b/apps/SoapySDRProbe.cpp
index 228bafc..cb353de 100644
--- a/apps/SoapySDRProbe.cpp
+++ b/apps/SoapySDRProbe.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // Copyright (c) 2016-2016 Bastille Networks
 // SPDX-License-Identifier: BSL-1.0
 
@@ -21,17 +21,26 @@ std::string toString(const std::vector<Type> &options)
 std::string toString(const SoapySDR::Range &range)
 {
     std::stringstream ss;
-    ss << "[" << range.minimum() << ", " << range.maximum() << "]";
+    ss << "[" << range.minimum() << ", " << range.maximum();
+    if (range.step() != 0.0) ss << ", " << range.step();
+    ss << "]";
     return ss.str();
 }
 
 std::string toString(const SoapySDR::RangeList &range, const double scale)
 {
+    const size_t MAXRLEN = 10; //for abbreviating long lists
     std::stringstream ss;
     for (size_t i = 0; i < range.size(); i++)
     {
+        if (range.size() >= MAXRLEN and i >= MAXRLEN/2 and i < (range.size()-MAXRLEN/2))
+        {
+            if (i == MAXRLEN) ss << ", ...";
+            continue;
+        }
         if (not ss.str().empty()) ss << ", ";
-        ss << "[" << (range[i].minimum()/scale) << ", " << (range[i].maximum()/scale) << "]";
+        if (range[i].minimum() == range[i].maximum()) ss << (range[i].minimum()/scale);
+        else ss << "[" << (range[i].minimum()/scale) << ", " << (range[i].maximum()/scale) << "]";
     }
     return ss.str();
 }
@@ -179,10 +188,10 @@ static std::string probeChannel(SoapySDR::Device *device, const int dir, const s
     if (not freqArgs.empty()) ss << "  Tune args:" << std::endl << freqArgs;
 
     //rates
-    ss << "  Sample rates: " << toString(device->listSampleRates(dir, chan), 1e6) << " MHz" << std::endl;
+    ss << "  Sample rates: " << toString(device->getSampleRateRange(dir, chan), 1e6) << " MSps" << std::endl;
 
     //bandwidths
-    const std::vector<double> bws = device->listBandwidths(dir, chan);
+    const auto bws = device->getBandwidthRange(dir, chan);
     if (not bws.empty()) ss << "  Filter bandwidths: " << toString(bws, 1e6) << " MHz" << std::endl;
 
     //sensors
diff --git a/apps/SoapySDRUtil.cpp b/apps/SoapySDRUtil.cpp
index ed52a0a..7cd6e05 100644
--- a/apps/SoapySDRUtil.cpp
+++ b/apps/SoapySDRUtil.cpp
@@ -11,6 +11,11 @@
 #include <getopt.h>
 
 std::string SoapySDRDeviceProbe(SoapySDR::Device *);
+int SoapySDRRateTest(
+    const std::string &argStr,
+    const double sampleRate,
+    const std::string &channelStr,
+    const std::string &directionStr);
 
 /***********************************************************************
  * Print help message
@@ -26,6 +31,13 @@ static int printHelp(void)
     std::cout << "    --probe[=\"driver=foo,type=bar\"] \t Print detailed information" << std::endl;
     std::cout << "    --check[=driverName] \t\t Check if driver is present" << std::endl;
     std::cout << std::endl;
+
+    std::cout << "  Rate testing options:" << std::endl;
+    std::cout << "    --args[=\"driver=foo\"] \t\t Arguments for testing" << std::endl;
+    std::cout << "    --rate[=stream rate Sps] \t\t Rate in samples per second" << std::endl;
+    std::cout << "    --channels[=\"0, 1, 2\"] \t\t List of channels, default 0" << std::endl;
+    std::cout << "    --direction[=RX or TX] \t\t Specify the channel direction" << std::endl;
+    std::cout << std::endl;
     return EXIT_SUCCESS;
 }
 
@@ -39,6 +51,9 @@ static int printInfo(void)
     std::cout << "ABI Version: v" << SoapySDR::getABIVersion() << std::endl;
     std::cout << "Install root: " << SoapySDR::getRootPath() << std::endl;
 
+    for (const auto &path : SoapySDR::listSearchPaths())
+        std::cout << "Search path: " << path << std::endl;
+
     const auto modules = SoapySDR::listModules();
     for (const auto &mod : modules) std::cout << "Module found: " << mod << std::endl;
     if (modules.empty()) std::cout << "No modules found!" << std::endl;
@@ -172,6 +187,11 @@ int main(int argc, char *argv[])
     std::cout << "######################################################" << std::endl;
     std::cout << std::endl;
 
+    std::string argStr;
+    std::string chanStr;
+    std::string dirStr;
+    double sampleRate(0.0);
+
     /*******************************************************************
      * parse command line options
      ******************************************************************/
@@ -182,6 +202,11 @@ int main(int argc, char *argv[])
         {"info", optional_argument, 0, 'i'},
         {"probe", optional_argument, 0, 'p'},
         {"check", optional_argument, 0, 'c'},
+
+        {"args", optional_argument, 0, 'a'},
+        {"rate", optional_argument, 0, 'r'},
+        {"channels", optional_argument, 0, 'n'},
+        {"direction", optional_argument, 0, 'd'},
         {0, 0, 0,  0}
     };
     int long_index = 0;
@@ -196,9 +221,27 @@ int main(int argc, char *argv[])
         case 'm': return makeDevice();
         case 'p': return probeDevice();
         case 'c': return checkDriver();
+        case 'a':
+            if (optarg != nullptr) argStr = optarg;
+            break;
+        case 'r':
+            if (optarg != nullptr) sampleRate = std::stod(optarg);
+            break;
+        case 'n':
+            if (optarg != nullptr) chanStr = optarg;
+            break;
+        case 'd':
+            if (optarg != nullptr) dirStr = optarg;
+            break;
         }
     }
 
+    //invoke utilities that rely on multiple arguments
+    if (sampleRate != 0.0)
+    {
+        return SoapySDRRateTest(argStr, sampleRate, chanStr, dirStr);
+    }
+
     //unknown or unspecified options, do help...
     return printHelp();
 }
diff --git a/cmake/SoapySDRConfig.cmake b/cmake/SoapySDRConfig.cmake
index 984e1a0..682ccaf 100644
--- a/cmake/SoapySDRConfig.cmake
+++ b/cmake/SoapySDRConfig.cmake
@@ -119,7 +119,7 @@ endif()
 ########################################################################
 function(_SOAPY_SDR_GET_ABI_VERSION VERSION SOAPY_SDR_INCLUDE_DIR)
     file(READ "${SOAPY_SDR_INCLUDE_DIR}/SoapySDR/Version.h" version_h)
-    string(REGEX MATCH "\\#define SOAPY_SDR_ABI_VERSION \"([0-9]+\\.[0-9]+-[0-9]+)\"" SOAPY_SDR_ABI_VERSION_MATCHES "${version_h}")
+    string(REGEX MATCH "\\#define SOAPY_SDR_ABI_VERSION \"([0-9]+\\.[0-9]+(-[A-Za-z0-9]+)?)\"" SOAPY_SDR_ABI_VERSION_MATCHES "${version_h}")
     if(NOT SOAPY_SDR_ABI_VERSION_MATCHES)
         message(FATAL_ERROR "Failed to extract version number from Version.h")
     endif(NOT SOAPY_SDR_ABI_VERSION_MATCHES)
diff --git a/debian/changelog b/debian/changelog
index 34cb1a6..ff973ba 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+soapysdr (0.6.0-1) unstable; urgency=low
+
+  * Release 0.6.0 (2017-04-29)
+
+ -- Josh Blum <josh at pothosware.com>  Sat, 29 Apr 2017 08:35:26 -0000
+
+soapysdr (0.5.5-1) unstable; urgency=low
+
+  * Release 0.5.5 (2017-04-28)
+
+ -- Josh Blum <josh at pothosware.com>  Fri, 28 Apr 2017 21:53:39 -0000
+
 soapysdr (0.5.4-ppa1) unstable; urgency=low
 
   * Release 0.5.4 (2016-11-29)
diff --git a/debian/control b/debian/control
index ddd69a9..1112831 100644
--- a/debian/control
+++ b/debian/control
@@ -15,7 +15,7 @@ Homepage: https://github.com/pothosware/SoapySDR/wiki
 Vcs-Git: https://github.com/pothosware/SoapySDR.git
 Vcs-Browser: https://github.com/pothosware/SoapySDR
 
-Package: libsoapysdr0.5-2
+Package: libsoapysdr0.6
 Section: libs
 Architecture: any
 Multi-Arch: same
@@ -32,7 +32,7 @@ Package: libsoapysdr-dev
 Section: libdevel
 Architecture: any
 Depends:
-    libsoapysdr0.5-2 (= ${binary:Version}),
+    libsoapysdr0.6 (= ${binary:Version}),
     ${misc:Depends}
 Description: SoapySDR library development files
  SoapySDR is a library providing a common interface to SDR (software
@@ -44,7 +44,7 @@ Description: SoapySDR library development files
 Package: soapysdr
 Architecture: any
 Depends:
-    libsoapysdr0.5-2 (= ${binary:Version}),
+    libsoapysdr0.6 (= ${binary:Version}),
     ${shlibs:Depends},
     ${misc:Depends}
 Description: software defined radio interface library tools
@@ -59,7 +59,7 @@ Package: python-soapysdr
 Section: python
 Architecture: any
 Depends:
-    libsoapysdr0.5-2 (= ${binary:Version}),
+    libsoapysdr0.6 (= ${binary:Version}),
     python,
     ${shlibs:Depends},
     ${misc:Depends}
@@ -75,7 +75,7 @@ Package: python3-soapysdr
 Section: python
 Architecture: any
 Depends:
-    libsoapysdr0.5-2 (= ${binary:Version}),
+    libsoapysdr0.6 (= ${binary:Version}),
     python3,
     ${shlibs:Depends},
     ${misc:Depends}
diff --git a/debian/copyright b/debian/copyright
index 3166daa..9c0ac39 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -4,7 +4,7 @@ Source: https://github.com/pothosware/SoapySDR/wiki
 
 Files: *
 Copyright:
-    Copyright (c) 2014-2016 Josh Blum <josh at pothosware.com>
+    Copyright (c) 2014-2017 Josh Blum <josh at pothosware.com>
     Copyright (c) 2016-2016 Bastille Networks
 License: BSL-1.0
  Boost Software License - Version 1.0 - August 17th, 2003
diff --git a/debian/libsoapysdr0.5-2.install b/debian/libsoapysdr0.6.install
similarity index 100%
rename from debian/libsoapysdr0.5-2.install
rename to debian/libsoapysdr0.6.install
diff --git a/include/SoapySDR/Device.h b/include/SoapySDR/Device.h
index 8c78c3e..7edfeea 100644
--- a/include/SoapySDR/Device.h
+++ b/include/SoapySDR/Device.h
@@ -8,7 +8,7 @@
 /// The caller must free non-const array results.
 ///
 /// \copyright
-/// Copyright (c) 2014-2016 Josh Blum
+/// Copyright (c) 2014-2017 Josh Blum
 /// Copyright (c) 2016-2016 Bastille Networks
 /// SPDX-License-Identifier: BSL-1.0
 ///
@@ -84,19 +84,17 @@ SOAPY_SDR_API SoapySDRDevice *SoapySDRDevice_make(const SoapySDRKwargs *args);
  * For every call to make, there should be a matched call to unmake.
  *
  * \param args a markup string of key/value arguments
- * \return a pointer to a new Device object
+ * \return a pointer to a new Device object or null for error
  */
 SOAPY_SDR_API SoapySDRDevice *SoapySDRDevice_makeStrArgs(const char *args);
 
 /*!
  * Unmake or release a device object handle.
  *
- * \note This call is not thread safe. Implementations calling into unmake
- * from multiple threads should protect this call with a mutex.
- *
  * \param device a pointer to a device object
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_unmake(SoapySDRDevice *device);
+SOAPY_SDR_API int SoapySDRDevice_unmake(SoapySDRDevice *device);
 
 /*******************************************************************
  * Identification API
@@ -186,7 +184,8 @@ SOAPY_SDR_API bool SoapySDRDevice_getFullDuplex(const SoapySDRDevice *device, co
  * \param direction the channel direction RX or TX
  * \param channel an available channel on the device
  * \param [out] length the number of format strings
- * \return a list of allowed format strings
+ * \return a list of allowed format strings.
+ *  See SoapySDRDevice_setupStream() for the format syntax.
  */
 SOAPY_SDR_API char **SoapySDRDevice_getStreamFormats(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
 
@@ -214,34 +213,55 @@ SOAPY_SDR_API SoapySDRArgInfo *SoapySDRDevice_getStreamArgsInfo(const SoapySDRDe
 
 /*!
  * Initialize a stream given a list of channels and stream arguments.
+ * The implementation may change switches or power-up components.
  * All stream API calls should be usable with the new stream object
  * after setupStream() is complete, regardless of the activity state.
  *
- * Format string markup guidelines:
- *  - C means complex
- *  - F means floating point
- *  - S means signed integer
- *  - U means unsigned integer
- *  - number float/int size in bytes (complex is 2x this size)
- *
- * Example format strings:
- *  - CF32 complex float32 (8 bytes per element)
- *  - CS16 complex int16 (4 bytes per element)
- *  - CS12 complex int12 (3 bytes per element)
- *  - CS4 complex int4 (1 byte per element)
- *  - S32 int32 (4 bytes per element)
- *  - U8 uint8 (1 byte per element)
+ * The API allows any number of simultaneous TX and RX streams, but many dual-channel
+ * devices are limited to one stream in each direction, using either one or both channels.
+ * This call will return an error if an unsupported combination is requested,
+ * or if a requested channel in this direction is already in use by another stream.
  *
- * Recommended keys to use in the args dictionary:
- *  - "WIRE" - format of the samples between device and host
+ * When multiple channels are added to a stream, they are typically expected to have
+ * the same sample rate. See SoapySDRDevice_setSampleRate().
  *
  * \param device a pointer to a device instance
- * \param [out] stream the opaque pointer to a stream handle
- * \param direction the channel direction RX or TX
- * \param format the desired buffer format in read/writeStream()
- * \param channels a list of channels for empty for automatic
+ * \param [out] stream the opaque pointer to a stream handle.
+ * \parblock
+ *
+ * The returned stream is not required to have internal locking, and may not be used
+ * concurrently from multiple threads.
+ * \endparblock
+ *
+ * \param direction the channel direction (`SOAPY_SDR_RX` or `SOAPY_SDR_TX`)
+ * \param format A string representing the desired buffer format in read/writeStream()
+ * \parblock
+ *
+ * The first character selects the number type:
+ *   - "C" means complex
+ *   - "F" means floating point
+ *   - "S" means signed integer
+ *   - "U" means unsigned integer
+ *
+ * The type character is followed by the number of bits per number (complex is 2x this size per sample)
+ *
+ *  Example format strings:
+ *   - "CF32" -  complex float32 (8 bytes per element)
+ *   - "CS16" -  complex int16 (4 bytes per element)
+ *   - "CS12" -  complex int12 (3 bytes per element)
+ *   - "CS4" -  complex int4 (1 byte per element)
+ *   - "S32" -  int32 (4 bytes per element)
+ *   - "U8" -  uint8 (1 byte per element)
+ *
+ * \endparblock
+ * \param channels a list of channels or empty for automatic
  * \param numChans the number of elements in the channels array
  * \param args stream args or empty for defaults
+ * \parblock
+ *
+ *   Recommended keys to use in the args dictionary:
+ *    - "WIRE" - format of the samples between device and host
+ * \endparblock
  * \return 0 for success or error code on failure
  */
 SOAPY_SDR_API int SoapySDRDevice_setupStream(SoapySDRDevice *device,
@@ -256,8 +276,9 @@ SOAPY_SDR_API int SoapySDRDevice_setupStream(SoapySDRDevice *device,
  * Close an open stream created by setupStream
  * \param device a pointer to a device instance
  * \param stream the opaque pointer to a stream handle
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_closeStream(SoapySDRDevice *device, SoapySDRStream *stream);
+SOAPY_SDR_API int SoapySDRDevice_closeStream(SoapySDRDevice *device, SoapySDRStream *stream);
 
 /*!
  * Get the stream's maximum transmission unit (MTU) in number of elements.
@@ -612,8 +633,9 @@ SOAPY_SDR_API int SoapySDRDevice_setDCOffset(SoapySDRDevice *device, const int d
  * \param channel an available channel on the device
  * \param [out] offsetI the relative correction (1.0 max)
  * \param [out] offsetQ the relative correction (1.0 max)
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_getDCOffset(const SoapySDRDevice *device, const int direction, const size_t channel, double *offsetI, double *offsetQ);
+SOAPY_SDR_API int SoapySDRDevice_getDCOffset(const SoapySDRDevice *device, const int direction, const size_t channel, double *offsetI, double *offsetQ);
 
 /*!
  * Does the device support frontend IQ balance correction?
@@ -642,8 +664,37 @@ SOAPY_SDR_API int SoapySDRDevice_setIQBalance(SoapySDRDevice *device, const int
  * \param channel an available channel on the device
  * \param [out] balanceI the relative correction (1.0 max)
  * \param [out] balanceQ the relative correction (1.0 max)
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_getIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel, double *balanceI, double *balanceQ);
+SOAPY_SDR_API int SoapySDRDevice_getIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel, double *balanceI, double *balanceQ);
+
+/*!
+ * Does the device support frontend frequency correction?
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return true if frequency corrections are supported
+ */
+SOAPY_SDR_API bool SoapySDRDevice_hasFrequencyCorrection(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Fine tune the frontend frequency correction.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param value the correction in PPM
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setFrequencyCorrection(SoapySDRDevice *device, const int direction, const size_t channel, const double value);
+
+/*!
+ * Get the frontend frequency correction value.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return the correction value in PPM
+ */
+SOAPY_SDR_API double SoapySDRDevice_getFrequencyCorrection(const SoapySDRDevice *device, const int direction, const size_t channel);
 
 /*******************************************************************
  * Gain API
@@ -891,6 +942,7 @@ SOAPY_SDR_API double SoapySDRDevice_getSampleRate(const SoapySDRDevice *device,
 
 /*!
  * Get the range of possible baseband sample rates.
+ * \deprecated replaced by getSampleRateRange()
  * \param device a pointer to a device instance
  * \param direction the channel direction RX or TX
  * \param channel an available channel on the device
@@ -899,6 +951,16 @@ SOAPY_SDR_API double SoapySDRDevice_getSampleRate(const SoapySDRDevice *device,
  */
 SOAPY_SDR_API double *SoapySDRDevice_listSampleRates(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
 
+/*!
+ * Get the range of possible baseband sample rates.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of sample rates
+ * \return a list of sample rate ranges in samples per second
+ */
+SOAPY_SDR_API SoapySDRRange *SoapySDRDevice_getSampleRateRange(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
 /*******************************************************************
  * Bandwidth API
  ******************************************************************/
@@ -1043,8 +1105,9 @@ SOAPY_SDR_API long long SoapySDRDevice_getHardwareTime(const SoapySDRDevice *dev
  * \param device a pointer to a device instance
  * \param timeNs time in nanoseconds
  * \param what optional argument
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_setHardwareTime(SoapySDRDevice *device, const long long timeNs, const char *what);
+SOAPY_SDR_API int SoapySDRDevice_setHardwareTime(SoapySDRDevice *device, const long long timeNs, const char *what);
 
 /*!
  * Set the time of subsequent configuration calls.
@@ -1054,8 +1117,9 @@ SOAPY_SDR_API void SoapySDRDevice_setHardwareTime(SoapySDRDevice *device, const
  * \param device a pointer to a device instance
  * \param timeNs time in nanoseconds
  * \param what optional argument
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_setCommandTime(SoapySDRDevice *device, const long long timeNs, const char *what);
+SOAPY_SDR_API int SoapySDRDevice_setCommandTime(SoapySDRDevice *device, const long long timeNs, const char *what);
 
 /*******************************************************************
  * Sensor API
@@ -1143,8 +1207,9 @@ SOAPY_SDR_API char **SoapySDRDevice_listRegisterInterfaces(const SoapySDRDevice
  * \param name the name of a available register interface
  * \param addr the register address
  * \param value the register value
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeNamedRegister(SoapySDRDevice *device, const char *name, const unsigned addr, const unsigned value);
+SOAPY_SDR_API int SoapySDRDevice_writeRegister(SoapySDRDevice *device, const char *name, const unsigned addr, const unsigned value);
 
 /*!
  * Read a register on the device given the interface name.
@@ -1153,27 +1218,32 @@ SOAPY_SDR_API void SoapySDRDevice_writeNamedRegister(SoapySDRDevice *device, con
  * \param addr the register address
  * \return the register value
  */
-SOAPY_SDR_API unsigned SoapySDRDevice_readNamedRegister(const SoapySDRDevice *device, const char *name, const unsigned addr);
+SOAPY_SDR_API unsigned SoapySDRDevice_readRegister(const SoapySDRDevice *device, const char *name, const unsigned addr);
 
 /*!
- * Write a register on the device.
- * This can represent a register on a soft CPU, FPGA, IC;
+ * Write a memory block on the device given the interface name.
+ * This can represent a memory block on a soft CPU, FPGA, IC;
  * the interpretation is up the implementation to decide.
- * \deprecated replaced by writeRegister(name)
  * \param device a pointer to a device instance
- * \param addr the register address
- * \param value the register value
+ * \param name the name of a available memory block interface
+ * \param addr the memory block start address
+ * \param value the memory block content
+ * \param length the number of words in the block
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeRegister(SoapySDRDevice *device, const unsigned addr, const unsigned value);
+SOAPY_SDR_API int SoapySDRDevice_writeRegisters(SoapySDRDevice *device, const char *name, const unsigned addr, const unsigned *value, const size_t length);
 
 /*!
- * Read a register on the device.
- * \deprecated replaced by readRegister(name)
+ * Read a memory block on the device given the interface name.
+ * Pass the number of words to be read in via length;
+ * length will be set to the number of actual words read.
  * \param device a pointer to a device instance
- * \param addr the register address
- * \return the register value
+ * \param name the name of a available memory block interface
+ * \param addr the memory block start address
+ * \param [inout] length number of words to be read from memory block
+ * \return the memory block content
  */
-SOAPY_SDR_API unsigned SoapySDRDevice_readRegister(const SoapySDRDevice *device, const unsigned addr);
+SOAPY_SDR_API unsigned *SoapySDRDevice_readRegisters(const SoapySDRDevice *device, const char *name, const unsigned addr, size_t *length);
 
 /*******************************************************************
  * Settings API
@@ -1193,8 +1263,9 @@ SOAPY_SDR_API SoapySDRArgInfo *SoapySDRDevice_getSettingInfo(const SoapySDRDevic
  * \param device a pointer to a device instance
  * \param key the setting identifier
  * \param value the setting value
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeSetting(SoapySDRDevice *device, const char *key, const char *value);
+SOAPY_SDR_API int SoapySDRDevice_writeSetting(SoapySDRDevice *device, const char *key, const char *value);
 
 /*!
  * Read an arbitrary setting on the device.
@@ -1222,8 +1293,9 @@ SOAPY_SDR_API SoapySDRArgInfo *SoapySDRDevice_getChannelSettingInfo(const SoapyS
  * \param channel an available channel on the device
  * \param key the setting identifier
  * \param value the setting value
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeChannelSetting(SoapySDRDevice *device, const int direction, const size_t channel, const char *key, const char *value);
+SOAPY_SDR_API int SoapySDRDevice_writeChannelSetting(SoapySDRDevice *device, const int direction, const size_t channel, const char *key, const char *value);
 
 /*!
  * Read an arbitrary channel setting on the device.
@@ -1251,8 +1323,9 @@ SOAPY_SDR_API char **SoapySDRDevice_listGPIOBanks(const SoapySDRDevice *device,
  * \param device a pointer to a device instance
  * \param bank the name of an available bank
  * \param value an integer representing GPIO bits
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeGPIO(SoapySDRDevice *device, const char *bank, const unsigned value);
+SOAPY_SDR_API int SoapySDRDevice_writeGPIO(SoapySDRDevice *device, const char *bank, const unsigned value);
 
 /*!
  * Write the value of a GPIO bank with modification mask.
@@ -1260,8 +1333,9 @@ SOAPY_SDR_API void SoapySDRDevice_writeGPIO(SoapySDRDevice *device, const char *
  * \param bank the name of an available bank
  * \param value an integer representing GPIO bits
  * \param mask a modification mask where 1 = modify
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeGPIOMasked(SoapySDRDevice *device, const char *bank, const unsigned value, const unsigned mask);
+SOAPY_SDR_API int SoapySDRDevice_writeGPIOMasked(SoapySDRDevice *device, const char *bank, const unsigned value, const unsigned mask);
 
 /*!
  * Readback the value of a GPIO bank.
@@ -1277,8 +1351,9 @@ SOAPY_SDR_API unsigned SoapySDRDevice_readGPIO(const SoapySDRDevice *device, con
  * \param device a pointer to a device instance
  * \param bank the name of an available bank
  * \param dir an integer representing data direction bits
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeGPIODir(SoapySDRDevice *device, const char *bank, const unsigned dir);
+SOAPY_SDR_API int SoapySDRDevice_writeGPIODir(SoapySDRDevice *device, const char *bank, const unsigned dir);
 
 /*!
  * Write the data direction of a GPIO bank with modification mask.
@@ -1287,8 +1362,9 @@ SOAPY_SDR_API void SoapySDRDevice_writeGPIODir(SoapySDRDevice *device, const cha
  * \param bank the name of an available bank
  * \param dir an integer representing data direction bits
  * \param mask a modification mask where 1 = modify
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeGPIODirMasked(SoapySDRDevice *device, const char *bank, const unsigned dir, const unsigned mask);
+SOAPY_SDR_API int SoapySDRDevice_writeGPIODirMasked(SoapySDRDevice *device, const char *bank, const unsigned dir, const unsigned mask);
 
 /*!
  * Read the data direction of a GPIO bank.
@@ -1311,19 +1387,22 @@ SOAPY_SDR_API unsigned SoapySDRDevice_readGPIODir(const SoapySDRDevice *device,
  * \param addr the address of the slave
  * \param data an array of bytes write out
  * \param numBytes the number of bytes to write
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeI2C(SoapySDRDevice *device, const int addr, const char *data, const size_t numBytes);
+SOAPY_SDR_API int SoapySDRDevice_writeI2C(SoapySDRDevice *device, const int addr, const char *data, const size_t numBytes);
 
 /*!
  * Read from an available I2C slave.
  * If the device contains multiple I2C masters,
  * the address bits can encode which master.
+ * Pass the number of bytes to be read in via numBytes;
+ * numBytes will be set to the number of actual bytes read.
  * \param device a pointer to a device instance
  * \param addr the address of the slave
- * \param numBytes the number of bytes to read
+ * \param [inout] numBytes the number of bytes to read
  * \return an array of bytes read from the slave
  */
-SOAPY_SDR_API char *SoapySDRDevice_readI2C(SoapySDRDevice *device, const int addr, const size_t numBytes);
+SOAPY_SDR_API char *SoapySDRDevice_readI2C(SoapySDRDevice *device, const int addr, size_t *numBytes);
 
 /*******************************************************************
  * SPI API
@@ -1365,8 +1444,9 @@ SOAPY_SDR_API char **SoapySDRDevice_listUARTs(const SoapySDRDevice *device, size
  * \param device a pointer to a device instance
  * \param which the name of an available UART
  * \param data a null terminated array of bytes
+ * \return 0 for success or error code on failure
  */
-SOAPY_SDR_API void SoapySDRDevice_writeUART(SoapySDRDevice *device, const char *which, const char *data);
+SOAPY_SDR_API int SoapySDRDevice_writeUART(SoapySDRDevice *device, const char *which, const char *data);
 
 /*!
  * Read bytes from a UART until timeout or newline.
diff --git a/include/SoapySDR/Device.hpp b/include/SoapySDR/Device.hpp
index 496a077..16865c9 100644
--- a/include/SoapySDR/Device.hpp
+++ b/include/SoapySDR/Device.hpp
@@ -4,7 +4,7 @@
 /// Interface definition for Soapy SDR devices.
 ///
 /// \copyright
-/// Copyright (c) 2014-2016 Josh Blum
+/// Copyright (c) 2014-2017 Josh Blum
 /// Copyright (c) 2016-2016 Bastille Networks
 /// SPDX-License-Identifier: BSL-1.0
 ///
@@ -75,9 +75,6 @@ public:
     /*!
      * Unmake or release a device object handle.
      *
-     * \note This call is not thread safe. Implementations calling into unmake
-     * from multiple threads should protect this call with a mutex.
-     *
      * \param device a pointer to a device object
      */
     static void unmake(Device *device);
@@ -161,7 +158,7 @@ public:
      * Query a list of the available stream formats.
      * \param direction the channel direction RX or TX
      * \param channel an available channel on the device
-     * \return a list of allowed format strings
+     * \return a list of allowed format strings. See setupStream() for the format syntax.
      */
     virtual std::vector<std::string> getStreamFormats(const int direction, const size_t channel) const;
 
@@ -190,29 +187,48 @@ public:
      * All stream API calls should be usable with the new stream object
      * after setupStream() is complete, regardless of the activity state.
      *
-     * Format string markup guidelines:
-     *  - C means complex
-     *  - F means floating point
-     *  - S means signed integer
-     *  - U means unsigned integer
-     *  - number float/int size in bytes (complex is 2x this size)
+     * The API allows any number of simultaneous TX and RX streams, but many dual-channel
+     * devices are limited to one stream in each direction, using either one or both channels.
+     * This call will throw an exception if an unsupported combination is requested,
+     * or if a requested channel in this direction is already in use by another stream.
      *
-     * Example format strings:
-     *  - CF32 complex float32 (8 bytes per element)
-     *  - CS16 complex int16 (4 bytes per element)
-     *  - CS12 complex int12 (3 bytes per element)
-     *  - CS4 complex int4 (1 byte per element)
-     *  - S32 int32 (4 bytes per element)
-     *  - U8 uint8 (1 byte per element)
+     * When multiple channels are added to a stream, they are typically expected to have
+     * the same sample rate. See setSampleRate().
      *
-     * Recommended keys to use in the args dictionary:
-     *  - "WIRE" - format of the samples between device and host
+     * \param direction the channel direction (`SOAPY_SDR_RX` or `SOAPY_SDR_TX`)
+     * \param format A string representing the desired buffer format in read/writeStream()
+     * \parblock
      *
-     * \param direction the channel direction RX or TX
-     * \param format the desired buffer format in read/writeStream()
-     * \param channels a list of channels for empty for automatic
-     * \param args stream args or empty for defaults
-     * \return an opaque pointer to a stream handle
+     * The first character selects the number type:
+     *   - "C" means complex
+     *   - "F" means floating point
+     *   - "S" means signed integer
+     *   - "U" means unsigned integer
+     *
+     * The type character is followed by the number of bits per number (complex is 2x this size per sample)
+     *
+     *  Example format strings:
+     *   - "CF32" -  complex float32 (8 bytes per element)
+     *   - "CS16" -  complex int16 (4 bytes per element)
+     *   - "CS12" -  complex int12 (3 bytes per element)
+     *   - "CS4" -  complex int4 (1 byte per element)
+     *   - "S32" -  int32 (4 bytes per element)
+     *   - "U8" -  uint8 (1 byte per element)
+     *
+     * \endparblock
+     * \param channels a list of channels or empty for automatic.
+     * \param args stream args or empty for defaults.
+     * \parblock
+     *
+     *   Recommended keys to use in the args dictionary:
+     *    - "WIRE" - format of the samples between device and host
+     * \endparblock
+     * \return an opaque pointer to a stream handle.
+     * \parblock
+     *
+     * The returned stream is not required to have internal locking, and may not be used
+     * concurrently from multiple threads.
+     * \endparblock
      */
     virtual Stream *setupStream(
         const int direction,
@@ -580,6 +596,30 @@ public:
      */
     virtual std::complex<double> getIQBalance(const int direction, const size_t channel) const;
 
+    /*!
+     * Does the device support frontend frequency correction?
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return true if frequency corrections are supported
+     */
+    virtual bool hasFrequencyCorrection(const int direction, const size_t channel) const;
+
+    /*!
+     * Fine tune the frontend frequency correction.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param value the correction in PPM
+     */
+    virtual void setFrequencyCorrection(const int direction, const size_t channel, const double value);
+
+    /*!
+     * Get the frontend frequency correction value.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return the correction value in PPM
+     */
+    virtual double getFrequencyCorrection(const int direction, const size_t channel) const;
+
     /*******************************************************************
      * Gain API
      ******************************************************************/
@@ -795,12 +835,21 @@ public:
 
     /*!
      * Get the range of possible baseband sample rates.
+     * \deprecated replaced by getSampleRateRange()
      * \param direction the channel direction RX or TX
      * \param channel an available channel on the device
      * \return a list of possible rates in samples per second
      */
     virtual std::vector<double> listSampleRates(const int direction, const size_t channel) const;
 
+    /*!
+     * Get the range of possible baseband sample rates.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of sample rate ranges in samples per second
+     */
+    virtual RangeList getSampleRateRange(const int direction, const size_t channel) const;
+
     /*******************************************************************
      * Bandwidth API
      ******************************************************************/
@@ -1037,6 +1086,25 @@ public:
      */
     virtual unsigned readRegister(const unsigned addr) const;
 
+    /*!
+     * Write a memory block on the device given the interface name.
+     * This can represent a memory block on a soft CPU, FPGA, IC;
+     * the interpretation is up the implementation to decide.
+     * \param name the name of a available memory block interface
+     * \param addr the memory block start address
+     * \param value the memory block content
+     */
+    virtual void writeRegisters(const std::string &name, const unsigned addr, const std::vector<unsigned> &value);
+
+    /*!
+     * Read a memory block on the device given the interface name.
+     * \param name the name of a available memory block interface
+     * \param addr the memory block start address
+     * \param length number of words to be read from memory block
+     * \return the memory block content
+     */
+    virtual std::vector<unsigned> readRegisters(const std::string &name, const unsigned addr, const size_t length) const;
+
     /*******************************************************************
      * Settings API
      ******************************************************************/
diff --git a/include/SoapySDR/Modules.h b/include/SoapySDR/Modules.h
index 67d74b6..b064043 100644
--- a/include/SoapySDR/Modules.h
+++ b/include/SoapySDR/Modules.h
@@ -23,6 +23,13 @@ extern "C" {
 SOAPY_SDR_API const char *SoapySDR_getRootPath(void);
 
 /*!
+ * The list of paths automatically searched by loadModules().
+ * \param [out] length the number of elements in the result.
+ * \return a list of automatically searched file paths
+ */
+SOAPY_SDR_API char **SoapySDR_listSearchPaths(size_t *length);
+
+/*!
  * List all modules found in default path.
  * The result is an array of strings owned by the caller.
  * \param [out] length the number of elements in the result.
diff --git a/include/SoapySDR/Modules.hpp b/include/SoapySDR/Modules.hpp
index 8a1e77c..267df77 100644
--- a/include/SoapySDR/Modules.hpp
+++ b/include/SoapySDR/Modules.hpp
@@ -23,6 +23,12 @@ namespace SoapySDR
 SOAPY_SDR_API std::string getRootPath(void);
 
 /*!
+ * The list of paths automatically searched by loadModules().
+ * \return a list of automatically searched file paths
+ */
+SOAPY_SDR_API std::vector<std::string> listSearchPaths(void);
+
+/*!
  * List all modules found in default path.
  * \return a list of file paths to loadable modules
  */
diff --git a/include/SoapySDR/Types.h b/include/SoapySDR/Types.h
index 33caf52..b4a9542 100644
--- a/include/SoapySDR/Types.h
+++ b/include/SoapySDR/Types.h
@@ -4,7 +4,7 @@
 /// Misc data type definitions used in the API.
 ///
 /// \copyright
-/// Copyright (c) 2014-2015 Josh Blum
+/// Copyright (c) 2014-2017 Josh Blum
 /// SPDX-License-Identifier: BSL-1.0
 ///
 
@@ -21,6 +21,7 @@ typedef struct
 {
     double minimum;
     double maximum;
+    double step;
 } SoapySDRRange;
 
 //! Definition for a key/value string map
@@ -31,6 +32,18 @@ typedef struct
     char **vals;
 } SoapySDRKwargs;
 
+/*!
+ * Convert a markup string to a key-value map.
+ * The markup format is: "key0=value0, key1=value1"
+ */
+SOAPY_SDR_API SoapySDRKwargs SoapySDRKwargs_fromString(const char *markup);
+
+/*!
+ * Convert a key-value map to a markup string.
+ * The markup format is: "key0=value0, key1=value1"
+ */
+SOAPY_SDR_API char *SoapySDRKwargs_toString(const SoapySDRKwargs *args);
+
 //! Possible data types for argument info
 typedef enum
 {
diff --git a/include/SoapySDR/Types.hpp b/include/SoapySDR/Types.hpp
index 74ccbf3..f9ad013 100644
--- a/include/SoapySDR/Types.hpp
+++ b/include/SoapySDR/Types.hpp
@@ -4,7 +4,7 @@
 /// Misc data type definitions used in the API.
 ///
 /// \copyright
-/// Copyright (c) 2014-2015 Josh Blum
+/// Copyright (c) 2014-2017 Josh Blum
 /// SPDX-License-Identifier: BSL-1.0
 ///
 
@@ -21,6 +21,18 @@ namespace SoapySDR
 //! Typedef for a dictionary of key-value string arguments
 typedef std::map<std::string, std::string> Kwargs;
 
+/*!
+ * Convert a markup string to a key-value map.
+ * The markup format is: "key0=value0, key1=value1"
+ */
+SOAPY_SDR_API Kwargs KwargsFromString(const std::string &markup);
+
+/*!
+ * Convert a key-value map to a markup string.
+ * The markup format is: "key0=value0, key1=value1"
+ */
+SOAPY_SDR_API std::string KwargsToString(const Kwargs &args);
+
 //! Typedef for a list of key-word dictionaries
 typedef std::vector<Kwargs> KwargsList;
 
@@ -35,7 +47,7 @@ public:
     Range(void);
 
     //! Create a min/max range
-    Range(const double minimum, const double maximum);
+    Range(const double minimum, const double maximum, const double step=0.0);
 
     //! Get the range minimum
     double minimum(void) const;
@@ -43,8 +55,11 @@ public:
     //! Get the range maximum
     double maximum(void) const;
 
+    //! Get the range step size
+    double step(void) const;
+
 private:
-    double _min, _max;
+    double _min, _max, _step;
 };
 
 /*!
@@ -122,3 +137,8 @@ inline double SoapySDR::Range::maximum(void) const
 {
     return _max;
 }
+
+inline double SoapySDR::Range::step(void) const
+{
+    return _step;
+}
diff --git a/include/SoapySDR/Version.h b/include/SoapySDR/Version.h
index 7af432b..11280aa 100644
--- a/include/SoapySDR/Version.h
+++ b/include/SoapySDR/Version.h
@@ -4,7 +4,7 @@
 /// Utility functions to query version information.
 ///
 /// \copyright
-/// Copyright (c) 2014-2016 Josh Blum
+/// Copyright (c) 2014-2017 Josh Blum
 /// Copyright (c) 2016-2016 Bastille Networks
 /// SPDX-License-Identifier: BSL-1.0
 ///
@@ -26,16 +26,16 @@
  * #endif
  * \endcode
  */
-#define SOAPY_SDR_API_VERSION 0x00050002
+#define SOAPY_SDR_API_VERSION 0x00060000
 
 /*!
  * ABI Version Information - incremented when the ABI is changed.
- * The ABI version format is <b>major.minor-bump</b>. The <i>major.minor</i>
- * comes from the in-progress library version when the change was made,
- * and <i>bump</i> signifies a change to the ABI during library development.
+ * The ABI version format is <b>version[-extra]</b>.
+ * The <i>version</i> comes from the associated library major.minor version.
+ * And <i>extra</i> is empty for releases but set on development branches.
  * The ABI should remain constant across patch releases of the library.
  */
-#define SOAPY_SDR_ABI_VERSION "0.5-2"
+#define SOAPY_SDR_ABI_VERSION "0.6"
 
 /*!
  * Compatibility define for GPIO access API with masks
@@ -122,6 +122,26 @@
  */
 #define SOAPY_SDR_API_HAS_NAMED_REGISTER_API
 
+/*!
+ * Compatibility define for named memory block interface API
+ */
+#define SOAPY_SDR_API_HAS_NAMED_REGISTERS_API
+
+/*!
+ * Compatibility define for step field in range type
+ */
+#define SOAPY_SDR_API_HAS_RANGE_TYPE_STEP
+
+/*!
+ * Compatibility define for get sample rate range API
+ */
+#define SOAPY_SDR_API_HAS_GET_SAMPLE_RATE_RANGE
+
+/*!
+ * Compatibility define for frequency correction API
+ */
+#define SOAPY_SDR_API_HAS_FREQUENCY_CORRECTION_API
+
 #ifdef __cplusplus
 extern "C" {
 #endif
diff --git a/lib/Device.cpp b/lib/Device.cpp
index f2490da..dc88253 100644
--- a/lib/Device.cpp
+++ b/lib/Device.cpp
@@ -1,11 +1,11 @@
-// Copyright (c) 2014-2016 Josh Blum
+// Copyright (c) 2014-2017 Josh Blum
 // Copyright (c) 2016-2016 Bastille Networks
 // SPDX-License-Identifier: BSL-1.0
 
 #include <SoapySDR/Device.hpp>
 #include <SoapySDR/Formats.hpp>
 #include <cstdlib>
-#include <algorithm> //min/max
+#include <algorithm> //min/max/find
 
 SoapySDR::Device::~Device(void)
 {
@@ -218,6 +218,34 @@ std::complex<double> SoapySDR::Device::getIQBalance(const int, const size_t) con
     return std::complex<double>();
 }
 
+bool SoapySDR::Device::hasFrequencyCorrection(const int direction, const size_t channel) const
+{
+    //backwards compatibility with "CORR" string arg
+    const auto components = this->listFrequencies(direction, channel);
+    return (std::find(components.begin(), components.end(), "CORR") != components.end());
+}
+
+void SoapySDR::Device::setFrequencyCorrection(const int direction, const size_t channel, const double value)
+{
+    //backwards compatibility with "CORR" string arg
+    const auto components = this->listFrequencies(direction, channel);
+    if (std::find(components.begin(), components.end(), "CORR") != components.end())
+    {
+        this->setFrequency(direction, channel, "CORR", value);
+    }
+}
+
+double SoapySDR::Device::getFrequencyCorrection(const int direction, const size_t channel) const
+{
+    //backwards compatibility with "CORR" string arg
+    const auto components = this->listFrequencies(direction, channel);
+    if (std::find(components.begin(), components.end(), "CORR") != components.end())
+    {
+        return this->getFrequency(direction, channel, "CORR");
+    }
+    return 0.0;
+}
+
 /*******************************************************************
  * Gain API
  ******************************************************************/
@@ -480,6 +508,17 @@ std::vector<double> SoapySDR::Device::listSampleRates(const int, const size_t) c
     return std::vector<double>();
 }
 
+SoapySDR::RangeList SoapySDR::Device::getSampleRateRange(const int direction, const size_t channel) const
+{
+    SoapySDR::RangeList ranges;
+    //call into the older deprecated listSampleRates() call
+    for (auto &bw : this->listSampleRates(direction, channel))
+    {
+        ranges.push_back(SoapySDR::Range(bw, bw));
+    }
+    return ranges;
+}
+
 /*******************************************************************
  * Bandwidth API
  ******************************************************************/
@@ -642,6 +681,16 @@ unsigned SoapySDR::Device::readRegister(const unsigned) const
     return 0;
 }
 
+void SoapySDR::Device::writeRegisters(const std::string &, const unsigned, const std::vector<unsigned> &)
+{
+    return;
+}
+
+std::vector<unsigned> SoapySDR::Device::readRegisters(const std::string &, const unsigned, size_t length) const
+{
+    return std::vector<unsigned>(length, 0);
+}
+
 /*******************************************************************
  * Settings API
  ******************************************************************/
diff --git a/lib/DeviceC.cpp b/lib/DeviceC.cpp
index 900ce78..1fe7707 100644
--- a/lib/DeviceC.cpp
+++ b/lib/DeviceC.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2016 Josh Blum
+// Copyright (c) 2014-2017 Josh Blum
 // Copyright (c) 2016-2016 Bastille Networks
 // SPDX-License-Identifier: BSL-1.0
 
@@ -51,7 +51,7 @@ const char *SoapySDRDevice_lastError(void)
 
 static const bool SoapySDRBoolErr = bool(-1);
 
-static const SoapySDRRange SoapySDRRangeNAN = {NAN, NAN};
+static const SoapySDRRange SoapySDRRangeNAN = {NAN, NAN, 0.0};
 
 static SoapySDRArgInfo SoapySDRArgInfoNull(void)
 {
@@ -164,11 +164,11 @@ int SoapySDRDevice_setupStream(SoapySDRDevice *device, SoapySDRStream **stream,
     //__SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
-void SoapySDRDevice_closeStream(SoapySDRDevice *device, SoapySDRStream *stream)
+int SoapySDRDevice_closeStream(SoapySDRDevice *device, SoapySDRStream *stream)
 {
     __SOAPY_SDR_C_TRY
-    return device->closeStream(reinterpret_cast<SoapySDR::Stream *>(stream));
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->closeStream(reinterpret_cast<SoapySDR::Stream *>(stream));
+    __SOAPY_SDR_C_CATCH
 }
 
 size_t SoapySDRDevice_getStreamMTU(const SoapySDRDevice *device, SoapySDRStream *stream)
@@ -345,13 +345,13 @@ int SoapySDRDevice_setDCOffset(SoapySDRDevice *device, const int direction, cons
     __SOAPY_SDR_C_CATCH
 }
 
-void SoapySDRDevice_getDCOffset(const SoapySDRDevice *device, const int direction, const size_t channel, double *offsetI, double *offsetQ)
+int SoapySDRDevice_getDCOffset(const SoapySDRDevice *device, const int direction, const size_t channel, double *offsetI, double *offsetQ)
 {
     __SOAPY_SDR_C_TRY
     std::complex<double> ret = device->getDCOffset(direction, channel);
     *offsetI = ret.real();
     *offsetQ = ret.imag();
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    __SOAPY_SDR_C_CATCH
 }
 
 bool SoapySDRDevice_hasIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel)
@@ -368,13 +368,34 @@ int SoapySDRDevice_setIQBalance(SoapySDRDevice *device, const int direction, con
     __SOAPY_SDR_C_CATCH
 }
 
-void SoapySDRDevice_getIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel, double *balanceI, double *balanceQ)
+int SoapySDRDevice_getIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel, double *balanceI, double *balanceQ)
 {
     __SOAPY_SDR_C_TRY
     std::complex<double> ret = device->getIQBalance(direction, channel);
     *balanceI = ret.real();
     *balanceQ = ret.imag();
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    __SOAPY_SDR_C_CATCH
+}
+
+bool SoapySDRDevice_hasFrequencyCorrection(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    __SOAPY_SDR_C_TRY
+    return device->hasFrequencyCorrection(direction, channel);
+    __SOAPY_SDR_C_CATCH_RET(SoapySDRBoolErr);
+}
+
+int SoapySDRDevice_setFrequencyCorrection(SoapySDRDevice *device, const int direction, const size_t channel, const double value)
+{
+    __SOAPY_SDR_C_TRY
+    device->setFrequencyCorrection(direction, channel, value);
+    __SOAPY_SDR_C_CATCH
+}
+
+double SoapySDRDevice_getFrequencyCorrection(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    __SOAPY_SDR_C_TRY
+    return device->getFrequencyCorrection(direction, channel);
+    __SOAPY_SDR_C_CATCH_RET(NAN);
 }
 
 /*******************************************************************
@@ -539,6 +560,14 @@ double *SoapySDRDevice_listSampleRates(const SoapySDRDevice *device, const int d
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
+SoapySDRRange *SoapySDRDevice_getSampleRateRange(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    *length = 0;
+    __SOAPY_SDR_C_TRY
+    return toRangeList(device->getSampleRateRange(direction, channel), length);
+    __SOAPY_SDR_C_CATCH_RET(nullptr);
+}
+
 /*******************************************************************
  * Bandwidth API
  ******************************************************************/
@@ -659,18 +688,18 @@ long long SoapySDRDevice_getHardwareTime(const SoapySDRDevice *device, const cha
     __SOAPY_SDR_C_CATCH
 }
 
-void SoapySDRDevice_setHardwareTime(SoapySDRDevice *device, const long long timeNs, const char *what)
+int SoapySDRDevice_setHardwareTime(SoapySDRDevice *device, const long long timeNs, const char *what)
 {
     __SOAPY_SDR_C_TRY
     device->setHardwareTime(timeNs, what);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    __SOAPY_SDR_C_CATCH
 }
 
-void SoapySDRDevice_setCommandTime(SoapySDRDevice *device, const long long timeNs, const char *what)
+int SoapySDRDevice_setCommandTime(SoapySDRDevice *device, const long long timeNs, const char *what)
 {
     __SOAPY_SDR_C_TRY
     device->setCommandTime(timeNs, what);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    __SOAPY_SDR_C_CATCH
 }
 
 /*******************************************************************
@@ -731,32 +760,35 @@ char **SoapySDRDevice_listRegisterInterfaces(const SoapySDRDevice *device, size_
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
-void SoapySDRDevice_writeNamedRegister(SoapySDRDevice *device, const char *name, const unsigned addr, const unsigned value)
+int SoapySDRDevice_writeRegister(SoapySDRDevice *device, const char *name, const unsigned addr, const unsigned value)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeRegister(name, addr, value);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeRegister(name, addr, value);
+    __SOAPY_SDR_C_CATCH
 }
 
-unsigned SoapySDRDevice_readNamedRegister(const SoapySDRDevice *device, const char *name, const unsigned addr)
+unsigned SoapySDRDevice_readRegister(const SoapySDRDevice *device, const char *name, const unsigned addr)
 {
     __SOAPY_SDR_C_TRY
     return device->readRegister(name, addr);
     __SOAPY_SDR_C_CATCH
 }
 
-void SoapySDRDevice_writeRegister(SoapySDRDevice *device, const unsigned addr, const unsigned value)
+int SoapySDRDevice_writeRegisters(SoapySDRDevice *device, const char *name, const unsigned addr, const unsigned *value, const size_t length)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeRegister(addr, value);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeRegisters(name, addr, toNumericVector(value, length));
+    __SOAPY_SDR_C_CATCH
 }
 
-unsigned SoapySDRDevice_readRegister(const SoapySDRDevice *device, const unsigned addr)
+unsigned *SoapySDRDevice_readRegisters(const SoapySDRDevice *device, const char *name, const unsigned addr, size_t *length)
 {
+    const size_t inputLen = *length;
+    *length = 0; //clear in case of error
+
     __SOAPY_SDR_C_TRY
-    return device->readRegister(addr);
-    __SOAPY_SDR_C_CATCH
+    return toNumericList(device->readRegisters(name, addr, inputLen), length);
+    __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
 /*******************************************************************
@@ -770,11 +802,11 @@ SoapySDRArgInfo *SoapySDRDevice_getSettingInfo(const SoapySDRDevice *device, siz
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
-void SoapySDRDevice_writeSetting(SoapySDRDevice *device, const char *key, const char *value)
+int SoapySDRDevice_writeSetting(SoapySDRDevice *device, const char *key, const char *value)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeSetting(key, value);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeSetting(key, value);
+    __SOAPY_SDR_C_CATCH
 }
 
 char *SoapySDRDevice_readSetting(const SoapySDRDevice *device, const char *key)
@@ -792,11 +824,11 @@ SoapySDRArgInfo *SoapySDRDevice_getChannelSettingInfo(const SoapySDRDevice *devi
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
-void SoapySDRDevice_writeChannelSetting(SoapySDRDevice *device, const int direction, const size_t channel, const char *key, const char *value)
+int SoapySDRDevice_writeChannelSetting(SoapySDRDevice *device, const int direction, const size_t channel, const char *key, const char *value)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeSetting(direction, channel, key, value);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeSetting(direction, channel, key, value);
+    __SOAPY_SDR_C_CATCH
 }
 
 char *SoapySDRDevice_readChannelSetting(const SoapySDRDevice *device, const int direction, const size_t channel, const char *key)
@@ -817,18 +849,18 @@ char **SoapySDRDevice_listGPIOBanks(const SoapySDRDevice *device, size_t *length
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
-void SoapySDRDevice_writeGPIO(SoapySDRDevice *device, const char *bank, const unsigned value)
+int SoapySDRDevice_writeGPIO(SoapySDRDevice *device, const char *bank, const unsigned value)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeGPIO(bank, value);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeGPIO(bank, value);
+    __SOAPY_SDR_C_CATCH
 }
 
-void SoapySDRDevice_writeGPIOMasked(SoapySDRDevice *device, const char *bank, const unsigned value, const unsigned mask)
+int SoapySDRDevice_writeGPIOMasked(SoapySDRDevice *device, const char *bank, const unsigned value, const unsigned mask)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeGPIO(bank, value, mask);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeGPIO(bank, value, mask);
+    __SOAPY_SDR_C_CATCH
 }
 
 unsigned SoapySDRDevice_readGPIO(const SoapySDRDevice *device, const char *bank)
@@ -838,18 +870,18 @@ unsigned SoapySDRDevice_readGPIO(const SoapySDRDevice *device, const char *bank)
     __SOAPY_SDR_C_CATCH
 }
 
-void SoapySDRDevice_writeGPIODir(SoapySDRDevice *device, const char *bank, const unsigned dir)
+int SoapySDRDevice_writeGPIODir(SoapySDRDevice *device, const char *bank, const unsigned dir)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeGPIODir(bank, dir);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeGPIODir(bank, dir);
+    __SOAPY_SDR_C_CATCH
 }
 
-void SoapySDRDevice_writeGPIODirMasked(SoapySDRDevice *device, const char *bank, const unsigned dir, const unsigned mask)
+int SoapySDRDevice_writeGPIODirMasked(SoapySDRDevice *device, const char *bank, const unsigned dir, const unsigned mask)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeGPIODir(bank, dir, mask);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeGPIODir(bank, dir, mask);
+    __SOAPY_SDR_C_CATCH
 }
 
 unsigned SoapySDRDevice_readGPIODir(const SoapySDRDevice *device, const char *bank)
@@ -862,19 +894,23 @@ unsigned SoapySDRDevice_readGPIODir(const SoapySDRDevice *device, const char *ba
 /*******************************************************************
  * I2C API
  ******************************************************************/
-void SoapySDRDevice_writeI2C(SoapySDRDevice *device, const int addr, const char *data, const size_t numBytes)
+int SoapySDRDevice_writeI2C(SoapySDRDevice *device, const int addr, const char *data, const size_t numBytes)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeI2C(addr, std::string(data, numBytes));
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeI2C(addr, std::string(data, numBytes));
+    __SOAPY_SDR_C_CATCH
 }
 
-char *SoapySDRDevice_readI2C(SoapySDRDevice *device, const int addr, const size_t numBytes)
+char *SoapySDRDevice_readI2C(SoapySDRDevice *device, const int addr, size_t *numBytes)
 {
+    const size_t inputNumBytes = *numBytes;
+    *numBytes = 0; //clear in case of error
+
     __SOAPY_SDR_C_TRY
-    const std::string bytes = device->readI2C(addr, numBytes).c_str();
+    const std::string bytes = device->readI2C(addr, inputNumBytes).c_str();
     char *buff = (char *)std::malloc(bytes.size());
     std::copy(bytes.begin(), bytes.end(), buff);
+    *numBytes = bytes.size();
     return buff;
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
@@ -900,11 +936,11 @@ char **SoapySDRDevice_listUARTs(const SoapySDRDevice *device, size_t *length)
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
-void SoapySDRDevice_writeUART(SoapySDRDevice *device, const char *which, const char *data)
+int SoapySDRDevice_writeUART(SoapySDRDevice *device, const char *which, const char *data)
 {
     __SOAPY_SDR_C_TRY
-    return device->writeUART(which, data);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    device->writeUART(which, data);
+    __SOAPY_SDR_C_CATCH
 }
 
 char *SoapySDRDevice_readUART(const SoapySDRDevice *device, const char *which, const long timeoutUs)
diff --git a/lib/Factory.cpp b/lib/Factory.cpp
index 493c096..a5b4ee8 100644
--- a/lib/Factory.cpp
+++ b/lib/Factory.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2016 Josh Blum
+// Copyright (c) 2014-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include <SoapySDR/Device.hpp>
@@ -6,7 +6,6 @@
 #include <SoapySDR/Modules.hpp>
 #include <stdexcept>
 #include <iostream>
-#include <cctype>
 #include <mutex>
 
 static std::recursive_mutex &getFactoryMutex(void)
@@ -61,103 +60,72 @@ SoapySDR::KwargsList SoapySDR::Device::enumerate(const Kwargs &args)
     return results;
 }
 
-static std::string trim(const std::string &s)
+SoapySDR::KwargsList SoapySDR::Device::enumerate(const std::string &args)
 {
-    std::string out = s;
-    while (not out.empty() and std::isspace(out[0])) out = out.substr(1);
-    while (not out.empty() and std::isspace(out[out.size()-1])) out = out.substr(0, out.size()-1);
-    return out;
+    return enumerate(KwargsFromString(args));
 }
 
-static SoapySDR::Kwargs argsStrToKwargs(const std::string &args)
+static SoapySDR::Device* getDeviceFromTable(const SoapySDR::Kwargs &args)
 {
-    SoapySDR::Kwargs kwargs;
-
-    bool inKey = true;
-    std::string key, val;
-    for (size_t i = 0; i < args.size(); i++)
+    if (args.empty()) return nullptr;
+    std::lock_guard<std::recursive_mutex> lock(getFactoryMutex());
+    if (getDeviceTable().count(args) != 0 and getDeviceCounts().count(getDeviceTable().at(args)) != 0)
     {
-        const char ch = args[i];
-        if (inKey)
-        {
-            if (ch == '=') inKey = false;
-            else if (ch == ',') inKey = true;
-            else key += ch;
-        }
-        else
-        {
-            if (ch == ',') inKey = true;
-            else val += ch;
-        }
-        if ((inKey and not val.empty()) or ((i+1) == args.size()))
-        {
-            key = trim(key);
-            val = trim(val);
-            if (not key.empty()) kwargs[key] = val;
-            key = "";
-            val = "";
-        }
+        auto device = getDeviceTable().at(args);
+        getDeviceCounts()[device]++;
+        return device;
     }
-
-    return kwargs;
+    return nullptr;
 }
 
-SoapySDR::KwargsList SoapySDR::Device::enumerate(const std::string &args)
+SoapySDR::Device* SoapySDR::Device::make(const Kwargs &inputArgs)
 {
-    return enumerate(argsStrToKwargs(args));
-}
+    Device *device = nullptr;
 
-SoapySDR::Device* SoapySDR::Device::make(const Kwargs &args_)
-{
-    std::lock_guard<std::recursive_mutex> lock(getFactoryMutex());
+    //the arguments may have already come from enumerate and been used to open a device
+    device = getDeviceFromTable(inputArgs);
+    if (device != nullptr) return device;
 
-    loadModules();
-    Kwargs args = args_;
-    Device *device = nullptr;
+    //otherwise the args must always come from an enumeration result
+    Kwargs discoveredArgs;
+    const auto results = Device::enumerate(inputArgs);
+    if (not results.empty()) discoveredArgs = results.front();
 
     //check the device table for an already allocated device
-    if (getDeviceTable().count(args_) != 0 and getDeviceCounts().count(getDeviceTable().at(args_)) != 0)
-    {
-        device = getDeviceTable().at(args_);
-    }
+    device = getDeviceFromTable(discoveredArgs);
+    if (device != nullptr) return device;
 
-    //otherwise call into one of the factory functions
-    else
+    //load the enumeration args with missing keys from the make argument
+    Kwargs hybridArgs = discoveredArgs;
+    for (const auto &it : inputArgs)
     {
-        //the args must always come from an enumeration result
-        {
-            const auto results = Device::enumerate(args);
-            if (not results.empty()) args = results.front();
-        }
+        if (hybridArgs.count(it.first) == 0) hybridArgs[it.first] = it.second;
+    }
 
-        //load the enumeration args with missing keys from the make argument
-        for (const auto &it : args_)
-        {
-            if (args.count(it.first) == 0) args[it.first] = it.second;
-        }
+    //lock during device construction
+    //make itself can be parallelized, but we need to keep track of in-process factories
+    //so that other calling threads with the same args can wait on the result
+    std::lock_guard<std::recursive_mutex> lock(getFactoryMutex());
 
-        //loop through make functions and call on module match
-        for (const auto &it : Registry::listMakeFunctions())
-        {
-            if (args.count("driver") != 0 and args.at("driver") != it.first) continue;
-            device = it.second(args);
-            break;
-        }
+    //loop through make functions and call on module match
+    for (const auto &it : Registry::listMakeFunctions())
+    {
+        if (hybridArgs.count("driver") != 0 and hybridArgs.at("driver") != it.first) continue;
+        device = it.second(hybridArgs);
+        break;
     }
-
     if (device == nullptr) throw std::runtime_error("SoapySDR::Device::make() no match");
 
     //store into the table
-    getDeviceTable()[args_] = device;
+    getDeviceTable()[discoveredArgs] = device;
     getDeviceCounts()[device]++;
 
     return device;
 }
 
-
 SoapySDR::Device *SoapySDR::Device::make(const std::string &args)
 {
-    return make(argsStrToKwargs(args));
+    return make(KwargsFromString(args));
 }
 
 void SoapySDR::Device::unmake(Device *device)
diff --git a/lib/FactoryC.cpp b/lib/FactoryC.cpp
index 67bce81..47ee8e4 100644
--- a/lib/FactoryC.cpp
+++ b/lib/FactoryC.cpp
@@ -18,7 +18,7 @@ SoapySDRKwargs *SoapySDRDevice_enumerate(const SoapySDRKwargs *args, size_t *len
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
-SOAPY_SDR_API SoapySDRKwargs *SoapySDRDevice_enumerateStrArgs(const char *args, size_t *length)
+SoapySDRKwargs *SoapySDRDevice_enumerateStrArgs(const char *args, size_t *length)
 {
     *length = 0;
     __SOAPY_SDR_C_TRY
@@ -40,11 +40,11 @@ SoapySDRDevice *SoapySDRDevice_makeStrArgs(const char *args)
     __SOAPY_SDR_C_CATCH_RET(nullptr);
 }
 
-void SoapySDRDevice_unmake(SoapySDRDevice *device)
+int SoapySDRDevice_unmake(SoapySDRDevice *device)
 {
     __SOAPY_SDR_C_TRY
     SoapySDR::Device::unmake((SoapySDR::Device *)device);
-    __SOAPY_SDR_C_CATCH_RET(SoapySDRVoidRet);
+    __SOAPY_SDR_C_CATCH
 }
 
 }
diff --git a/lib/LoggerC.cpp b/lib/LoggerC.cpp
index e9137e7..7a08ca0 100644
--- a/lib/LoggerC.cpp
+++ b/lib/LoggerC.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2015 Josh Blum
+// Copyright (c) 2014-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include <SoapySDR/Logger.h>
@@ -25,6 +25,7 @@ static SoapySDRLogLevel getDefaultLogLevel(void)
     checkLogLevelEnvStr(ERROR);
     checkLogLevelEnvStr(WARNING);
     checkLogLevelEnvStr(NOTICE);
+    checkLogLevelEnvStr(INFO);
     checkLogLevelEnvStr(DEBUG);
     checkLogLevelEnvStr(TRACE);
 
diff --git a/lib/Modules.in.cpp b/lib/Modules.in.cpp
index a03f101..fedd425 100644
--- a/lib/Modules.in.cpp
+++ b/lib/Modules.in.cpp
@@ -114,7 +114,7 @@ static std::vector<std::string> searchModulePath(const std::string &path)
     return modulePaths;
 }
 
-std::vector<std::string> SoapySDR::listModules(void)
+std::vector<std::string> SoapySDR::listSearchPaths(void)
 {
     //the default search path
     std::vector<std::string> searchPaths;
@@ -146,11 +146,16 @@ std::vector<std::string> SoapySDR::listModules(void)
         searchPaths.push_back(pluginPath);
     }
 
+    return searchPaths;
+}
+
+std::vector<std::string> SoapySDR::listModules(void)
+{
     //traverse the search paths
     std::vector<std::string> modules;
-    for (size_t i = 0; i < searchPaths.size(); i++)
+    for (const auto &searchPath : SoapySDR::listSearchPaths())
     {
-        const std::vector<std::string> subModules = SoapySDR::listModules(searchPaths.at(i));
+        const std::vector<std::string> subModules = SoapySDR::listModules(searchPath);
         modules.insert(modules.end(), subModules.begin(), subModules.end());
     }
     return modules;
diff --git a/lib/ModulesC.cpp b/lib/ModulesC.cpp
index 1894fd2..1ecf6ab 100644
--- a/lib/ModulesC.cpp
+++ b/lib/ModulesC.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2015 Josh Blum
+// Copyright (c) 2014-2016 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include "TypeHelpers.hpp"
@@ -15,6 +15,11 @@ const char *SoapySDR_getRootPath(void)
     return root.c_str();
 }
 
+char **SoapySDR_listSearchPaths(size_t *length)
+{
+    return toStrArray(SoapySDR::listSearchPaths(), length);
+}
+
 char **SoapySDR_listModules(size_t *length)
 {
     return toStrArray(SoapySDR::listModules(), length);
diff --git a/lib/TypeHelpers.hpp b/lib/TypeHelpers.hpp
index edcec52..9a856cf 100644
--- a/lib/TypeHelpers.hpp
+++ b/lib/TypeHelpers.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2016 Josh Blum
+// Copyright (c) 2014-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #pragma once
@@ -29,6 +29,7 @@ static inline SoapySDRRange toRange(const SoapySDR::Range &range)
     SoapySDRRange out;
     out.minimum = range.minimum();
     out.maximum = range.maximum();
+    out.step = range.step();
     return out;
 }
 
@@ -103,3 +104,19 @@ static inline SoapySDRArgInfo *toArgInfoList(const SoapySDR::ArgInfoList &infos,
     *length = infos.size();
     return out;
 }
+
+static inline std::vector<unsigned> toNumericVector(const unsigned *values, size_t length)
+{
+	std::vector<unsigned> out (length, 0);
+    for (size_t i = 0; i < length; i++) out[i] = values[i];
+    return out;
+}
+
+static inline unsigned *toNumericList(const std::vector<unsigned> &values, size_t *length)
+{
+	unsigned *out = (unsigned *)calloc(values.size(), sizeof(unsigned));
+    for (size_t i = 0; i < values.size(); i++) out[i] = values[i];
+    *length = values.size();
+    return out;
+}
+
diff --git a/lib/Types.cpp b/lib/Types.cpp
index ae87e44..df53dd7 100644
--- a/lib/Types.cpp
+++ b/lib/Types.cpp
@@ -1,7 +1,62 @@
-// Copyright (c) 2014-2015 Josh Blum
+// Copyright (c) 2014-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include <SoapySDR/Types.hpp>
+#include <cctype>
+
+static std::string trim(const std::string &s)
+{
+    std::string out = s;
+    while (not out.empty() and std::isspace(out[0])) out = out.substr(1);
+    while (not out.empty() and std::isspace(out[out.size()-1])) out = out.substr(0, out.size()-1);
+    return out;
+}
+
+SoapySDR::Kwargs SoapySDR::KwargsFromString(const std::string &markup)
+{
+    SoapySDR::Kwargs kwargs;
+
+    bool inKey = true;
+    std::string key, val;
+    for (size_t i = 0; i < markup.size(); i++)
+    {
+        const char ch = markup[i];
+        if (inKey)
+        {
+            if (ch == '=') inKey = false;
+            else if (ch == ',') inKey = true;
+            else key += ch;
+        }
+        else
+        {
+            if (ch == ',') inKey = true;
+            else val += ch;
+        }
+        if ((inKey and (not val.empty() or (ch == ','))) or ((i+1) == markup.size()))
+        {
+            key = trim(key);
+            val = trim(val);
+            if (not key.empty()) kwargs[key] = val;
+            key = "";
+            val = "";
+        }
+    }
+
+    return kwargs;
+}
+
+std::string SoapySDR::KwargsToString(const SoapySDR::Kwargs &args)
+{
+    std::string markup;
+
+    for (const auto &pair : args)
+    {
+        if (not markup.empty()) markup += ", ";
+        markup += pair.first + "=" + pair.second;
+    }
+
+    return markup;
+}
 
 SoapySDR::Range::Range(void):
     _min(0.0),
@@ -10,9 +65,10 @@ SoapySDR::Range::Range(void):
     return;
 }
 
-SoapySDR::Range::Range(const double minimum, const double maximum):
+SoapySDR::Range::Range(const double minimum, const double maximum, const double step):
     _min(minimum),
-    _max(maximum)
+    _max(maximum),
+    _step(step)
 {
     return;
 }
diff --git a/lib/TypesC.cpp b/lib/TypesC.cpp
index d545146..ad25ea3 100644
--- a/lib/TypesC.cpp
+++ b/lib/TypesC.cpp
@@ -1,12 +1,23 @@
-// Copyright (c) 2014-2015 Josh Blum
+// Copyright (c) 2014-2016 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
+#include "TypeHelpers.hpp"
 #include <SoapySDR/Types.h>
 #include <cstdlib>
 #include <cstring>
 
 extern "C" {
 
+SoapySDRKwargs SoapySDRKwargs_fromString(const char *markup)
+{
+    return toKwargs(SoapySDR::KwargsFromString(markup));
+}
+
+char *SoapySDRKwargs_toString(const SoapySDRKwargs *args)
+{
+    return strdup(SoapySDR::KwargsToString(toKwargs(args)).c_str());
+}
+
 void SoapySDRStrings_clear(char ***elems, const size_t length)
 {
     for (size_t i = 0; i < length; i++)
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index cf80b36..c5776c5 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -32,9 +32,9 @@ execute_process(
     COMMAND ${PYTHON_EXECUTABLE} -c
     "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True, prefix=''))"
     OUTPUT_STRIP_TRAILING_WHITESPACE
-    OUTPUT_VARIABLE PYTHON_INSTALL_DIR
+    OUTPUT_VARIABLE PYTHON_INSTALL_DIR_SYSCONF
 )
-set(PYTHON_INSTALL_DIR ${PYTHON_INSTALL_DIR} CACHE STRING "python install prefix")
+set(PYTHON_INSTALL_DIR "${PYTHON_INSTALL_DIR_SYSCONF}" CACHE STRING "python install prefix")
 message(STATUS "PYTHON_INSTALL_DIR: \${prefix}/${PYTHON_INSTALL_DIR}")
 
 ########################################################################
@@ -91,6 +91,25 @@ if(PYTHON_VERSION_STRING AND "${PYTHON_VERSION_STRING}" VERSION_LESS "3.0")
 endif()
 
 ########################################################################
+## set the swig flags - shared with python3 build
+########################################################################
+set(CMAKE_SWIG_FLAGS -c++ -threads)
+
+#check for size_t issue on arm 32-bit platforms
+include(CheckCXXSourceCompiles)
+CHECK_CXX_SOURCE_COMPILES("
+    #include <cstddef>
+    int main() {
+    size_t *x = (unsigned int *)(NULL);
+    return 0; }" SIZE_T_IS_UNSIGNED_INT)
+
+if (SIZE_T_IS_UNSIGNED_INT)
+    list(APPEND CMAKE_SWIG_FLAGS -DSIZE_T_IS_UNSIGNED_INT)
+endif (SIZE_T_IS_UNSIGNED_INT)
+
+set(CMAKE_SWIG_FLAGS ${CMAKE_SWIG_FLAGS} PARENT_SCOPE)
+
+########################################################################
 ## Feature registration
 ########################################################################
 include(FeatureSummary)
@@ -108,7 +127,7 @@ include(UseSWIG)
 include_directories(${SoapySDR_INCLUDE_DIRS})
 include_directories(${PYTHON_INCLUDE_DIRS})
 
-set(CMAKE_SWIG_FLAGS -c++ -threads)
+message(STATUS "CMAKE_SWIG_FLAGS=${CMAKE_SWIG_FLAGS}")
 set_source_files_properties(SoapySDR.i PROPERTIES CPLUSPLUS ON)
 
 SWIG_ADD_MODULE(SoapySDR python SoapySDR.i)
diff --git a/python/SoapySDR.i b/python/SoapySDR.i
index 4702215..ee2affc 100644
--- a/python/SoapySDR.i
+++ b/python/SoapySDR.i
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2016 Josh Blum
+// Copyright (c) 2014-2017 Josh Blum
 // Copyright (c) 2016-2016 Bastille Networks
 // SPDX-License-Identifier: BSL-1.0
 
@@ -13,6 +13,8 @@
 #include <SoapySDR/Device.hpp>
 #include <SoapySDR/Errors.hpp>
 #include <SoapySDR/Formats.hpp>
+#include <SoapySDR/Time.hpp>
+#include <SoapySDR/Logger.hpp>
 %}
 
 ////////////////////////////////////////////////////////////////////////
@@ -43,6 +45,13 @@
 %include <std_map.i>
 %include <SoapySDR/Types.hpp>
 
+//handle arm 32-bit case where size_t and unsigned are the same
+#ifdef SIZE_T_IS_UNSIGNED_INT
+%typedef unsigned int size_t;
+#else
+%template(SoapySDRUnsignedList) std::vector<unsigned>;
+#endif
+
 %template(SoapySDRKwargs) std::map<std::string, std::string>;
 %template(SoapySDRKwargsList) std::vector<SoapySDR::Kwargs>;
 %template(SoapySDRArgInfoList) std::vector<SoapySDR::ArgInfo>;
@@ -68,7 +77,9 @@
     %insert("python")
     %{
         def __str__(self):
-            return "%s, %s"%(self.minimum(), self.maximum())
+            fields = [self.minimum(), self.maximum()]
+            if self.step() != 0.0: fields.append(self.step())
+            return ', '.join(['%g'%f for f in fields])
     %}
 };
 
@@ -105,6 +116,11 @@
 %include <SoapySDR/Version.h>
 %include <SoapySDR/Formats.h>
 
+%ignore SoapySDR_logf;
+%ignore SoapySDR_vlogf;
+%ignore SoapySDR_registerLogHandler;
+%include <SoapySDR/Logger.h>
+
 ////////////////////////////////////////////////////////////////////////
 // Utility functions
 ////////////////////////////////////////////////////////////////////////
@@ -112,6 +128,12 @@
 %include <SoapySDR/Version.hpp>
 %include <SoapySDR/Modules.hpp>
 %include <SoapySDR/Formats.hpp>
+%include <SoapySDR/Time.hpp>
+
+%ignore SoapySDR::logf;
+%ignore SoapySDR::vlogf;
+%ignore SoapySDR::registerLogHandler;
+%include <SoapySDR/Logger.hpp>
 
 ////////////////////////////////////////////////////////////////////////
 // Device object
diff --git a/python/apps/SimpleSiggen.py b/python/apps/SimpleSiggen.py
index 8ea7898..cc3f652 100644
--- a/python/apps/SimpleSiggen.py
+++ b/python/apps/SimpleSiggen.py
@@ -67,7 +67,7 @@ def siggen_app(
         phaseAcc = phaseAccNext
         while phaseAcc > math.pi*2: phaseAcc -= math.pi*2
 
-        sr = sdr.writeStream(txStream, [sampsCh0], sampsCh0.size)
+        sr = sdr.writeStream(txStream, [sampsCh0], sampsCh0.size, timeoutUs=1000000)
         if sr.ret != sampsCh0.size:
             raise Exception("Expected writeStream() to consume all samples! %d"%sr.ret)
         totalSamps += sr.ret
diff --git a/python3/CMakeLists.txt b/python3/CMakeLists.txt
index 554bdc2..9204837 100644
--- a/python3/CMakeLists.txt
+++ b/python3/CMakeLists.txt
@@ -28,9 +28,9 @@ execute_process(
     COMMAND ${PYTHON3_EXECUTABLE} -c
     "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True, prefix=''))"
     OUTPUT_STRIP_TRAILING_WHITESPACE
-    OUTPUT_VARIABLE PYTHON3_INSTALL_DIR
+    OUTPUT_VARIABLE PYTHON3_INSTALL_DIR_SYSCONF
 )
-set(PYTHON3_INSTALL_DIR ${PYTHON3_INSTALL_DIR} CACHE STRING "python3 install prefix")
+set(PYTHON3_INSTALL_DIR "${PYTHON3_INSTALL_DIR_SYSCONF}" CACHE STRING "python3 install prefix")
 message(STATUS "PYTHON3_INSTALL_DIR: \${prefix}/${PYTHON3_INSTALL_DIR}")
 
 ########################################################################
@@ -73,7 +73,7 @@ configure_file(
     ${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.i
 @ONLY)
 
-set(CMAKE_SWIG_FLAGS -c++ -threads)
+message(STATUS "CMAKE_SWIG_FLAGS=${CMAKE_SWIG_FLAGS}")
 set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.i PROPERTIES CPLUSPLUS ON)
 
 SWIG_ADD_MODULE(SoapySDR3 python ${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.i)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 674f300..92555b1 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -19,3 +19,7 @@ add_test(TestTimeConversion TestTimeConversion)
 add_executable(TestFormatParser TestFormatParser.cpp)
 target_link_libraries(TestFormatParser SoapySDR)
 add_test(TestFormatParser TestFormatParser)
+
+add_executable(TestKwargsMarkup TestKwargsMarkup.cpp)
+target_link_libraries(TestKwargsMarkup SoapySDR)
+add_test(TestKwargsMarkup TestKwargsMarkup)
diff --git a/tests/TestKwargsMarkup.cpp b/tests/TestKwargsMarkup.cpp
new file mode 100644
index 0000000..ab68cb0
--- /dev/null
+++ b/tests/TestKwargsMarkup.cpp
@@ -0,0 +1,76 @@
+// Copyright (c) 2016-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Types.hpp>
+#include <cstdlib>
+#include <cstdio>
+
+bool checkArgsInLhs__(const SoapySDR::Kwargs &lhs, const SoapySDR::Kwargs &rhs)
+{
+    const auto strLhs = SoapySDR::KwargsToString(lhs);
+    const auto strRhs = SoapySDR::KwargsToString(rhs);
+    for (const auto &pair : lhs)
+    {
+        if (rhs.count(pair.first) == 0)
+        {
+            printf("FAIL: {%s}[%s] not found in {%s}\n",
+                strLhs.c_str(), pair.first.c_str(), strRhs.c_str());
+            return false;
+        }
+        if (pair.second != rhs.at(pair.first))
+        {
+            printf("FAIL: {%s}[%s] != {%s}[%s]\n",
+                strLhs.c_str(), pair.first.c_str(), strRhs.c_str(), pair.first.c_str());
+            return false;
+        }
+    }
+    return true;
+}
+
+int main(void)
+{
+    #define checkArgsEq(lhs, rhs) \
+    { \
+        printf("Test line %d\n", __LINE__); \
+        if (not checkArgsInLhs__(lhs, rhs)) return EXIT_FAILURE; \
+        if (not checkArgsInLhs__(rhs, lhs)) return EXIT_FAILURE; \
+    }
+
+    //string to args - empty string
+    checkArgsEq(SoapySDR::KwargsFromString(""), SoapySDR::Kwargs());
+    checkArgsEq(SoapySDR::KwargsFromString(" "), SoapySDR::Kwargs());
+
+    //single keyword
+    SoapySDR::Kwargs args0;
+    args0["Foo"] = "Bar";
+    checkArgsEq(SoapySDR::KwargsFromString("Foo=Bar"), args0);
+    checkArgsEq(SoapySDR::KwargsFromString(" Foo = Bar "), args0);
+    checkArgsEq(SoapySDR::KwargsFromString(" Foo = Bar, "), args0);
+
+    //two keywords
+    SoapySDR::Kwargs args1;
+    args1["Foo"] = "Bar";
+    args1["Baz"] = "123";
+    checkArgsEq(SoapySDR::KwargsFromString("Foo=Bar, Baz=123"), args1);
+    checkArgsEq(SoapySDR::KwargsFromString("Baz=123,Foo = Bar "), args1);
+    checkArgsEq(SoapySDR::KwargsFromString("Baz=123,Foo = Bar , "), args1);
+
+    //empty value
+    SoapySDR::Kwargs args2;
+    args2["Foo"] = "Bar";
+    args2["Baz"] = "";
+    checkArgsEq(SoapySDR::KwargsFromString("Foo=Bar, Baz="), args2);
+    checkArgsEq(SoapySDR::KwargsFromString("Baz=,Foo = Bar "), args2);
+    checkArgsEq(SoapySDR::KwargsFromString("Baz= ,Foo = Bar , "), args2);
+    checkArgsEq(SoapySDR::KwargsFromString("Baz ,Foo = Bar"), args2);
+    checkArgsEq(SoapySDR::KwargsFromString("Baz,Foo = Bar"), args2);
+
+    //loopback arg to markup to arg
+    checkArgsEq(SoapySDR::KwargsFromString(SoapySDR::KwargsToString(SoapySDR::Kwargs())), SoapySDR::Kwargs());
+    checkArgsEq(SoapySDR::KwargsFromString(SoapySDR::KwargsToString(args0)), args0);
+    checkArgsEq(SoapySDR::KwargsFromString(SoapySDR::KwargsToString(args1)), args1);
+    checkArgsEq(SoapySDR::KwargsFromString(SoapySDR::KwargsToString(args2)), args2);
+
+    printf("DONE!\n");
+    return EXIT_SUCCESS;
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/soapysdr.git



More information about the pkg-hamradio-commits mailing list