[hamradio-commits] [soapyremote] 01/09: New upstream version 0.4.2

Andreas E. Bombe aeb at moszumanska.debian.org
Fri Aug 11 19:14:47 UTC 2017


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

aeb pushed a commit to branch master
in repository soapyremote.

commit be3e1030a579d1bd57678597ffe2c970b5fcd78b
Author: Andreas Bombe <aeb at debian.org>
Date:   Mon Aug 7 17:13:20 2017 -0400

    New upstream version 0.4.2
---
 Changelog.txt                                      |  29 +++
 README.md                                          |   2 +-
 client/ClientStreamData.cpp                        | 118 +++++++++++-
 client/ClientStreamData.hpp                        |   5 +-
 client/LogAcceptor.cpp                             |  14 +-
 client/Registration.cpp                            |  15 +-
 client/Settings.cpp                                | 104 +++++++++-
 client/SoapyClient.hpp                             |  19 +-
 client/Streaming.cpp                               | 122 ++++++++----
 common/CMakeLists.txt                              |   1 +
 common/SoapyRPCPacker.cpp                          |  18 +-
 common/SoapyRPCPacker.hpp                          |   5 +-
 common/SoapyRPCSocket.cpp                          |  83 +++++++-
 common/SoapyRPCSocket.hpp                          |  11 +-
 common/SoapyRPCUnpacker.cpp                        |  51 ++++-
 common/SoapyRPCUnpacker.hpp                        |  11 +-
 common/SoapyRemoteDefs.hpp                         |  15 +-
 common/SoapySocketDefs.in.hpp                      |  11 +-
 common/SoapyStreamEndpoint.cpp                     |  45 ++++-
 common/SoapyStreamEndpoint.hpp                     |   4 +-
 debian/changelog                                   |  20 +-
 debian/control                                     |   6 +-
 debian/copyright                                   |   2 +-
 debian/soapysdr-server.install                     |   3 +-
 debian/soapysdr-server.postrm                      |  13 ++
 debian/soapysdr-server.prerm                       |   3 +-
 ...e.install => soapysdr0.6-module-remote.install} |   0
 server/ClientHandler.cpp                           | 209 +++++++++++++++++----
 server/ServerStreamData.cpp                        |  20 +-
 server/ServerStreamData.hpp                        |   7 +-
 system/SoapySDRServer.service.in                   |   2 +
 31 files changed, 836 insertions(+), 132 deletions(-)

diff --git a/Changelog.txt b/Changelog.txt
index fdc4051..ef099b7 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,32 @@
+Release 0.4.2 (2017-07-31)
+==========================
+
+- Fixed timeout problem in log acceptor receiver loop
+- Added server check for slow calls and longer timeout
+  - This also resolves timeout errors for lengthy calls
+- Added network-online.target for systemd service script
+
+Release 0.4.1 (2017-06-08)
+==========================
+
+- Added timeout for logger connect and control unpacker
+- Fixed error log formatting in the client log acceptor
+
+Release 0.4.0 (2017-04-29)
+==========================
+
+- Added support for frequency corrections for fine adjustments
+- Added support forgetSampleRateRange() for continuous ranges
+- Added support for CF32 local format with CS12 remote format
+- Added support for CS16 local format with CS12 remote format
+- Added support for CS16 local format with CS8 remote format
+- readStream thread support for SOAPY_SDR_END_ABRUPT flag
+- readStream thread apply second SOAPY_SDR_END_BURST flag
+- Added timeout to socket connect and factory calls
+- Support range with step size (backwards compatible)
+- Added tcp support for streaming via remote:prot=tcp
+- Support for bulk register read/write interface APIs
+
 Release 0.3.2 (2016-12-04)
 ==========================
 
diff --git a/README.md b/README.md
index 6c16ff5..f45107a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Use any Soapy SDR remotely
 
