Exemple #1
0
SoapySDR::Kwargs SDRThread::combineArgs(SoapySDR::Kwargs a, SoapySDR::Kwargs b) {
    SoapySDR::Kwargs c;
    SoapySDR::Kwargs::iterator i;
    for (i = a.begin(); i != a.end(); i++) {
        c[i->first] = i->second;
    }
    for (i = b.begin(); i != b.end(); i++) {
        c[i->first] = i->second;
    }
    return c;
}
Exemple #2
0
std::vector<SoapySDR::Kwargs> findNullDevice(const SoapySDR::Kwargs &args)
{
    std::vector<SoapySDR::Kwargs> results;

    //require that the user specify type=null
    if (args.count("type") == 0) return results;
    if (args.at("type") != "null") return results;

    SoapySDR::Kwargs nullArgs;
    nullArgs["type"] = "null";
    results.push_back(nullArgs);

    return results;
}
static bladerf_devinfo kwargs_to_devinfo(const SoapySDR::Kwargs &args)
{
    std::stringstream ss;

    if (args.count("backend") != 0)
    {
        ss << args.at("backend") << ":";
    }
    else ss << "*:";

    if (args.count("device") != 0)
    {
        ss << "device=" << args.at("device") << " ";
    }

    if (args.count("instance") != 0)
    {
        ss << "instance=" << args.at("instance") << " ";
    }

    if (args.count("serial") != 0)
    {
        ss << "serial=" << args.at("serial") << " ";
    }

    bladerf_devinfo info;
    bladerf_init_devinfo(&info);
    bladerf_get_devinfo_from_str(ss.str().c_str(), &info);
    return info;
}
void SoapyRPCUnpacker::operator&(SoapySDR::Kwargs &value)
{
    UNPACK_TYPE_HELPER(SOAPY_REMOTE_KWARGS);
    int size = 0;
    *this & size;
    value.clear();
    for (size_t i = 0; i < size_t(size); i++)
    {
        std::string key, val;
        *this & key;
        *this & val;
        value[key] = val;
    }
}
Exemple #5
0
/***********************************************************************
 * Discovery routine -- connect to server when key specified
 **********************************************************************/