-##Build Status
+## Build Status
 
 - Travis: [![Travis Build Status](https://travis-ci.org/pothosware/SoapyRemote.svg?branch=master)](https://travis-ci.org/pothosware/SoapyRemote)
 
diff --git a/client/ClientStreamData.cpp b/client/ClientStreamData.cpp
index 7f46170..dca1ae7 100644
--- a/client/ClientStreamData.cpp
+++ b/client/ClientStreamData.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include "ClientStreamData.hpp"
@@ -57,6 +57,65 @@ void ClientStreamData::convertRecvBuffs(void * const *buffs, const size_t numEle
     break;
 
     ///////////////////////////
+    case CONVERT_CF32_CS12:
+    ///////////////////////////
+    {
+        const float scale = float(1.0/scaleFactor);
+        for (size_t i = 0; i < recvBuffs.size(); i++)
+        {
+            auto in = (uint8_t *)recvBuffs[i];
+            auto out = (float *)buffs[i];
+            for (size_t j = 0; j < numElems; j++)
+            {
+                uint16_t part0 = uint16_t(*(in++));
+                uint16_t part1 = uint16_t(*(in++));
+                uint16_t part2 = uint16_t(*(in++));
+                int16_t i = int16_t((part1 << 12) | (part0 << 4));
+                int16_t q = int16_t((part2 << 8) | (part1 & 0xf0));
+                *(out++) = float(i)*scale;
+                *(out++) = float(q)*scale;
+            }
+        }
+    }
+    break;
+
+    ///////////////////////////
+    case CONVERT_CS16_CS12:
+    ///////////////////////////
+    {
+        for (size_t i = 0; i < recvBuffs.size(); i++)
+        {
+            auto in = (uint8_t *)recvBuffs[i];
+            auto out = (int16_t *)buffs[i];
+            for (size_t j = 0; j < numElems; j++)
+            {
+                uint16_t part0 = uint16_t(*(in++));
+                uint16_t part1 = uint16_t(*(in++));
+                uint16_t part2 = uint16_t(*(in++));
+                *(out++) = int16_t((part1 << 12) | (part0 << 4));
+                *(out++) = int16_t((part2 << 8) | (part1 & 0xf0));
+            }
+        }
+    }
+    break;
+
+    ///////////////////////////
+    case CONVERT_CS16_CS8:
+    ///////////////////////////
+    {
+        for (size_t i = 0; i < recvBuffs.size(); i++)
+        {
+            auto in = (int8_t *)recvBuffs[i];
+            auto out = (int16_t *)buffs[i];
+            for (size_t j = 0; j < numElems*2; j++)
+            {
+                out[j] = int16_t(in[j]);
+            }
+        }
+    }
+    break;
+
+    ///////////////////////////
     case CONVERT_CF32_CS8:
     ///////////////////////////
     {
@@ -131,6 +190,63 @@ void ClientStreamData::convertSendBuffs(const void * const *buffs, const size_t
     break;
 
     ///////////////////////////
+    case CONVERT_CF32_CS12:
+    ///////////////////////////
+    {
+        const float scale = float(scaleFactor);
+        for (size_t i = 0; i < sendBuffs.size(); i++)
+        {
+            auto in = (float *)buffs[i];
+            auto out = (uint8_t *)sendBuffs[i];
+            for (size_t j = 0; j < numElems; j++)
+            {
+                uint16_t i = uint16_t(*(in++)*scale);
+                uint16_t q = uint16_t(*(in++)*scale);
+                *(out++) = uint8_t(i >> 4);
+                *(out++) = uint8_t((q & 0xf0)|(i >> 12));
+                *(out++) = uint8_t(q >> 8);
+            }
+        }
+    }
+    break;
+
+    ///////////////////////////
+    case CONVERT_CS16_CS12:
+    ///////////////////////////
+    {
+        for (size_t i = 0; i < sendBuffs.size(); i++)
+        {
+            auto in = (int16_t *)buffs[i];
+            auto out = (uint8_t *)sendBuffs[i];
+            for (size_t j = 0; j < numElems; j++)
+            {
+                uint16_t i = uint16_t(*(in++));
+                uint16_t q = uint16_t(*(in++));
+                *(out++) = uint8_t(i >> 4);
+                *(out++) = uint8_t((q & 0xf0)|(i >> 12));
+                *(out++) = uint8_t(q >> 8);
+            }
+        }
+    }
+    break;
+
+    ///////////////////////////
+    case CONVERT_CS16_CS8:
+    ///////////////////////////
+    {
+        for (size_t i = 0; i < sendBuffs.size(); i++)
+        {
+            auto in = (int16_t *)buffs[i];
+            auto out = (int8_t *)sendBuffs[i];
+            for (size_t j = 0; j < numElems*2; j++)
+            {
+                out[j] = int8_t(in[j]);
+            }
+        }
+    }
+    break;
+
+    ///////////////////////////
     case CONVERT_CF32_CS8:
     ///////////////////////////
     {
diff --git a/client/ClientStreamData.hpp b/client/ClientStreamData.hpp
index 84c12e5..78ed190 100644
--- a/client/ClientStreamData.hpp
+++ b/client/ClientStreamData.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #pragma once
@@ -12,6 +12,9 @@ enum ConvertTypes
 {
     CONVERT_MEMCPY,
     CONVERT_CF32_CS16,
+    CONVERT_CF32_CS12,
+    CONVERT_CS16_CS12,
+    CONVERT_CS16_CS8,
     CONVERT_CF32_CS8,
     CONVERT_CF32_CU8,
 };
diff --git a/client/LogAcceptor.cpp b/client/LogAcceptor.cpp
index 9ba2520..be25052 100644
--- a/client/LogAcceptor.cpp
+++ b/client/LogAcceptor.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include "LogAcceptor.hpp"
@@ -47,7 +47,9 @@ struct LogAcceptorThreadData
 void LogAcceptorThreadData::activate(void)
 {
     client = SoapyRPCSocket();
-    int ret = client.connect(url);
+    //specify a timeout on connect because the link may be lost
+    //when the thread attempts to re-establish a connection
+    int ret = client.connect(url, SOAPY_REMOTE_SOCKET_TIMEOUT_US);
     if (ret != 0)
     {
         SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::connect() FAIL: %s", client.lastErrorMsg());
@@ -67,7 +69,7 @@ void LogAcceptorThreadData::activate(void)
     }
     catch (const std::exception &ex)
     {
-        SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::reactivate() ", ex.what());
+        SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::activate() FAIL: %s", ex.what());
         done = true;
     }
 }
@@ -88,7 +90,7 @@ void LogAcceptorThreadData::shutdown(void)
     }
     catch (const std::exception &ex)
     {
-        SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::shutdown() ", ex.what());
+        SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::shutdown() FAIL: %s", ex.what());
     }
 
     //the thread will exit due to the requests above
@@ -106,7 +108,7 @@ void LogAcceptorThreadData::handlerLoop(void)
         //loop while active to relay messages to logger
         while (true)
         {
-            SoapyRPCUnpacker unpackerLogMsg(client);
+            SoapyRPCUnpacker unpackerLogMsg(client, true, -1/*no timeout*/);
             if (unpackerLogMsg.done()) break; //got stop reply
             char logLevel = 0;
             std::string message;
@@ -117,7 +119,7 @@ void LogAcceptorThreadData::handlerLoop(void)
     }
     catch (const std::exception &ex)
     {
-        SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::handlerLoop() ", ex.what());
+        SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::handlerLoop() FAIL: %s", ex.what());
     }
 
     done = true;
diff --git a/client/Registration.cpp b/client/Registration.cpp
index aa0dc5f..5b80619 100644
--- a/client/Registration.cpp
+++ b/client/Registration.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include "SoapyClient.hpp"
@@ -11,6 +11,7 @@
 #include <SoapySDR/Registry.hpp>
 #include <SoapySDR/Logger.hpp>
 #include <thread>
+#include <future>
 
 /***********************************************************************
  * Args translator for nested keywords
@@ -77,11 +78,19 @@ static std::vector<SoapySDR::Kwargs> findRemote(const SoapySDR::Kwargs &args)
         const auto ipVerIt = args.find("remote:ipver");
         if (ipVerIt != args.end()) ipVer = std::stoi(ipVerIt->second);
 
+        //spawn futures to connect to each remote
+        std::vector<std::future<SoapySDR::KwargsList>> futures;
         for (const auto &url : SoapySSDPEndpoint::getInstance()->getServerURLs(ipVer))
         {
             auto argsWithURL = args;
             argsWithURL["remote"] = url;
-            const auto subResult = findRemote(argsWithURL);
+            futures.push_back(std::async(std::launch::async, &findRemote, argsWithURL));
+        }
+
+        //wait on all futures for results
+        for (auto &future : futures)
+        {
+            const auto subResult = future.get();
             result.insert(result.end(), subResult.begin(), subResult.end());
         }
 
@@ -98,7 +107,7 @@ static std::vector<SoapySDR::Kwargs> findRemote(const SoapySDR::Kwargs &args)
     //try to connect to the remote server
     SoapySocketSession sess;
     SoapyRPCSocket s;
-    int ret = s.connect(url.toString());
+    int ret = s.connect(url.toString(), SOAPY_REMOTE_SOCKET_TIMEOUT_US);
     if (ret != 0)
     {
         SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyRemote::find() -- connect(%s) FAIL: %s", url.toString().c_str(), s.lastErrorMsg());
diff --git a/client/Settings.cpp b/client/Settings.cpp
index 3e1fd93..4264495 100644
--- a/client/Settings.cpp
+++ b/client/Settings.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
 
@@ -10,19 +10,16 @@
 #include <SoapySDR/Logger.hpp>
 #include <stdexcept>
 
-//lazy fix for the const call issue -- FIXME
-#define _mutex const_cast<std::mutex &>(_mutex)
-#define _sock const_cast<SoapyRPCSocket &>(_sock)
-
 /*******************************************************************
  * Constructor
  ******************************************************************/
 
 SoapyRemoteDevice::SoapyRemoteDevice(const std::string &url, const SoapySDR::Kwargs &args):
-    _logAcceptor(nullptr)
+    _logAcceptor(nullptr),
+    _defaultStreamProt("udp")
 {
     //try to connect to the remote server
-    int ret = _sock.connect(url);
+    int ret = _sock.connect(url, SOAPY_REMOTE_SOCKET_TIMEOUT_US);
     if (ret != 0)
     {
         throw std::runtime_error("SoapyRemoteDevice("+url+") -- connect FAIL: " + _sock.lastErrorMsg());
@@ -37,6 +34,10 @@ SoapyRemoteDevice::SoapyRemoteDevice(const std::string &url, const SoapySDR::Kwa
     packer & args;
     packer();
     SoapyRPCUnpacker unpacker(_sock);
+
+    //default stream protocol specified in device args
+    const auto protIt = args.find("prot");
+    if (protIt != args.end()) _defaultStreamProt = protIt->second;
 }
 
 SoapyRemoteDevice::~SoapyRemoteDevice(void)
@@ -362,6 +363,49 @@ std::complex<double> SoapyRemoteDevice::getIQBalance(const int direction, const
     return result;
 }
 
+bool SoapyRemoteDevice::hasFrequencyCorrection(const int direction, const size_t channel) const
+{
+    std::lock_guard<std::mutex> lock(_mutex);
+    SoapyRPCPacker packer(_sock);
+    packer & SOAPY_REMOTE_HAS_FREQUENCY_CORRECTION;
+    packer & char(direction);
+    packer & int(channel);
+    packer();
+
+    SoapyRPCUnpacker unpacker(_sock);
+    bool result;
+    unpacker & result;
+    return result;
+}
+
+void SoapyRemoteDevice::setFrequencyCorrection(const int direction, const size_t channel, const double value)
+{
+    std::lock_guard<std::mutex> lock(_mutex);
+    SoapyRPCPacker packer(_sock);
+    packer & SOAPY_REMOTE_SET_FREQUENCY_CORRECTION;
+    packer & char(direction);
+    packer & int(channel);
+    packer & value;
+    packer();
+
+    SoapyRPCUnpacker unpacker(_sock);
+}
+
+double SoapyRemoteDevice::getFrequencyCorrection(const int direction, const size_t channel) const
+{
+    std::lock_guard<std::mutex> lock(_mutex);
+    SoapyRPCPacker packer(_sock);
+    packer & SOAPY_REMOTE_GET_FREQUENCY_CORRECTION;
+    packer & char(direction);
+    packer & int(channel);
+    packer();
+
+    SoapyRPCUnpacker unpacker(_sock);
+    double result;
+    unpacker & result;
+    return result;
+}
+
 /*******************************************************************
  * Gain API
  ******************************************************************/
@@ -685,6 +729,21 @@ std::vector<double> SoapyRemoteDevice::listSampleRates(const int direction, cons
     return result;
 }
 
+SoapySDR::RangeList SoapyRemoteDevice::getSampleRateRange(const int direction, const size_t channel) const
+{
+    std::lock_guard<std::mutex> lock(_mutex);
+    SoapyRPCPacker packer(_sock);
+    packer & SOAPY_REMOTE_GET_SAMPLE_RATE_RANGE;
+    packer & char(direction);
+    packer & int(channel);
+    packer();
+
+    SoapyRPCUnpacker unpacker(_sock);
+    SoapySDR::RangeList result;
+    unpacker & result;
+    return result;
+}
+
 /*******************************************************************
  * Bandwidth API
  ******************************************************************/
@@ -1081,6 +1140,37 @@ unsigned SoapyRemoteDevice::readRegister(const unsigned addr) const
     return unsigned(result);
 }
 
+void SoapyRemoteDevice::writeRegisters(const std::string &name, const unsigned addr, const std::vector<unsigned> &value)
+{
+    std::lock_guard<std::mutex> lock(_mutex);
+    SoapyRPCPacker packer(_sock);
+    std::vector<size_t> val (value.begin(), value.end());
+    packer & SOAPY_REMOTE_WRITE_REGISTERS;
+    packer & name;
+    packer & int(addr);
+    packer & val;
+    packer();
+
+    SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::vector<unsigned> SoapyRemoteDevice::readRegisters(const std::string &name, const unsigned addr, const size_t length) const
+{
+    std::lock_guard<std::mutex> lock(_mutex);
+    SoapyRPCPacker packer(_sock);
+    packer & SOAPY_REMOTE_READ_REGISTERS;
+    packer & name;
+    packer & int(addr);
+    packer & int(length);
+    packer();
+
+    SoapyRPCUnpacker unpacker(_sock);
+    std::vector<size_t> result;
+    unpacker & result;
+    std::vector<unsigned> res (result.begin(), result.end());
+    return res;
+}
+
 /*******************************************************************
  * Settings API
  ******************************************************************/
diff --git a/client/SoapyClient.hpp b/client/SoapyClient.hpp
index 8e5eb62..0f24fe5 100644
--- a/client/SoapyClient.hpp
+++ b/client/SoapyClient.hpp
@@ -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
 
@@ -161,6 +161,12 @@ public:
 
     std::complex<double> getIQBalance(const int direction, const size_t channel) const;
 
+    bool hasFrequencyCorrection(const int direction, const size_t channel) const;
+
+    void setFrequencyCorrection(const int direction, const size_t channel, const double value);
+
+    double getFrequencyCorrection(const int direction, const size_t channel) const;
+
     /*******************************************************************
      * Gain API
      ******************************************************************/
@@ -215,6 +221,8 @@ public:
 
     std::vector<double> listSampleRates(const int direction, const size_t channel) const;
 
+    SoapySDR::RangeList getSampleRateRange(const int direction, const size_t channel) const;
+
     /*******************************************************************
      * Bandwidth API
      ******************************************************************/
@@ -291,6 +299,10 @@ public:
 
     unsigned readRegister(const unsigned addr) const;
 
+    void writeRegisters(const std::string &name, const unsigned addr, const std::vector<unsigned> &value);
+
+    std::vector<unsigned> readRegisters(const std::string &name, const unsigned addr, const size_t length) const;
+
     /*******************************************************************
      * Settings API
      ******************************************************************/
@@ -351,7 +363,8 @@ public:
 
 private:
     SoapySocketSession _sess;
-    SoapyRPCSocket _sock;
+    mutable SoapyRPCSocket _sock;
     SoapyLogAcceptor *_logAcceptor;
-    std::mutex _mutex;
+    mutable std::mutex _mutex;
+    std::string _defaultStreamProt;
 };
diff --git a/client/Streaming.cpp b/client/Streaming.cpp
index 2b35bd1..21ba4a7 100644
--- a/client/Streaming.cpp
+++ b/client/Streaming.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include <SoapySDR/Logger.hpp>
@@ -12,10 +12,6 @@
 #include "SoapyStreamEndpoint.hpp"
 #include <algorithm> //std::min, std::find
 
-//lazy fix for the const call issue -- FIXME
-#define _mutex const_cast<std::mutex &>(_mutex)
-#define _sock const_cast<SoapyRPCSocket &>(_sock)
-
 std::vector<std::string> SoapyRemoteDevice::__getRemoteOnlyStreamFormats(const int direction, const size_t channel) const
 {
     std::lock_guard<std::mutex> lock(_mutex);
@@ -130,19 +126,23 @@ SoapySDR::Stream *SoapyRemoteDevice::setupStream(
     const int direction,
     const std::string &localFormat,
     const std::vector<size_t> &channels_,
-    const SoapySDR::Kwargs &args)
+    const SoapySDR::Kwargs &args_)
 {
     //default to channel 0 when not specified
     //the channels vector cannot be empty
     //its used for stream endpoint allocation
     auto channels = channels_;
     if (channels.empty()) channels.push_back(0);
+    SoapySDR::Kwargs args(args_); //read/write copy
 
     //use the remote device's native stream format and scale factor when the conversion is supported
     double nativeScaleFactor = 0.0;
     auto nativeFormat = this->getNativeStreamFormat(direction, channels.front(), nativeScaleFactor);
     const bool useNative = (localFormat == nativeFormat) or
         (localFormat == SOAPY_SDR_CF32 and nativeFormat == SOAPY_SDR_CS16) or
+        (localFormat == SOAPY_SDR_CF32 and nativeFormat == SOAPY_SDR_CS12) or
+        (localFormat == SOAPY_SDR_CS16 and nativeFormat == SOAPY_SDR_CS12) or
+        (localFormat == SOAPY_SDR_CS16 and nativeFormat == SOAPY_SDR_CS8) or
         (localFormat == SOAPY_SDR_CF32 and nativeFormat == SOAPY_SDR_CS8) or
         (localFormat == SOAPY_SDR_CF32 and nativeFormat == SOAPY_SDR_CU8);
 
@@ -158,13 +158,27 @@ SoapySDR::Stream *SoapyRemoteDevice::setupStream(
     const auto scaleFactorIt = args.find(SOAPY_REMOTE_KWARG_SCALAR);
     if (scaleFactorIt != args.end()) scaleFactor = std::stod(scaleFactorIt->second);
 
-    size_t mtu = SOAPY_REMOTE_DEFAULT_ENDPOINT_MTU;
+    //determine reliable stream mode with tcp or datagram mode
+    std::string prot = _defaultStreamProt;
+    const auto protIt = args.find(SOAPY_REMOTE_KWARG_PROT);
+    if (protIt != args.end()) prot = protIt->second;
+    const bool datagramMode = (prot == "udp");
+    if (prot == "udp") {}
+    else if (prot == "tcp") {}
+    else throw std::runtime_error(
+        "SoapyRemote::setupStream() protcol not supported;"
+        "expected 'udp' or 'tcp', but got '"+prot+"'");
+    args[SOAPY_REMOTE_KWARG_PROT] = prot;
+
+    size_t mtu = datagramMode?SOAPY_REMOTE_DEFAULT_ENDPOINT_MTU:SOAPY_REMOTE_SOCKET_BUFFMAX;
     const auto mtuIt = args.find(SOAPY_REMOTE_KWARG_MTU);
     if (mtuIt != args.end()) mtu = size_t(std::stod(mtuIt->second));
+    args[SOAPY_REMOTE_KWARG_MTU] = std::to_string(mtu);
 
     size_t window = SOAPY_REMOTE_DEFAULT_ENDPOINT_WINDOW;
     const auto windowIt = args.find(SOAPY_REMOTE_KWARG_WINDOW);
     if (windowIt != args.end()) window = size_t(std::stod(windowIt->second));
+    args[SOAPY_REMOTE_KWARG_WINDOW] = std::to_string(window);
 
     SoapySDR::logf(SOAPY_SDR_INFO, "SoapyRemote::setup%sStream(remoteFormat=%s, localFormat=%s, scaleFactor=%g, mtu=%d, window=%d)",
         (direction == SOAPY_SDR_RX)?"Rx":"Tx", remoteFormat.c_str(), localFormat.c_str(), scaleFactor, int(mtu), int(window));
@@ -173,6 +187,9 @@ SoapySDR::Stream *SoapyRemoteDevice::setupStream(
     ConvertTypes convertType = CONVERT_MEMCPY;
     if (localFormat == remoteFormat) convertType = CONVERT_MEMCPY;
     else if (localFormat == SOAPY_SDR_CF32 and remoteFormat == SOAPY_SDR_CS16) convertType = CONVERT_CF32_CS16;
+    else if (localFormat == SOAPY_SDR_CF32 and remoteFormat == SOAPY_SDR_CS12) convertType = CONVERT_CF32_CS12;
+    else if (localFormat == SOAPY_SDR_CS16 and remoteFormat == SOAPY_SDR_CS12) convertType = CONVERT_CS16_CS12;
+    else if (localFormat == SOAPY_SDR_CS16 and remoteFormat == SOAPY_SDR_CS8) convertType = CONVERT_CS16_CS8;
     else if (localFormat == SOAPY_SDR_CF32 and remoteFormat == SOAPY_SDR_CS8) convertType = CONVERT_CF32_CS8;
     else if (localFormat == SOAPY_SDR_CF32 and remoteFormat == SOAPY_SDR_CU8) convertType = CONVERT_CF32_CU8;
     else throw std::runtime_error(
@@ -192,28 +209,33 @@ SoapySDR::Stream *SoapyRemoteDevice::setupStream(
     const auto localNode = SoapyURL(_sock.getsockname()).getNode();
     const auto remoteNode = SoapyURL(_sock.getpeername()).getNode();
 
-    //bind the stream socket to an automatic port
-    const auto bindURL = SoapyURL("udp", localNode, "0").toString();
-    int ret = data->streamSock.bind(bindURL);
-    if (ret != 0)
+    //bind the receiver side of the sockets in datagram mode
+    std::string clientBindPort, statusBindPort;
+    if (datagramMode)
     {
-        const std::string errorMsg = data->streamSock.lastErrorMsg();
-        delete data;
-        throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
-    }
-    SoapySDR::logf(SOAPY_SDR_INFO, "Client side stream bound to %s", data->streamSock.getsockname().c_str());
-    const auto clientBindPort = SoapyURL(data->streamSock.getsockname()).getService();
+        //bind the stream socket to an automatic port
+        const auto bindURL = SoapyURL("udp", localNode, "0").toString();
+        int ret = data->streamSock.bind(bindURL);
+        if (ret != 0)
+        {
+            const std::string errorMsg = data->streamSock.lastErrorMsg();
+            delete data;
+            throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
+        }
+        SoapySDR::logf(SOAPY_SDR_INFO, "Client side stream bound to %s", data->streamSock.getsockname().c_str());
+        clientBindPort = SoapyURL(data->streamSock.getsockname()).getService();
 
-    //bind the status socket to an automatic port
-    ret = data->statusSock.bind(bindURL);
-    if (ret != 0)
-    {
-        const std::string errorMsg = data->statusSock.lastErrorMsg();
-        delete data;
-        throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
+        //bind the status socket to an automatic port
+        ret = data->statusSock.bind(bindURL);
+        if (ret != 0)
+        {
+            const std::string errorMsg = data->statusSock.lastErrorMsg();
+            delete data;
+            throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
+        }
+        SoapySDR::logf(SOAPY_SDR_INFO, "Client side status bound to %s", data->statusSock.getsockname().c_str());
+        statusBindPort = SoapyURL(data->statusSock.getsockname()).getService();
     }
-    SoapySDR::logf(SOAPY_SDR_INFO, "Client side status bound to %s", data->statusSock.getsockname().c_str());
-    const auto statusBindPort = SoapyURL(data->statusSock.getsockname()).getService();
 
     //setup the remote end of the stream
     std::lock_guard<std::mutex> lock(_mutex);
@@ -227,25 +249,53 @@ SoapySDR::Stream *SoapyRemoteDevice::setupStream(
     packer & statusBindPort;
     packer();
 
-    SoapyRPCUnpacker unpacker(_sock);
+    //for tcp mode: get the binding port here and connect to it
     std::string serverBindPort;
+    if (not datagramMode)
+    {
+        SoapyRPCUnpacker unpackerTcp(_sock);
+        unpackerTcp & serverBindPort;
+        const auto connectURL = SoapyURL(prot, remoteNode, serverBindPort).toString();
+        int ret = data->streamSock.connect(connectURL);
+        if (ret != 0)
+        {
+            const std::string errorMsg = data->streamSock.lastErrorMsg();
+            delete data;
+            throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+        }
+        ret = data->statusSock.connect(connectURL);
+        if (ret != 0)
+        {
+            const std::string errorMsg = data->statusSock.lastErrorMsg();
+            delete data;
+            throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+        }
+    }
+
+    //and wait for the response with binding port and stream id
+    SoapyRPCUnpacker unpacker(_sock);
     unpacker & data->streamId;
     unpacker & serverBindPort;
 
-    //connect the stream socket to the specified port
-    const auto connectURL = SoapyURL("udp", remoteNode, serverBindPort).toString();
-    ret = data->streamSock.connect(connectURL);
-    if (ret != 0)
+    //connect the sending end of the stream socket
+    if (datagramMode)
     {
-        const std::string errorMsg = data->streamSock.lastErrorMsg();
-        delete data;
-        throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+        //connect the stream socket to the specified port
+        const auto connectURL = SoapyURL(prot, remoteNode, serverBindPort).toString();
+        int ret = data->streamSock.connect(connectURL);
+        if (ret != 0)
+        {
+            const std::string errorMsg = data->streamSock.lastErrorMsg();
+            delete data;
+            throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+        }
+        SoapySDR::logf(SOAPY_SDR_INFO, "Client side stream connected to %s", data->streamSock.getpeername().c_str());
     }
-    SoapySDR::logf(SOAPY_SDR_INFO, "Client side stream connected to %s", data->streamSock.getpeername().c_str());
 
     //create endpoint
     data->endpoint = new SoapyStreamEndpoint(data->streamSock, data->statusSock,
-        direction == SOAPY_SDR_RX, channels.size(), SoapySDR::formatToSize(remoteFormat), mtu, window);
+        datagramMode, direction == SOAPY_SDR_RX, channels.size(),
+        SoapySDR::formatToSize(remoteFormat), mtu, window);
 
     return (SoapySDR::Stream *)data;
 }
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 8967291..7bcb359 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -31,6 +31,7 @@ CHECK_INCLUDE_FILES(sys/socket.h HAS_SYS_SOCKET_H)
 CHECK_INCLUDE_FILES(arpa/inet.h HAS_ARPA_INET_H)
 CHECK_INCLUDE_FILES(ifaddrs.h HAS_IFADDRS_H)
 CHECK_INCLUDE_FILES(net/if.h HAS_NET_IF_H)
+CHECK_INCLUDE_FILES(fcntl.h HAS_FCNTL_H)
 
 #network libraries
 if (WIN32)
diff --git a/common/SoapyRPCPacker.cpp b/common/SoapyRPCPacker.cpp
index e768aff..bfbaa9b 100644
--- a/common/SoapyRPCPacker.cpp
+++ b/common/SoapyRPCPacker.cpp
@@ -1,10 +1,11 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include "SoapySocketDefs.hpp"
 #include "SoapyRemoteDefs.hpp"
 #include "SoapyRPCSocket.hpp"
 #include "SoapyRPCPacker.hpp"
+#include <SoapySDR/Version.hpp> //feature defines
 #include <cfloat> //DBL_MANT_DIG
 #include <cmath> //frexp
 #include <cstring> //memcpy
@@ -12,11 +13,12 @@
 #include <algorithm> //min, max
 #include <stdexcept>
 
-SoapyRPCPacker::SoapyRPCPacker(SoapyRPCSocket &sock):
+SoapyRPCPacker::SoapyRPCPacker(SoapyRPCSocket &sock, unsigned int remoteRPCVersion):
     _sock(sock),
     _message(NULL),
     _size(0),
-    _capacity(0)
+    _capacity(0),
+    _remoteRPCVersion(remoteRPCVersion)
 {
     //default allocation
     this->ensureSpace(512);
@@ -129,6 +131,16 @@ void SoapyRPCPacker::operator&(const SoapySDR::Range &value)
     *this & SOAPY_REMOTE_RANGE;
     *this & value.minimum();
     *this & value.maximum();
+
+    //a step size is sent when the remote version matches our current
+    if (_remoteRPCVersion >= SoapyRPCVersion)
+    {
+        #ifdef SOAPY_SDR_API_HAS_RANGE_TYPE_STEP
+        *this & value.step();
+        #else
+        *this & double(0.0);
+        #endif
+    }
 }
 
 void SoapyRPCPacker::operator&(const SoapySDR::RangeList &value)
diff --git a/common/SoapyRPCPacker.hpp b/common/SoapyRPCPacker.hpp
index d6b0734..0fe3117 100644
--- a/common/SoapyRPCPacker.hpp
+++ b/common/SoapyRPCPacker.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #pragma once
@@ -18,7 +18,7 @@ class SoapyRPCSocket;
 class SOAPY_REMOTE_API SoapyRPCPacker
 {
 public:
-    SoapyRPCPacker(SoapyRPCSocket &sock);
+    SoapyRPCPacker(SoapyRPCSocket &sock, unsigned int remoteRPCVersion = SoapyRPCVersion);
 
     ~SoapyRPCPacker(void);
 
@@ -114,4 +114,5 @@ private:
     char *_message;
     size_t _size;
     size_t _capacity;
+    unsigned int _remoteRPCVersion;
 };
diff --git a/common/SoapyRPCSocket.cpp b/common/SoapyRPCSocket.cpp
index 7bdbad8..1e121ee 100644
--- a/common/SoapyRPCSocket.cpp
+++ b/common/SoapyRPCSocket.cpp
@@ -181,6 +181,83 @@ int SoapyRPCSocket::connect(const std::string &url)
     return ret;
 }
 
+int SoapyRPCSocket::connect(const std::string &url, const long timeoutUs)
+{
+    SoapyURL urlObj(url);
+    SockAddrData addr;
+    const auto errorMsg = urlObj.toSockAddr(addr);
+    if (not errorMsg.empty())
+    {
+        this->reportError("getaddrinfo("+url+")", errorMsg);
+        return -1;
+    }
+
+    if (this->null()) _sock = ::socket(addr.addr()->sa_family, urlObj.getType(), 0);
+    if (this->null()) return -1;
+    if (urlObj.getType() == SOCK_STREAM) this->setDefaultTcpSockOpts();
+
+    //enable non blocking
+    int ret = this->setNonBlocking(true);
+    if (ret != 0) return ret;
+
+    //non blocking connect, check for non busy
+    ret = ::connect(_sock, addr.addr(), addr.addrlen());
+    if (ret != 0 and SOCKET_ERRNO != SOCKET_EINPROGRESS)
+    {
+        this->reportError("connect("+url+")");
+        return ret;
+    }
+
+    //fill in the select structures
+    struct timeval tv;
+    tv.tv_sec = timeoutUs / 1000000;
+    tv.tv_usec = timeoutUs % 1000000;
+
+    fd_set fds;
+    FD_ZERO(&fds);
+    FD_SET(_sock, &fds);
+
+    //wait for connect or timeout
+    ret = ::select(_sock+1, NULL, &fds, NULL, &tv);
+    if (ret != 1)
+    {
+        this->reportError("connect("+url+")", SOCKET_ETIMEDOUT);
+        return -1;
+    }
+
+    //get the error code from connect()
+    int opt = 0;
+    socklen_t optlen = sizeof(opt);
+    ::getsockopt(_sock, SOL_SOCKET, SO_ERROR, (char *)&opt, &optlen);
+    if (opt != 0)
+    {
+        this->reportError("connect("+url+")", opt);
+        return opt;
+    }
+
+    //revert non blocking on socket
+    ret = this->setNonBlocking(false);
+    if (ret != 0) return ret;
+
+    return opt;
+}
+
+int SoapyRPCSocket::setNonBlocking(const bool nonblock)
+{
+    int ret = 0;
+    #ifdef _MSC_VER
+    u_long mode = nonblock?1:0;  // 1 to enable non-blocking socket
+    ret = ioctlsocket(_sock, FIONBIO, &mode);
+    #else
+    int mode = fcntl(_sock, F_GETFL, 0);
+    if (nonblock) mode |= O_NONBLOCK;
+    else mode &= ~(O_NONBLOCK);
+    ret = fcntl(_sock, F_SETFL, mode);
+    #endif
+    if (ret != 0) this->reportError("setNonBlocking("+std::string(nonblock?"true":"false")+")");
+    return ret;
+}
+
 /*!
  * OSX doesn't support automatic ipv6mr_interface = 0.
  * The following code attempts to work around this issue
@@ -393,7 +470,11 @@ static std::string errToString(const int err)
 
 void SoapyRPCSocket::reportError(const std::string &what)
 {
-    const int err = SOCKET_ERRNO;
+    this->reportError(what, SOCKET_ERRNO);
+}
+
+void SoapyRPCSocket::reportError(const std::string &what, const int err)
+{
     if (err == 0) _lastErrorMsg = what;
     else this->reportError(what, std::to_string(err) + ": " + errToString(err));
 }
diff --git a/common/SoapyRPCSocket.hpp b/common/SoapyRPCSocket.hpp
index 48d4361..52a13c0 100644
--- a/common/SoapyRPCSocket.hpp
+++ b/common/SoapyRPCSocket.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #pragma once
@@ -79,6 +79,14 @@ public:
     int connect(const std::string &url);
 
     /*!
+     * Connect to client with a timeout is microseconds.
+     */
+    int connect(const std::string &url, const long timeoutUs);
+
+    //! set or clear non blocking on socket
+    int setNonBlocking(const bool nonblock);
+
+    /*!
      * Join a multi-cast group.
      * \param group the url for the multicast group and port number
      * \param loop specify to receive local loopback
@@ -152,6 +160,7 @@ private:
     std::string _lastErrorMsg;
 
     void reportError(const std::string &what, const std::string &errorMsg);
+    void reportError(const std::string &what, const int err);
     void reportError(const std::string &what);
     void setDefaultTcpSockOpts(void);
 };
diff --git a/common/SoapyRPCUnpacker.cpp b/common/SoapyRPCUnpacker.cpp
index 7f7c2e1..6c818af 100644
--- a/common/SoapyRPCUnpacker.cpp
+++ b/common/SoapyRPCUnpacker.cpp
@@ -1,11 +1,13 @@
-// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #include "SoapySocketDefs.hpp"
 #include "SoapyRemoteDefs.hpp"
 #include "SoapyRPCSocket.hpp"
 #include "SoapyRPCUnpacker.hpp"
+#include "SoapyRPCPacker.hpp"
 #include <SoapySDR/Logger.hpp>
+#include <SoapySDR/Version.hpp> //feature defines
 #include <cfloat> //DBL_MANT_DIG
 #include <cmath> //ldexp
 #include <cstring> //memcpy
@@ -13,12 +15,41 @@
 #include <algorithm> //min, max
 #include <stdexcept>
 
-SoapyRPCUnpacker::SoapyRPCUnpacker(SoapyRPCSocket &sock, const bool autoRecv):
+static void testServerConnection(const std::string &url)
+{
+    SoapyRPCSocket s;
+    int ret = s.connect(url, SOAPY_REMOTE_SOCKET_TIMEOUT_US);
+    if (ret != 0) throw std::runtime_error("SoapyRPCUnpacker::recv() FAIL test server connection: "+std::string(s.lastErrorMsg()));
+    SoapyRPCPacker packerHangup(s);
+    packerHangup & SOAPY_REMOTE_HANGUP;
+    packerHangup();
+    s.selectRecv(SOAPY_REMOTE_SOCKET_TIMEOUT_US);
+}
+
+SoapyRPCUnpacker::SoapyRPCUnpacker(SoapyRPCSocket &sock, const bool autoRecv, const long timeoutUs):
     _sock(sock),
     _message(NULL),
     _offset(0),
-    _capacity(0)
+    _capacity(0),
+    _remoteRPCVersion(SoapyRPCVersion)
 {
+    //auto recv expects a reply packet within a reasonable time window
+    //or else the link might be down, in which case we throw an error.
+    //Calls are allowed to take a long time (up to 31 seconds).
+    //However, we continually check that the server is active
+    //so that we can tear down immediately if the server goes away.
+    if (timeoutUs >= 0)
+    {
+        auto subTimeout = std::min<long>(timeoutUs, 1000000); //1 second
+        while (true)
+        {
+            if (_sock.selectRecv(subTimeout)) break;
+            testServerConnection(_sock.getpeername());
+            subTimeout *= 2; //server is up, increase timeout check
+            if (subTimeout >= timeoutUs) throw std::runtime_error("SoapyRPCUnpacker::recv() TIMEOUT: "+std::string(_sock.lastErrorMsg()));
+        }
+    }
+
     if (autoRecv) this->recv();
 }
 
@@ -48,6 +79,7 @@ void SoapyRPCUnpacker::recv(void)
     {
         throw std::runtime_error("SoapyRPCUnpacker::recv() FAIL: header word");
     }
+    _remoteRPCVersion = ntohl(header.version);
     //TODO ignoring the version for now
     //the check may need to be delicate with the version major, minor vs patch number
     const size_t length = ntohl(header.length);
@@ -187,10 +219,21 @@ void SoapyRPCUnpacker::operator&(std::string &value)
 void SoapyRPCUnpacker::operator&(SoapySDR::Range &value)
 {
     UNPACK_TYPE_HELPER(SOAPY_REMOTE_RANGE);
-    double minimum = 0.0, maximum = 0.0;
+    double minimum = 0.0, maximum = 0.0, step = 0.0;
     *this & minimum;
     *this & maximum;
+
+    //a step size is sent when the remote version matches our current
+    if (_remoteRPCVersion >= SoapyRPCVersion)
+    {
+        *this & step;
+    }
+
+    #ifdef SOAPY_SDR_API_HAS_RANGE_TYPE_STEP
+    value = SoapySDR::Range(minimum, maximum, step);
+    #else
     value = SoapySDR::Range(minimum, maximum);
+    #endif
 }
 
 void SoapyRPCUnpacker::operator&(SoapySDR::RangeList &value)
diff --git a/common/SoapyRPCUnpacker.hpp b/common/SoapyRPCUnpacker.hpp
index 6fee795..3104135 100644
--- a/common/SoapyRPCUnpacker.hpp
+++ b/common/SoapyRPCUnpacker.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #pragma once
@@ -17,7 +17,7 @@ class SoapyRPCSocket;
 class SOAPY_REMOTE_API SoapyRPCUnpacker
 {
 public:
-    SoapyRPCUnpacker(SoapyRPCSocket &sock, const bool autoRecv = true);
+    SoapyRPCUnpacker(SoapyRPCSocket &sock, const bool autoRecv = true, const long timeoutUs = 30000000);
 
     ~SoapyRPCUnpacker(void);
 
@@ -104,6 +104,12 @@ public:
     //! Unpack a list of arg infos
     void operator&(SoapySDR::ArgInfoList &value);
 
+    //! Get the received RPC version number
+    unsigned int remoteRPCVersion(void) const
+    {
+        return _remoteRPCVersion;
+    }
+
 private:
 
     void ensureSpace(const size_t length);
@@ -112,4 +118,5 @@ private:
     char *_message;
     size_t _offset;
     size_t _capacity;
+    unsigned int _remoteRPCVersion;
 };
diff --git a/common/SoapyRemoteDefs.hpp b/common/SoapyRemoteDefs.hpp
index 23ebe16..368ae71 100644
--- a/common/SoapyRemoteDefs.hpp
+++ b/common/SoapyRemoteDefs.hpp
@@ -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
 
@@ -23,6 +23,9 @@
 //! Stream args key to set the buffer MTU bytes for network transfers
 #define SOAPY_REMOTE_KWARG_MTU (SOAPY_REMOTE_KWARG_PREFIX "mtu")
 
+//! Stream args key to select the stream's protocol (tcp or udp)
+#define SOAPY_REMOTE_KWARG_PROT (SOAPY_REMOTE_KWARG_PREFIX "prot")
+
 /*!
  * Default stream transfer size (under network MTU).
  * Larger transfer sizes may not be supported in hardware
@@ -64,7 +67,7 @@
 #define SOAPY_REMOTE_DEFAULT_SERVICE "55132"
 
 //! Use this timeout for every socket poll loop
-#define SOAPY_REMOTE_SOCKET_TIMEOUT_US (50*1000) //50 ms
+#define SOAPY_REMOTE_SOCKET_TIMEOUT_US (500*1000) //500 ms
 
 //! Backlog count for the server socket listen
 #define SOAPY_REMOTE_LISTEN_BACKLOG 100
@@ -92,7 +95,7 @@
  **********************************************************************/
 //major, minor, patch when this was last updated
 //bump the version number when changes are made
-static const unsigned int SoapyRPCVersion = 0x000300;
+static const unsigned int SoapyRPCVersion = 0x000400;
 
 enum SoapyRemoteTypes
 {
@@ -167,6 +170,9 @@ enum SoapyRemoteCalls
     SOAPY_REMOTE_HAS_IQ_BALANCE_MODE      = 606,
     SOAPY_REMOTE_SET_IQ_BALANCE_MODE      = 607,
     SOAPY_REMOTE_GET_IQ_BALANCE_MODE      = 608,
+    SOAPY_REMOTE_HAS_FREQUENCY_CORRECTION  = 503,
+    SOAPY_REMOTE_SET_FREQUENCY_CORRECTION  = 504,
+    SOAPY_REMOTE_GET_FREQUENCY_CORRECTION  = 505,
 
     //gain
     SOAPY_REMOTE_LIST_GAINS               = 700,
@@ -194,6 +200,7 @@ enum SoapyRemoteCalls
     SOAPY_REMOTE_SET_SAMPLE_RATE               = 900,
     SOAPY_REMOTE_GET_SAMPLE_RATE               = 901,
     SOAPY_REMOTE_LIST_SAMPLE_RATES             = 902,
+    SOAPY_REMOTE_GET_SAMPLE_RATE_RANGE         = 907,
 
     //bandwidth
     SOAPY_REMOTE_SET_BANDWIDTH                 = 903,
@@ -232,6 +239,8 @@ enum SoapyRemoteCalls
     SOAPY_REMOTE_LIST_REGISTER_INTERFACES  = 1302,
     SOAPY_REMOTE_WRITE_REGISTER_NAMED      = 1303,
     SOAPY_REMOTE_READ_REGISTER_NAMED       = 1304,
+    SOAPY_REMOTE_WRITE_REGISTERS           = 1305,
+    SOAPY_REMOTE_READ_REGISTERS            = 1306,
 
     //settings
     SOAPY_REMOTE_WRITE_SETTING            = 1400,
diff --git a/common/SoapySocketDefs.in.hpp b/common/SoapySocketDefs.in.hpp
index 9356ed8..19ae419 100644
--- a/common/SoapySocketDefs.in.hpp
+++ b/common/SoapySocketDefs.in.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 // ** This header should be included first, to avoid compile errors.
@@ -73,6 +73,11 @@ typedef int socklen_t;
 #include <net/if.h> //if_nametoindex
 #endif //HAS_NET_IF_H
 
+#cmakedefine HAS_FCNTL_H
+#ifdef HAS_FCNTL_H
+#include <fcntl.h> //fcntl and constants
+#endif //HAS_FCNTL_H
+
 /***********************************************************************
  * htonll and ntohll for GCC
  **********************************************************************/
@@ -104,8 +109,12 @@ typedef int socklen_t;
  **********************************************************************/
 #ifdef _MSC_VER
 #define SOCKET_ERRNO WSAGetLastError()
+#define SOCKET_EINPROGRESS WSAEWOULDBLOCK
+#define SOCKET_ETIMEDOUT WSAETIMEDOUT
 #else
 #define SOCKET_ERRNO errno
+#define SOCKET_EINPROGRESS EINPROGRESS
+#define SOCKET_ETIMEDOUT ETIMEDOUT
 #endif
 
 /***********************************************************************
diff --git a/common/SoapyStreamEndpoint.cpp b/common/SoapyStreamEndpoint.cpp
index 4705031..5722412 100644
--- a/common/SoapyStreamEndpoint.cpp
+++ b/common/SoapyStreamEndpoint.cpp
@@ -8,6 +8,7 @@
 #include "SoapyURLUtils.hpp"
 #include "SoapyRemoteDefs.hpp"
 #include "SoapySocketDefs.hpp"
+#include <algorithm> //min/max
 #include <cassert>
 #include <cstdint>
 
@@ -28,6 +29,7 @@ struct StreamDatagramHeader
 SoapyStreamEndpoint::SoapyStreamEndpoint(
     SoapyRPCSocket &streamSock,
     SoapyRPCSocket &statusSock,
+    const bool datagramMode,
     const bool isRecv,
     const size_t numChans,
     const size_t elemSize,
@@ -35,6 +37,7 @@ SoapyStreamEndpoint::SoapyStreamEndpoint(
     const size_t window):
     _streamSock(streamSock),
     _statusSock(statusSock),
+    _datagramMode(datagramMode),
     _xferSize(mtu-PROTO_HEADER_SIZE),
     _numChans(numChans),
     _elemSize(elemSize),
@@ -168,6 +171,8 @@ bool SoapyStreamEndpoint::waitRecv(const long timeoutUs)
 
 int SoapyStreamEndpoint::acquireRecv(size_t &handle, const void **buffs, int &flags, long long &timeNs)
 {
+    int ret = 0;
+
     //no available handles, the user is hoarding them...
     if (_numHandlesAcquired == _buffData.size())
     {
@@ -181,23 +186,38 @@ int SoapyStreamEndpoint::acquireRecv(size_t &handle, const void **buffs, int &fl
 
     //receive into the buffer
     assert(not _streamSock.null());
-    int ret = _streamSock.recv(data.buff.data(), data.buff.size());
+    if (_datagramMode) ret = _streamSock.recv(data.buff.data(), data.buff.size());
+    else ret = _streamSock.recv(data.buff.data(), HEADER_SIZE, MSG_WAITALL);
     if (ret < 0)
     {
         SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::acquireRecv(), FAILED %s", _streamSock.lastErrorMsg());
         return SOAPY_SDR_STREAM_ERROR;
     }
+    size_t bytesRecvd = size_t(ret);
     _receiveInitial = true;
 
     //check the header
     auto header = (const StreamDatagramHeader*)data.buff.data();
     size_t bytes = ntohl(header->bytes);
-    if (bytes > size_t(ret))
+
+    if (_datagramMode and bytes > bytesRecvd)
     {
         SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::acquireRecv(%d bytes), FAILED %d\n"
             "This MTU setting may be unachievable. Check network configuration.", int(bytes), ret);
         return SOAPY_SDR_STREAM_ERROR;
     }
+
+    else while (bytesRecvd < bytes)
+    {
+        ret = _streamSock.recv(data.buff.data()+bytesRecvd, std::min<size_t>(SOAPY_REMOTE_SOCKET_BUFFMAX, bytes-bytesRecvd));
+        if (ret < 0)
+        {
+            SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::acquireRecv(), FAILED %s", _streamSock.lastErrorMsg());
+            return SOAPY_SDR_STREAM_ERROR;
+        }
+        bytesRecvd += size_t(ret);
+    }
+
     const int numElemsOrErr = int(ntohl(header->elems));
 
     //dropped or out of order packets
@@ -307,14 +327,21 @@ void SoapyStreamEndpoint::releaseSend(const size_t handle, const int numElemsOrE
 
     //send from the buffer
     assert(not _streamSock.null());
-    int ret = _streamSock.send(data.buff.data(), bytes);
-    if (ret < 0)
-    {
-        SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::releaseSend(), FAILED %s", _streamSock.lastErrorMsg());
-    }
-    else if (size_t(ret) != bytes)
+    size_t bytesSent = 0;
+    while (bytesSent < bytes)
     {
-        SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::releaseSend(%d bytes), FAILED %d", int(bytes), ret);
+        int ret = _streamSock.send(data.buff.data()+bytesSent, std::min<size_t>(SOAPY_REMOTE_SOCKET_BUFFMAX, bytes-bytesSent));
+        if (ret < 0)
+        {
+            SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::releaseSend(), FAILED %s", _streamSock.lastErrorMsg());
+            break;
+        }
+        bytesSent += size_t(ret);
+        if (not _datagramMode) continue;
+        if (bytesSent != bytes)
+        {
+            SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::releaseSend(%d bytes), FAILED %d", int(bytes), ret);
+        }
     }
 
     //actually release in order of handle index
diff --git a/common/SoapyStreamEndpoint.hpp b/common/SoapyStreamEndpoint.hpp
index 93587ea..ceac48a 100644
--- a/common/SoapyStreamEndpoint.hpp
+++ b/common/SoapyStreamEndpoint.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2016 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #pragma once
@@ -19,6 +19,7 @@ public:
     SoapyStreamEndpoint(
         SoapyRPCSocket &streamSock,
         SoapyRPCSocket &statusSock,
+        const bool datagramMode,
         const bool isRecv,
         const size_t numChans,
         const size_t elemSize,
@@ -125,6 +126,7 @@ public:
 private:
     SoapyRPCSocket &_streamSock;
     SoapyRPCSocket &_statusSock;
+    const bool _datagramMode;
     const size_t _xferSize;
     const size_t _numChans;
     const size_t _elemSize;
diff --git a/debian/changelog b/debian/changelog
index 0f49d24..52f7e31 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,22 @@
-soapyremote (0.3.2) unstable; urgency=low
+soapyremote (0.4.2-1) unstable; urgency=low
+
+  * Release 0.4.2 (2017-07-31)
+
+ -- Josh Blum <josh at pothosware.com>  Mon, 31 Jul 2017 09:15:34 -0000
+
+soapyremote (0.4.1-1) unstable; urgency=low
+
+  * Release 0.4.1 (2017-06-08)
+
+ -- Josh Blum <josh at pothosware.com>  Thu, 08 Jun 2017 18:18:32 -0000
+
+soapyremote (0.4.0-1) unstable; urgency=low
+
+  * Release 0.4.0 (2017-04-29)
+
+ -- Josh Blum <josh at pothosware.com>  Sat, 29 Apr 2017 10:11:25 -0000
+
+soapyremote (0.3.2-1) unstable; urgency=low
 
   * Release 0.3.2 (2016-12-04)
 
diff --git a/debian/control b/debian/control
index 16c85cc..3b67b88 100644
--- a/debian/control
+++ b/debian/control
@@ -11,7 +11,7 @@ Homepage: https://github.com/pothosware/SoapyRemote/wiki
 Vcs-Git: https://github.com/pothosware/SoapyRemote.git
 Vcs-Browser: https://github.com/pothosware/SoapyRemote
 
-Package: soapysdr0.5-2-module-remote
+Package: soapysdr0.6-module-remote
 Architecture: any
 Multi-Arch: same
 Depends: ${shlibs:Depends}, ${misc:Depends}
@@ -20,7 +20,7 @@ Description: Soapy Remote - Remote device support for Soapy SDR.
 
 Package: soapysdr-module-remote
 Architecture: all
-Depends: soapysdr0.5-2-module-remote, ${misc:Depends}
+Depends: soapysdr0.6-module-remote, ${misc:Depends}
 Description: Soapy Remote - Remote device support for Soapy SDR.
  A Soapy module that supports remote devices within the Soapy API.
  .
@@ -30,6 +30,6 @@ Description: Soapy Remote - Remote device support for Soapy SDR.
 Package: soapysdr-server
 Section: libs
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}
+Depends: ${shlibs:Depends}, ${misc:Depends}, init-system-helpers
 Description: Soapy Remote - Remote device support for Soapy SDR.
  The SoapySDRServer server application for remote devices.
diff --git a/debian/copyright b/debian/copyright
index 372433f..7154043 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -4,7 +4,7 @@ Source: https://github.com/pothosware/SoapyRemote/wiki
 
 Files: *
 Copyright:
-    Copyright (c) 2015-2016 Josh Blum <josh at pothosware.com>
+    Copyright (c) 2015-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/soapysdr-server.install b/debian/soapysdr-server.install
index ef04224..b968fd3 100644
--- a/debian/soapysdr-server.install
+++ b/debian/soapysdr-server.install
@@ -1,3 +1,4 @@
 usr/bin/
-usr/lib/systemd/
+#systemd-service-file-outside-lib
+usr/lib/systemd/ lib/
 usr/lib/sysctl.d/
diff --git a/debian/soapysdr-server.postrm b/debian/soapysdr-server.postrm
new file mode 100644
index 0000000..185dbe4
--- /dev/null
+++ b/debian/soapysdr-server.postrm
@@ -0,0 +1,13 @@
+#!/bin/sh
+set -e
+
+if [ "$1" = "remove" ]; then
+	deb-systemd-helper mask SoapySDRServer.service > /dev/null
+fi
+
+if [ "$1" = "remove" ]; then
+	deb-systemd-helper purge SoapySDRServer.service > /dev/null
+	deb-systemd-helper unmask SoapySDRServer.service > /dev/null
+fi
+
+#DEBHELPER#
diff --git a/debian/soapysdr-server.prerm b/debian/soapysdr-server.prerm
index 8028023..9b75ccc 100644
--- a/debian/soapysdr-server.prerm
+++ b/debian/soapysdr-server.prerm
@@ -2,8 +2,7 @@
 set -e
 
 if [ "$1" = "remove" ]; then
-	systemctl stop SoapySDRServer
-	systemctl disable SoapySDRServer
+	deb-systemd-invoke stop SoapySDRServer.service > /dev/null
 fi
 
 #DEBHELPER#
diff --git a/debian/soapysdr0.5-2-module-remote.install b/debian/soapysdr0.6-module-remote.install
similarity index 100%
rename from debian/soapysdr0.5-2-module-remote.install
rename to debian/soapysdr0.6-module-remote.install
diff --git a/server/ClientHandler.cpp b/server/ClientHandler.cpp
index f5e0a11..133b1a8 100644
--- a/server/ClientHandler.cpp
+++ b/server/ClientHandler.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
 
@@ -63,8 +63,8 @@ bool SoapyClientHandler::handleOnce(void)
     if (not _sock.selectRecv(SOAPY_REMOTE_SOCKET_TIMEOUT_US)) return true;
 
     //receive the client's request
-    SoapyRPCUnpacker unpacker(_sock);
-    SoapyRPCPacker packer(_sock);
+    SoapyRPCUnpacker unpacker(_sock, true, -1/*no timeout*/);
+    SoapyRPCPacker packer(_sock, unpacker.remoteRPCVersion());
 
     //handle the client's request
     bool again = true;
@@ -311,6 +311,11 @@ bool SoapyClientHandler::handleOnce(SoapyRPCUnpacker &unpacker, SoapyRPCPacker &
         const auto priorityIt = args.find(SOAPY_REMOTE_KWARG_PRIORITY);
         if (priorityIt != args.end()) priority = std::stod(priorityIt->second);
 
+        std::string prot = "udp";
+        const auto protIt = args.find(SOAPY_REMOTE_KWARG_PROT);
+        if (protIt != args.end()) prot = protIt->second;
+        const bool datagramMode = (prot == "udp");
+
         //create stream
         auto stream = _dev->setupStream(direction, format, channels, args);
 
@@ -327,43 +332,82 @@ bool SoapyClientHandler::handleOnce(SoapyRPCUnpacker &unpacker, SoapyRPCPacker &
         const auto localNode = SoapyURL(_sock.getsockname()).getNode();
         const auto remoteNode = SoapyURL(_sock.getpeername()).getNode();
 
-        //bind the stream socket to an automatic port
-        const auto bindURL = SoapyURL("udp", localNode, "0").toString();
-        int ret = data.streamSock.bind(bindURL);
-        if (ret != 0)
-        {
-            const std::string errorMsg = data.streamSock.lastErrorMsg();
-            _streamData.erase(data.streamId);
-            throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
-        }
-        SoapySDR::logf(SOAPY_SDR_INFO, "Server side stream bound to %s", data.streamSock.getsockname().c_str());
-        const auto serverBindPort = SoapyURL(data.streamSock.getsockname()).getService();
+        const auto bindURL = SoapyURL(prot, localNode, "0").toString();
+        std::string serverBindPort;
 
-        //connect the stream socket to the specified port
-        auto connectURL = SoapyURL("udp", remoteNode, clientBindPort).toString();
-        ret = data.streamSock.connect(connectURL);
-        if (ret != 0)
+        //in udp mode connect to the bound sockets on the client side
+        if (datagramMode)
         {
-            const std::string errorMsg = data.streamSock.lastErrorMsg();
-            _streamData.erase(data.streamId);
-            throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+            data.streamSock = new SoapyRPCSocket();
+            data.statusSock = new SoapyRPCSocket();
+
+            //bind the stream socket to an automatic port
+            int ret = data.streamSock->bind(bindURL);
+            if (ret != 0)
+            {
+                const std::string errorMsg = data.streamSock->lastErrorMsg();
+                _streamData.erase(data.streamId);
+                throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
+            }
+            SoapySDR::logf(SOAPY_SDR_INFO, "Server side stream bound to %s", data.streamSock->getsockname().c_str());
+            serverBindPort = SoapyURL(data.streamSock->getsockname()).getService();
+
+            //connect the stream socket to the specified port
+            auto connectURL = SoapyURL("udp", remoteNode, clientBindPort).toString();
+            ret = data.streamSock->connect(connectURL);
+            if (ret != 0)
+            {
+                const std::string errorMsg = data.streamSock->lastErrorMsg();
+                _streamData.erase(data.streamId);
+                throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+            }
+            SoapySDR::logf(SOAPY_SDR_INFO, "Server side stream connected to %s", data.streamSock->getpeername().c_str());
+
+            //connect the status socket to the specified port
+            connectURL = SoapyURL("udp", remoteNode, statusBindPort).toString();
+            ret = data.statusSock->connect(connectURL);
+            if (ret != 0)
+            {
+                const std::string errorMsg = data.statusSock->lastErrorMsg();
+                _streamData.erase(data.streamId);
+                throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+            }
+            SoapySDR::logf(SOAPY_SDR_INFO, "Server side status connected to %s", data.statusSock->getpeername().c_str());
         }
-        SoapySDR::logf(SOAPY_SDR_INFO, "Server side stream connected to %s", data.streamSock.getpeername().c_str());
 
-        //connect the status socket to the specified port
-        connectURL = SoapyURL("udp", remoteNode, statusBindPort).toString();
-        ret = data.statusSock.connect(connectURL);
-        if (ret != 0)
+        //in tcp mode, setup the server socket to listen,
+        //send the binding port back to the client and
+        //accept the client's new connections
+        else
         {
-            const std::string errorMsg = data.statusSock.lastErrorMsg();
-            _streamData.erase(data.streamId);
-            throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+            SoapyRPCSocket serverSocket;
+            int ret = serverSocket.bind(bindURL);
+            if (ret != 0)
+            {
+                const std::string errorMsg = serverSocket.lastErrorMsg();
+                _streamData.erase(data.streamId);
+                throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
+            }
+            SoapySDR::logf(SOAPY_SDR_INFO, "Server side stream bound to %s", serverSocket.getsockname().c_str());
+            serverBindPort = SoapyURL(serverSocket.getsockname()).getService();
+
+            serverSocket.listen(2);
+            SoapyRPCPacker packerTcp(_sock);
+            packerTcp & serverBindPort;
+            packerTcp();
+            data.streamSock = serverSocket.accept();
+            data.statusSock = serverSocket.accept();
+            if (data.streamSock == nullptr or data.statusSock == nullptr)
+            {
+                const std::string errorMsg = serverSocket.lastErrorMsg();
+                throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- accept FAIL: " + errorMsg);
+            }
         }
-        SoapySDR::logf(SOAPY_SDR_INFO, "Server side status connected to %s", data.statusSock.getpeername().c_str());
 
         //create endpoint
-        data.endpoint = new SoapyStreamEndpoint(data.streamSock, data.statusSock,
-            direction == SOAPY_SDR_TX, channels.size(), SoapySDR::formatToSize(format), mtu, window);
+        data.endpoint = new SoapyStreamEndpoint(*data.streamSock, *data.statusSock,
+            datagramMode, direction == SOAPY_SDR_TX, channels.size(),
+            SoapySDR::formatToSize(format), mtu, window);
 
         //start worker thread, this is not backwards,
         //receive from device means using a send endpoint
@@ -569,6 +613,54 @@ bool SoapyClientHandler::handleOnce(SoapyRPCUnpacker &unpacker, SoapyRPCPacker &
     } break;
 
     ////////////////////////////////////////////////////////////////////
+    case SOAPY_REMOTE_HAS_FREQUENCY_CORRECTION:
+    ////////////////////////////////////////////////////////////////////
+    {
+        char direction = 0;
+        int channel = 0;
+        unpacker & direction;
+        unpacker & channel;
+        #ifdef SOAPY_SDR_API_HAS_FREQUENCY_CORRECTION_API
+        packer & _dev->hasFrequencyCorrection(direction, channel);
+        #else
+        bool result(false);
+        packer & result;
+        #endif
+    } break;
+
+    ////////////////////////////////////////////////////////////////////
+    case SOAPY_REMOTE_SET_FREQUENCY_CORRECTION:
+    ////////////////////////////////////////////////////////////////////
+    {
+        char direction = 0;
+        int channel = 0;
+        double value(0.0);
+        unpacker & direction;
+        unpacker & channel;
+        unpacker & value;
+        #ifdef SOAPY_SDR_API_HAS_FREQUENCY_CORRECTION_API
+        _dev->setFrequencyCorrection(direction, channel, value);
+        #endif
+        packer & SOAPY_REMOTE_VOID;
+    } break;
+
+    ////////////////////////////////////////////////////////////////////
+    case SOAPY_REMOTE_GET_FREQUENCY_CORRECTION:
+    ////////////////////////////////////////////////////////////////////
+    {
+        char direction = 0;
+        int channel = 0;
+        unpacker & direction;
+        unpacker & channel;
+        #ifdef SOAPY_SDR_API_HAS_FREQUENCY_CORRECTION_API
+        packer & _dev->getFrequencyCorrection(direction, channel);
+        #else
+        double result(0.0);
+        packer & result;
+        #endif
+    } break;
+
+    ////////////////////////////////////////////////////////////////////
     case SOAPY_REMOTE_LIST_GAINS:
     ////////////////////////////////////////////////////////////////////
     {
@@ -835,6 +927,22 @@ bool SoapyClientHandler::handleOnce(SoapyRPCUnpacker &unpacker, SoapyRPCPacker &
     } break;
 
     ////////////////////////////////////////////////////////////////////
+    case SOAPY_REMOTE_GET_SAMPLE_RATE_RANGE:
+    ////////////////////////////////////////////////////////////////////
+    {
+        char direction = 0;
+        int channel = 0;
+        unpacker & direction;
+        unpacker & channel;
+        #ifdef SOAPY_SDR_API_HAS_GET_SAMPLE_RATE_RANGE
+        packer & _dev->getSampleRateRange(direction, channel);
+        #else
+        SoapySDR::RangeList result;
+        packer & result;
+        #endif
+    } break;
+
+    ////////////////////////////////////////////////////////////////////
     case SOAPY_REMOTE_SET_BANDWIDTH:
     ////////////////////////////////////////////////////////////////////
     {
@@ -1128,6 +1236,43 @@ bool SoapyClientHandler::handleOnce(SoapyRPCUnpacker &unpacker, SoapyRPCPacker &
     } break;
 
     ////////////////////////////////////////////////////////////////////
+    case SOAPY_REMOTE_WRITE_REGISTERS:
+    ////////////////////////////////////////////////////////////////////
+    {
+        std::string name;
+        int addr = 0;
+        std::vector<size_t> value;
+        unpacker & name;
+        unpacker & addr;
+        unpacker & value;
+        #ifdef SOAPY_SDR_API_HAS_NAMED_REGISTERS_API
+        std::vector <unsigned> val (value.begin(), value.end());
+        _dev->writeRegisters(name, unsigned(addr), val);
+        #endif
+        packer & SOAPY_REMOTE_VOID;
+    } break;
+
+    ////////////////////////////////////////////////////////////////////
+    case SOAPY_REMOTE_READ_REGISTERS:
+    ////////////////////////////////////////////////////////////////////
+    {
+        std::string name;
+        int addr = 0;
+        int length;
+        unpacker & name;
+        unpacker & addr;
+        unpacker & length;
+        #ifdef SOAPY_SDR_API_HAS_NAMED_REGISTERS_API
+        std::vector <unsigned> val = _dev->readRegisters(name, unsigned(addr), size_t(length));
+        std::vector <size_t> value (val.begin(), val.end());
+        packer & (value);
+        #else
+        std::vector <size_t> value;
+        packer & (value);
+        #endif
+    } break;
+
+    ////////////////////////////////////////////////////////////////////
     case SOAPY_REMOTE_GET_SETTING_INFO:
     ////////////////////////////////////////////////////////////////////
     {
diff --git a/server/ServerStreamData.cpp b/server/ServerStreamData.cpp
index a44e600..063d15d 100644
--- a/server/ServerStreamData.cpp
+++ b/server/ServerStreamData.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2017 Josh Blum
 // Copyright (c) 2016-2016 Bastille Networks
 // SPDX-License-Identifier: BSL-1.0
 
@@ -27,6 +27,8 @@ ServerStreamData::ServerStreamData(void):
     chanMask(0),
     priority(0.0),
     streamId(-1),
+    streamSock(nullptr),
+    statusSock(nullptr),
     endpoint(nullptr),
     streamThread(nullptr),
     statusThread(nullptr),
@@ -35,6 +37,12 @@ ServerStreamData::ServerStreamData(void):
     return;
 }
 
+ServerStreamData::~ServerStreamData(void)
+{
+    delete streamSock;
+    delete statusSock;
+}
+
 void ServerStreamData::startSendThread(void)
 {
     assert(streamId != -1);
@@ -100,7 +108,7 @@ void ServerStreamData::recvEndpointWork(void)
         ret = endpoint->acquireRecv(handle, buffs.data(), flags, timeNs);
         if (ret < 0)
         {
-            SoapySDR::logf(SOAPY_SDR_ERROR, "Server-side receive endpoint: %s; worker quitting...", streamSock.lastErrorMsg());
+            SoapySDR::logf(SOAPY_SDR_ERROR, "Server-side receive endpoint: %s; worker quitting...", streamSock->lastErrorMsg());
             return;
         }
 
@@ -153,7 +161,7 @@ void ServerStreamData::sendEndpointWork(void)
         ret = endpoint->acquireSend(handle, buffs.data());
         if (ret < 0)
         {
-            SoapySDR::logf(SOAPY_SDR_ERROR, "Server-side send endpoint: %s; worker quitting...", streamSock.lastErrorMsg());
+            SoapySDR::logf(SOAPY_SDR_ERROR, "Server-side send endpoint: %s; worker quitting...", streamSock->lastErrorMsg());
             return;
         }
 
@@ -182,7 +190,8 @@ void ServerStreamData::sendEndpointWork(void)
         //This is a latency optimization to forward to the host ASAP,
         //but to use the full bandwidth when more data is available.
         //Do not allow this optimization when end of burst or single packet mode to preserve boundaries
-        if (elemsRead != 0 and elemsLeft != 0 and (flags & (SOAPY_SDR_END_BURST | SOAPY_SDR_ONE_PACKET)) == 0)
+        static const int trailingFlags(SOAPY_SDR_END_BURST | SOAPY_SDR_ONE_PACKET | SOAPY_SDR_END_ABRUPT);
+        if (elemsRead != 0 and elemsLeft != 0 and (flags & trailingFlags) == 0)
         {
             int flags1 = 0;
             long long timeNs1 = 0;
@@ -193,6 +202,9 @@ void ServerStreamData::sendEndpointWork(void)
                 elemsLeft -= ret;
                 elemsRead += ret;
             }
+
+            //include trailing flags that come from the second read
+            flags |= (flags1 & trailingFlags);
         }
 
         //release the buffer with flags and time from the first read
diff --git a/server/ServerStreamData.hpp b/server/ServerStreamData.hpp
index c9f7ef2..ffd3b66 100644
--- a/server/ServerStreamData.hpp
+++ b/server/ServerStreamData.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2015-2016 Josh Blum
 // SPDX-License-Identifier: BSL-1.0
 
 #pragma once
@@ -25,6 +25,7 @@ class ServerStreamData
 {
 public:
     ServerStreamData(void);
+    ~ServerStreamData(void);
 
     SoapySDR::Device *device;
     SoapySDR::Stream *stream;
@@ -36,10 +37,10 @@ public:
     int streamId;
 
     //datagram socket for stream endpoint
-    SoapyRPCSocket streamSock;
+    SoapyRPCSocket *streamSock;
 
     //datagram socket for status endpoint
-    SoapyRPCSocket statusSock;
+    SoapyRPCSocket *statusSock;
 
     //remote side of the stream endpoint
     SoapyStreamEndpoint *endpoint;
diff --git a/system/SoapySDRServer.service.in b/system/SoapySDRServer.service.in
index a1dcb02..058b244 100644
--- a/system/SoapySDRServer.service.in
+++ b/system/SoapySDRServer.service.in
@@ -1,5 +1,7 @@
 [Unit]
 Description=SoapyRemote network server
+Wants=network-online.target
+After=network-online.target
 
 [Service]
 ExecStart=@CMAKE_INSTALL_PREFIX@/bin/SoapySDRServer --bind

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



More information about the pkg-hamradio-commits mailing list