static std::vector<SoapySDR::Kwargs> findRemote(const SoapySDR::Kwargs &args)
{
    std::vector<SoapySDR::Kwargs> result;

    if (args.count(SOAPY_REMOTE_KWARG_STOP) != 0) return result;

    //no remote specified, use the discovery protocol
    if (args.count("remote") == 0)
    {
        //On non-windows platforms the endpoint instance can last the
        //duration of the process because it can be cleaned up safely.
        //Windows has issues cleaning up threads and sockets on exit.
        #ifndef _MSC_VER
        static
        #endif //_MSC_VER
        auto ssdpEndpoint = SoapySSDPEndpoint::getInstance();

        //enable forces new search queries
        ssdpEndpoint->enablePeriodicSearch(true);

        //wait maximum timeout for replies
        std::this_thread::sleep_for(std::chrono::microseconds(SOAPY_REMOTE_SOCKET_TIMEOUT_US));

        for (const auto &url : SoapySSDPEndpoint::getInstance()->getServerURLs())
        {
            auto argsWithURL = args;
            argsWithURL["remote"] = url;
            const auto subResult = findRemote(argsWithURL);
            result.insert(result.end(), subResult.begin(), subResult.end());
        }

        return result;
    }

    //otherwise connect to a specific url and enumerate
    auto url = SoapyURL(args.at("remote"));

    //default url parameters when not specified
    if (url.getScheme().empty()) url.setScheme("tcp");
    if (url.getService().empty()) url.setService(SOAPY_REMOTE_DEFAULT_SERVICE);

    //try to connect to the remote server
    SoapySocketSession sess;
    SoapyRPCSocket s;
    int ret = s.connect(url.toString());
    if (ret != 0)
    {
        SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyRemote::find() -- connect(%s) FAIL: %s", url.toString().c_str(), s.lastErrorMsg());
        return result;
    }

    //find transaction
    try
    {
        SoapyLogAcceptor logAcceptor(url.toString(), s);

        SoapyRPCPacker packer(s);
        packer & SOAPY_REMOTE_FIND;
        packer & translateArgs(args);
        packer();
        SoapyRPCUnpacker unpacker(s);
        unpacker & result;

        //graceful disconnect
        SoapyRPCPacker packerHangup(s);
        packerHangup & SOAPY_REMOTE_HANGUP;
        packerHangup();
        SoapyRPCUnpacker unpackerHangup(s);
    }
    catch (const std::exception &ex)
    {
        SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyRemote::find() -- transact FAIL: %s", ex.what());
    }

    //remove instances of the stop key from the result
    for (auto &resultArgs : result)
    {
        resultArgs.erase(SOAPY_REMOTE_KWARG_STOP);
        if (resultArgs.count("driver") != 0)
        {
            resultArgs["remote:driver"] = resultArgs.at("driver");
            resultArgs.erase("driver");
        }
        if (resultArgs.count("type") != 0)
        {
            resultArgs["remote:type"] = resultArgs.at("type");
            resultArgs.erase("type");
        }
        resultArgs["remote"] = url.toString();
    }

    return result;
}
Exemple #6
0
std::vector<SDRDeviceInfo *> *SDREnumerator::enumerate_devices(std::string remoteAddr, bool noInit) {

    if (SDREnumerator::devs[remoteAddr].size()) {
        return &SDREnumerator::devs[remoteAddr];
    }
    
    if (noInit) {
        return NULL;
    }
    
    if (!soapy_initialized) {
        std::cout << "SoapySDR init.." << std::endl;
        std::cout << "\tAPI Version: v" << SoapySDR::getAPIVersion() << std::endl;
        std::cout << "\tABI Version: v" << SoapySDR::getABIVersion() << std::endl;
        std::cout << "\tInstall root: " << SoapySDR::getRootPath() << std::endl;
        
        std::cout << "\tLoading modules... " << std::endl;
        
        std::string userModPath = wxGetApp().getModulePath();
        
        if (userModPath != "") {
            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Loading SoapySDR modules from " + userModPath + "..");
            std::vector<std::string> localMods = SoapySDR::listModules(userModPath);
            for (std::vector<std::string>::iterator mods_i = localMods.begin(); mods_i != localMods.end(); mods_i++) {
                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Initializing user specified SoapySDR module " + (*mods_i) + "..");
                std::cout << "Initializing user specified SoapySDR module " << (*mods_i) <<  ".." << std::endl;
                SoapySDR::loadModule(*mods_i);
            }
        } else {
            #ifdef BUNDLE_SOAPY_MODS
            bool localModPref = wxGetApp().getUseLocalMod();
            if (localModPref) {
                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Loading SoapySDR modules..");
                std::cout << "Checking local system SoapySDR modules.." << std::flush;
                SoapySDR::loadModules();
            }

            wxFileName exePath = wxFileName(wxStandardPaths::Get().GetExecutablePath());
            std::vector<std::string> localMods = SoapySDR::listModules(exePath.GetPath().ToStdString() + "/modules/");
            for (std::vector<std::string>::iterator mods_i = localMods.begin(); mods_i != localMods.end(); mods_i++) {
                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Initializing bundled SoapySDR module " + (*mods_i) + "..");
                std::cout << "Loading bundled SoapySDR module " << (*mods_i) <<  ".." << std::endl;
                SoapySDR::loadModule(*mods_i);
            }
        
            if (!localModPref) {
                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Loading SoapySDR modules..");
                std::cout << "Checking system SoapySDR modules.." << std::flush;
                SoapySDR::loadModules();
            }
            #else
            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Loading SoapySDR modules..");
            SoapySDR::loadModules();
            #endif

        }
        
        if (SDREnumerator::factories.size()) {
            SDREnumerator::factories.erase(SDREnumerator::factories.begin(), SDREnumerator::factories.end());
        }
        
        std::cout << "\tAvailable factories...";
        SoapySDR::FindFunctions factories = SoapySDR::Registry::listFindFunctions();
        for (SoapySDR::FindFunctions::const_iterator it = factories.begin(); it != factories.end(); ++it) {
            if (it != factories.begin()) {
                std::cout << ", ";
            }
            std::cout << it->first;
            
            if (it->first == "remote") {
                has_remote = true;
            }
            SDREnumerator::factories.push_back(it->first);
        }
        if (factories.empty()) {
            std::cout << "No factories found!" << std::endl;
        }
        if ((factories.size() == 1) && factories.find("null") != factories.end()) {
            std::cout << "Just 'null' factory found." << std::endl;
            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_FAILED, std::string("No modules available."));
        }
        std::cout << std::endl;
        soapy_initialized = true;
    }
    
    modules = SoapySDR::listModules();

    std::vector<SoapySDR::Kwargs> results;
    SoapySDR::Kwargs enumArgs;
    bool isRemote = false;
    
    if (remoteAddr.length()) {
        std::cout << "Enumerating remote address: " << remoteAddr << std::endl;
        enumArgs["driver"] = "remote";
        enumArgs["remote"] = remoteAddr;
        isRemote = true;
        
        results = SoapySDR::Device::enumerate(enumArgs);
    } else {
        results = SoapySDR::Device::enumerate();
    }
    
    int manualsIdx = results.size();
    std::vector<std::string> manualParams;
    std::vector<bool> manualResult;
    
    if (manuals.size()) {
        for (std::vector<SDRManualDef>::const_iterator m_i = manuals.begin(); m_i != manuals.end(); m_i++) {
            std::vector<SoapySDR::Kwargs> manual_result;

            std::string strDevArgs = "driver="+m_i->factory+","+m_i->params;
            
            manualParams.push_back(m_i->params);
            
            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Enumerating manual device '") + strDevArgs + "'..");

            manual_result = SoapySDR::Device::enumerate(strDevArgs);
            
            if (manual_result.size()) {
                for (std::vector<SoapySDR::Kwargs>::const_iterator i = manual_result.begin(); i != manual_result.end(); i++) {
                    results.push_back(*i);
                    manualResult.push_back(true);
                }
            } else {
                SoapySDR::Kwargs failedEnum;
                failedEnum = argsStrToKwargs(strDevArgs);
                failedEnum["label"] = "Not Found ("+m_i->factory+")";
                results.push_back(failedEnum);
                manualResult.push_back(false);
            }
        }
    }
    
    if (isRemote) {
        wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Opening remote server ") + remoteAddr + "..");
    }
    for (size_t i = 0; i < results.size(); i++) {
        SDRDeviceInfo *dev = new SDRDeviceInfo();
        
        SoapySDR::Kwargs deviceArgs = results[i];

        for (SoapySDR::Kwargs::const_iterator it = deviceArgs.begin(); it != deviceArgs.end(); ++it) {
            std::cout << "  " << it->first << " = " << it->second << std::endl;
            if (it->first == "driver") {
                dev->setDriver(it->second);
            } else if (it->first == "label" || it->first == "device") {
                dev->setName(it->second);
			}
        }
        
        
        if (deviceArgs.count("remote")) {
            isRemote = true;
        } else {
            isRemote = false;
        }
        
        dev->setRemote(isRemote);
        dev->setManual(i>=manualsIdx);
        if (i>=manualsIdx) {
            dev->setManualParams(manualParams[i-manualsIdx]);
        }
        
        std::cout << "Make device " << i << std::endl;
        if (i<manualsIdx || manualResult[i-manualsIdx]) try {
            SoapySDR::Device *device = SoapySDR::Device::make(deviceArgs);
            SoapySDR::Kwargs info = device->getHardwareInfo();
            for (SoapySDR::Kwargs::const_iterator it = info.begin(); it != info.end(); ++it) {
                std::cout << "  " << it->first << "=" << it->second << std::endl;
                if (it->first == "hardware") {
                    dev->setHardware(it->second);
                }
            }
            
            if (isRemote) {
                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Querying remote " + remoteAddr + " device #" + std::to_string(i) + ": " + dev-> getName());
            } else {
                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Querying device #") + std::to_string(i) + ": " + dev->getName());
            }

            SoapySDR::ArgInfoList settingsInfo = device->getSettingInfo();

            DeviceConfig *cfg = wxGetApp().getConfig()->getDevice(dev->getDeviceId());

            ConfigSettings devSettings = cfg->getSettings();
            if (devSettings.size()) {
                for (ConfigSettings::const_iterator set_i = devSettings.begin(); set_i != devSettings.end(); set_i++) {
                    deviceArgs[set_i->first] = set_i->second;
                }
                for (int j = 0; j < settingsInfo.size(); j++) {
                    if (deviceArgs.find(settingsInfo[j].key) != deviceArgs.end()) {
                        settingsInfo[j].value = deviceArgs[settingsInfo[j].key];
                    }
                }
            }
            
            dev->setDeviceArgs(deviceArgs);
            dev->setSettingsInfo(settingsInfo);

            int numChan = device->getNumChannels(SOAPY_SDR_RX);
            for (int i = 0; i < numChan; i++) {
                SDRDeviceChannel *chan = new SDRDeviceChannel();

                SoapySDR::RangeList rfRange = device->getFrequencyRange(SOAPY_SDR_RX, i);
                double rfMin = rfRange[0].minimum();
                double rfMax = rfRange[rfRange.size()-1].maximum();
                chan->setChannel(i);
                chan->setFullDuplex(device->getFullDuplex(SOAPY_SDR_RX, i));
                chan->setRx(true);
                chan->setTx(false);
                chan->getRFRange().setLow(rfMin);
                chan->getRFRange().setHigh(rfMax);

                std::vector<std::string> freqs = device->listFrequencies(SOAPY_SDR_RX,i);
                if (std::find(freqs.begin(), freqs.end(), "CORR") != freqs.end()) {
                    chan->setCORR(true);
                } else {
                    chan->setCORR(false);
                }
                
                if (device->hasDCOffsetMode(SOAPY_SDR_RX, i)) {
                    chan->setHardwareDC(true);
                } else {
                    chan->setHardwareDC(false);
                }
                
                std::vector<double> rates = device->listSampleRates(SOAPY_SDR_RX, i);
                for (std::vector<double>::iterator i = rates.begin(); i != rates.end(); i++) {
                    chan->getSampleRates().push_back((long)(*i));
                }
                
                ConfigSettings devStreamOpts = cfg->getStreamOpts();
                if (devStreamOpts.size()) {
                    dev->setStreamArgs(devStreamOpts);
                }
                
                SoapySDR::ArgInfoList optArgs = device->getStreamArgsInfo(SOAPY_SDR_RX, i);

                if (devStreamOpts.size()) {
                    for (int j = 0, jMax = optArgs.size(); j < jMax; j++) {
                        if (devStreamOpts.find(optArgs[j].key) != devStreamOpts.end()) {
                            optArgs[j].value = devStreamOpts[optArgs[j].key];
                        }
                    }
                }
                chan->setStreamArgsInfo(optArgs);
                
                std::vector<std::string> gainNames = device->listGains(SOAPY_SDR_RX, i);
                
                for (std::vector<std::string>::iterator gname = gainNames.begin(); gname!= gainNames.end(); gname++) {
                    chan->addGain((*gname),device->getGainRange(SOAPY_SDR_RX, i, (*gname)));
                }
                
                dev->addChannel(chan);
            }
            
            SoapySDR::Device::unmake(device);
            
            dev->setAvailable(true);
        } catch (const std::exception &ex) {
            std::cerr << "Error making device: " << ex.what() << std::endl;
            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Error querying device #") + std::to_string(i));
            dev->setAvailable(false);
        } else {
            dev->setAvailable(false);
        }
        std::cout << std::endl;

        SDREnumerator::devs[remoteAddr].push_back(dev);
    }
    if (SDREnumerator::devs[remoteAddr].empty()) {
        wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("No devices found!"));
    }
    std::cout << std::endl;

    return &SDREnumerator::devs[remoteAddr];
}
/***********************************************************************
 * Handler dispatcher implementation
 **********************************************************************/
bool SoapyClientHandler::handleOnce(SoapyRPCUnpacker &unpacker, SoapyRPCPacker &packer)
{
    SoapyRemoteCalls call;
    unpacker & call;

    switch (call)
    {

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_FIND:
    ////////////////////////////////////////////////////////////////////
    {
        SoapySDR::Kwargs args;
        unpacker & args;
        packer & SoapySDR::Device::enumerate(args);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_MAKE:
    ////////////////////////////////////////////////////////////////////
    {
        SoapySDR::Kwargs args;
        unpacker & args;
        std::lock_guard<std::mutex> lock(factoryMutex);
        if (_dev == nullptr) _dev = SoapySDR::Device::make(args);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_UNMAKE:
    ////////////////////////////////////////////////////////////////////
    {
        std::lock_guard<std::mutex> lock(factoryMutex);
        if (_dev != nullptr) SoapySDR::Device::unmake(_dev);
        _dev = nullptr;
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_HANGUP:
    ////////////////////////////////////////////////////////////////////
    {
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_SERVER_ID:
    ////////////////////////////////////////////////////////////////////
    {
        packer & uniqueProcessId();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_START_LOG_FORWARDING:
    ////////////////////////////////////////////////////////////////////
    {
        if (_logForwarder == nullptr) _logForwarder = new SoapyLogForwarder(_sock);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_STOP_LOG_FORWARDING:
    ////////////////////////////////////////////////////////////////////
    {
        if (_logForwarder != nullptr) delete _logForwarder;
        _logForwarder = nullptr;
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_DRIVER_KEY:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->getDriverKey();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_HARDWARE_KEY:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->getHardwareKey();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_HARDWARE_INFO:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->getHardwareInfo();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_FRONTEND_MAPPING:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        std::string mapping;
        unpacker & direction;
        unpacker & mapping;
        _dev->setFrontendMapping(direction, mapping);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_FRONTEND_MAPPING:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        unpacker & direction;
        packer & _dev->getFrontendMapping(direction);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_NUM_CHANNELS:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        unpacker & direction;
        packer & int(_dev->getNumChannels(direction));
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_FULL_DUPLEX:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getFullDuplex(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SETUP_STREAM:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        std::string format;
        std::vector<size_t> channels;
        SoapySDR::Kwargs args;
        std::string clientBindPort;
        std::string statusBindPort;
        unpacker & direction;
        unpacker & format;
        unpacker & channels;
        unpacker & args;
        unpacker & clientBindPort;
        unpacker & statusBindPort;

        //parse args for buffer configuration
        size_t mtu = SOAPY_REMOTE_DEFAULT_ENDPOINT_MTU;
        const auto mtuIt = args.find(SOAPY_REMOTE_KWARG_MTU);
        if (mtuIt != args.end()) mtu = size_t(std::stod(mtuIt->second));

        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));

        double priority = SOAPY_REMOTE_DEFAULT_THREAD_PRIORITY;
        const auto priorityIt = args.find(SOAPY_REMOTE_KWARG_PRIORITY);
        if (priorityIt != args.end()) priority = std::stod(priorityIt->second);

        //create stream
        auto stream = _dev->setupStream(direction, format, channels, args);

        //load data structure
        auto &data = _streamData[_nextStreamId];
        data.streamId = _nextStreamId++;
        data.device = _dev;
        data.stream = stream;
        data.format = format;
        for (const auto chan : channels) data.chanMask |= (1 << chan);
        data.priority = priority;

        //extract socket node information
        std::string serverBindPort;
        std::string localNode, remoteNode;
        std::string scheme, node, service;
        splitURL(_sock.getsockname(), scheme, localNode, service);
        splitURL(_sock.getpeername(), scheme, remoteNode, service);

        //bind the stream socket to an automatic port
        const auto bindURL = combineURL("udp", localNode, "0");
        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());
        splitURL(data.streamSock.getsockname(), scheme, node, serverBindPort);

        //connect the stream socket to the specified port
        auto connectURL = combineURL("udp", remoteNode, clientBindPort);
        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 = combineURL("udp", remoteNode, statusBindPort);
        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());

        //create endpoint
        data.endpoint = new SoapyStreamEndpoint(data.streamSock, data.statusSock,
            direction == SOAPY_SDR_TX, channels.size(), formatToSize(format), mtu, window);

        //start worker thread, this is not backwards,
        //receive from device means using a send endpoint
        //transmit to device means using a recv endpoint
        if (direction == SOAPY_SDR_RX) data.startSendThread();
        if (direction == SOAPY_SDR_TX) data.startRecvThread();
        data.startStatThread();

        packer & data.streamId;
        packer & serverBindPort;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_CLOSE_STREAM:
    ////////////////////////////////////////////////////////////////////
    {
        int streamId = 0;
        unpacker & streamId;

        //cleanup data and stop worker thread
        auto &data = _streamData.at(streamId);
        data.stopThreads();
        _dev->closeStream(data.stream);
        _streamData.erase(streamId);

        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_ACTIVATE_STREAM:
    ////////////////////////////////////////////////////////////////////
    {
        int streamId = 0;
        int flags = 0;
        long long timeNs = 0;
        int numElems = 0;
        unpacker & streamId;
        unpacker & flags;
        unpacker & timeNs;
        unpacker & numElems;

        auto &data = _streamData.at(streamId);
        packer & _dev->activateStream(data.stream, flags, timeNs, size_t(numElems));
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_DEACTIVATE_STREAM:
    ////////////////////////////////////////////////////////////////////
    {
        int streamId = 0;
        int flags = 0;
        long long timeNs = 0;
        unpacker & streamId;
        unpacker & flags;
        unpacker & timeNs;

        auto &data = _streamData.at(streamId);
        packer & _dev->deactivateStream(data.stream, flags, timeNs);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_ANTENNAS:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->listAntennas(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_ANTENNA:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::string name;
        unpacker & direction;
        unpacker & channel;
        unpacker & name;
        _dev->setAntenna(direction, channel, name);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_ANTENNA:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getAntenna(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_HAS_DC_OFFSET_MODE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->hasDCOffsetMode(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_DC_OFFSET_MODE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        bool automatic = false;
        unpacker & direction;
        unpacker & channel;
        unpacker & automatic;
        _dev->setDCOffsetMode(direction, channel, automatic);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_DC_OFFSET_MODE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getDCOffsetMode(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_HAS_DC_OFFSET:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->hasDCOffset(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_DC_OFFSET:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::complex<double> offset;
        unpacker & direction;
        unpacker & channel;
        unpacker & offset;
        _dev->setDCOffset(direction, channel, offset);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_DC_OFFSET:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getDCOffset(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_HAS_IQ_BALANCE_MODE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->hasIQBalance(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_IQ_BALANCE_MODE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::complex<double> balance;
        unpacker & direction;
        unpacker & channel;
        unpacker & balance;
        _dev->setIQBalance(direction, channel, balance);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_IQ_BALANCE_MODE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getIQBalance(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_GAINS:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->listGains(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_GAIN_MODE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        bool automatic = false;
        unpacker & direction;
        unpacker & channel;
        unpacker & automatic;
        _dev->setGainMode(direction, channel, automatic);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_GAIN_MODE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getGainMode(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_GAIN:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        double value = 0;
        unpacker & direction;
        unpacker & channel;
        unpacker & value;
        _dev->setGain(direction, channel, value);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_GAIN_ELEMENT:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::string name;
        double value = 0;
        unpacker & direction;
        unpacker & channel;
        unpacker & name;
        unpacker & value;
        _dev->setGain(direction, channel, name, value);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_GAIN:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getGain(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_GAIN_ELEMENT:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::string name;
        unpacker & direction;
        unpacker & channel;
        unpacker & name;
        packer & _dev->getGain(direction, channel, name);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_GAIN_RANGE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getGainRange(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_GAIN_RANGE_ELEMENT:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::string name;
        unpacker & direction;
        unpacker & channel;
        unpacker & name;
        packer & _dev->getGainRange(direction, channel, name);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_FREQUENCY:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        double value = 0;
        SoapySDR::Kwargs args;
        unpacker & direction;
        unpacker & channel;
        unpacker & value;
        unpacker & args;
        _dev->setFrequency(direction, channel, value, args);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_FREQUENCY_COMPONENT:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::string name;
        double value = 0;
        SoapySDR::Kwargs args;
        unpacker & direction;
        unpacker & channel;
        unpacker & name;
        unpacker & value;
        unpacker & args;
        _dev->setFrequency(direction, channel, name, value, args);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_FREQUENCY:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getFrequency(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_FREQUENCY_COMPONENT:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::string name;
        unpacker & direction;
        unpacker & channel;
        unpacker & name;
        packer & _dev->getFrequency(direction, channel, name);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_FREQUENCIES:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->listFrequencies(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_FREQUENCY_RANGE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getFrequencyRange(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_FREQUENCY_RANGE_COMPONENT:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::string name;
        unpacker & direction;
        unpacker & channel;
        unpacker & name;
        packer & _dev->getFrequencyRange(direction, channel, name);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_SAMPLE_RATE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        double rate = 0;
        unpacker & direction;
        unpacker & channel;
        unpacker & rate;
        _dev->setSampleRate(direction, channel, rate);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_SAMPLE_RATE:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getSampleRate(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_SAMPLE_RATES:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->listSampleRates(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_BANDWIDTH:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        double bw = 0;
        unpacker & direction;
        unpacker & channel;
        unpacker & bw;
        _dev->setBandwidth(direction, channel, bw);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_BANDWIDTH:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->getBandwidth(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_BANDWIDTHS:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->listBandwidths(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_MASTER_CLOCK_RATE:
    ////////////////////////////////////////////////////////////////////
    {
        double rate = 0;
        unpacker & rate;
        _dev->setMasterClockRate(rate);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_MASTER_CLOCK_RATE:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->getMasterClockRate();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_CLOCK_SOURCES:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->listClockSources();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_CLOCK_SOURCE:
    ////////////////////////////////////////////////////////////////////
    {
        std::string source;
        unpacker & source;
        _dev->setClockSource(source);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_CLOCK_SOURCE:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->getClockSource();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_TIME_SOURCES:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->listTimeSources();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_TIME_SOURCE:
    ////////////////////////////////////////////////////////////////////
    {
        std::string source;
        unpacker & source;
        _dev->setTimeSource(source);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_TIME_SOURCE:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->getTimeSource();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_HAS_HARDWARE_TIME:
    ////////////////////////////////////////////////////////////////////
    {
        std::string what;
        unpacker & what;
        packer & _dev->hasHardwareTime(what);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_GET_HARDWARE_TIME:
    ////////////////////////////////////////////////////////////////////
    {
        std::string what;
        unpacker & what;
        packer & _dev->getHardwareTime(what);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_HARDWARE_TIME:
    ////////////////////////////////////////////////////////////////////
    {
        long long timeNs = 0;
        std::string what;
        unpacker & timeNs;
        unpacker & what;
        _dev->setHardwareTime(timeNs, what);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_SET_COMMAND_TIME:
    ////////////////////////////////////////////////////////////////////
    {
        long long timeNs = 0;
        std::string what;
        unpacker & timeNs;
        unpacker & what;
        _dev->setCommandTime(timeNs, what);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_SENSORS:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->listSensors();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_READ_SENSOR:
    ////////////////////////////////////////////////////////////////////
    {
        std::string name;
        unpacker & name;
        packer & _dev->readSensor(name);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_CHANNEL_SENSORS:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        unpacker & direction;
        unpacker & channel;
        packer & _dev->listSensors(direction, channel);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_READ_CHANNEL_SENSOR:
    ////////////////////////////////////////////////////////////////////
    {
        char direction = 0;
        int channel = 0;
        std::string name;
        unpacker & direction;
        unpacker & channel;
        unpacker & name;
        packer & _dev->readSensor(direction, channel, name);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_WRITE_REGISTER:
    ////////////////////////////////////////////////////////////////////
    {
        int addr = 0;
        int value = 0;
        unpacker & addr;
        unpacker & value;
        _dev->writeRegister(unsigned(addr), unsigned(value));
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_READ_REGISTER:
    ////////////////////////////////////////////////////////////////////
    {
        int addr = 0;
        unpacker & addr;
        packer & int(_dev->readRegister(unsigned(addr)));
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_WRITE_SETTING:
    ////////////////////////////////////////////////////////////////////
    {
        std::string key;
        std::string value;
        unpacker & key;
        unpacker & value;
        _dev->writeSetting(key, value);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_READ_SETTING:
    ////////////////////////////////////////////////////////////////////
    {
        std::string key;
        unpacker & key;
        packer & _dev->readSetting(key);
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_GPIO_BANKS:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->listGPIOBanks();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_WRITE_GPIO:
    ////////////////////////////////////////////////////////////////////
    {
        std::string bank;
        int value = 0;
        unpacker & bank;
        unpacker & value;
        _dev->writeGPIO(bank, unsigned(value));
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_WRITE_GPIO_MASKED:
    ////////////////////////////////////////////////////////////////////
    {
        std::string bank;
        int value = 0;
        int mask = 0;
        unpacker & bank;
        unpacker & value;
        unpacker & mask;
        _dev->writeGPIO(bank, unsigned(value), unsigned(mask));
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_READ_GPIO:
    ////////////////////////////////////////////////////////////////////
    {
        std::string bank;
        unpacker & bank;
        packer & int(_dev->readGPIO(bank));
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_WRITE_GPIO_DIR:
    ////////////////////////////////////////////////////////////////////
    {
        std::string bank;
        int dir = 0;
        unpacker & bank;
        unpacker & dir;
        _dev->writeGPIODir(bank, unsigned(dir));
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_WRITE_GPIO_DIR_MASKED:
    ////////////////////////////////////////////////////////////////////
    {
        std::string bank;
        int dir = 0;
        int mask = 0;
        unpacker & bank;
        unpacker & dir;
        unpacker & mask;
        _dev->writeGPIODir(bank, unsigned(dir), unsigned(mask));
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_READ_GPIO_DIR:
    ////////////////////////////////////////////////////////////////////
    {
        std::string bank;
        unpacker & bank;
        packer & int(_dev->readGPIODir(bank));
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_WRITE_I2C:
    ////////////////////////////////////////////////////////////////////
    {
        int addr = 0;
        std::string data;
        unpacker & addr;
        unpacker & data;
        _dev->writeI2C(addr, data);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_READ_I2C:
    ////////////////////////////////////////////////////////////////////
    {
        int addr = 0;
        int numBytes = 0;
        unpacker & addr;
        unpacker & numBytes;
        packer & _dev->readI2C(addr, unsigned(numBytes));
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_TRANSACT_SPI:
    ////////////////////////////////////////////////////////////////////
    {
        int addr = 0;
        int data = 0;
        int numBits = 0;
        unpacker & addr;
        unpacker & data;
        unpacker & numBits;
        packer & int(_dev->transactSPI(addr, unsigned(data), size_t(numBits)));
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_LIST_UARTS:
    ////////////////////////////////////////////////////////////////////
    {
        packer & _dev->listUARTs();
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_WRITE_UART:
    ////////////////////////////////////////////////////////////////////
    {
        std::string which;
        std::string data;
        unpacker & which;
        unpacker & data;
        _dev->writeUART(which, data);
        packer & SOAPY_REMOTE_VOID;
    } break;

    ////////////////////////////////////////////////////////////////////
    case SOAPY_REMOTE_READ_UART:
    ////////////////////////////////////////////////////////////////////
    {
        std::string which;
        int timeoutUs = 0;
        unpacker & which;
        unpacker & timeoutUs;
        packer & _dev->readUART(which, long(timeoutUs));
    } break;

    default: throw std::runtime_error(
        "SoapyClientHandler::handleOnce("+std::to_string(int(call))+") unknown call");
    }

    return call != SOAPY_REMOTE_HANGUP;
}