Example #1
0
void CubicSDR::setDevice(SDRDeviceInfo *dev) {
    if (!sdrThread->isTerminated()) {
        sdrThread->terminate();
        if (t_SDR) {
            t_SDR->join();
            delete t_SDR;
        }
    }
    
    for (SoapySDR::Kwargs::const_iterator i = settingArgs.begin(); i != settingArgs.end(); i++) {
        sdrThread->writeSetting(i->first, i->second);
    }
    sdrThread->setStreamArgs(streamArgs);
    sdrThread->setDevice(dev);
    
    DeviceConfig *devConfig = config.getDevice(dev->getDeviceId());
    
    SoapySDR::Device *soapyDev = dev->getSoapyDevice();
    
    if (soapyDev) {
        if (long devSampleRate = devConfig->getSampleRate()) {
            sampleRate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, devSampleRate);
            sampleRateInitialized.store(true);
        }
        
        if (!sampleRateInitialized.load()) {
            sampleRate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, DEFAULT_SAMPLE_RATE);
            sampleRateInitialized.store(true);
        } else {
            sampleRate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, sampleRate);
        }

        if (frequency < sampleRate/2) {
            frequency = sampleRate/2;
        }

        setFrequency(frequency);
        setSampleRate(sampleRate);

        setPPM(devConfig->getPPM());
        setOffset(devConfig->getOffset());
        

        if (devConfig->getAGCMode()) {
            setAGCMode(true);
        } else {
            setAGCMode(false);
        }

        t_SDR = new std::thread(&SDRThread::threadMain, sdrThread);
}
    
    stoppedDev = nullptr;
}
Example #2
0
void CubicSDR::setDevice(int deviceId) {
    sdrThread->setDeviceId(deviceId);
    SDRThreadCommand command(SDRThreadCommand::SDR_THREAD_CMD_SET_DEVICE);
    command.llong_value = deviceId;
    pipeSDRCommand->push(command);

    SDRDeviceInfo *dev = (*getDevices())[deviceId];
    DeviceConfig *devConfig = config.getDevice(dev->getDeviceId());

    setPPM(devConfig->getPPM());
    setDirectSampling(devConfig->getDirectSampling());
    setSwapIQ(devConfig->getIQSwap());
    setOffset(devConfig->getOffset());
}
Example #3
0
void CubicSDR::setDevice(SDRDeviceInfo *dev) {
    if (!sdrThread->isTerminated()) {
        sdrThread->terminate();
        if (t_SDR) {
            t_SDR->join();
            delete t_SDR;
        }
    }
    
    for (SoapySDR::Kwargs::const_iterator i = settingArgs.begin(); i != settingArgs.end(); i++) {
        sdrThread->writeSetting(i->first, i->second);
    }
    sdrThread->setStreamArgs(streamArgs);
    sdrThread->setDevice(dev);
    
    DeviceConfig *devConfig = config.getDevice(dev->getDeviceId());
    
    SoapySDR::Device *soapyDev = dev->getSoapyDevice();
    
    if (soapyDev) {
        //long long freqHigh, freqLow;
        
        //SoapySDR::RangeList freqRange = soapyDev->getFrequencyRange(SOAPY_SDR_RX, 0);
        
        //freqLow = freqRange[0].minimum();
        //freqHigh = freqRange[freqRange.size()-1].maximum();

        // Try for a reasonable default sample rate.
        if (!sampleRateInitialized.load()) {
            sampleRate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, DEFAULT_SAMPLE_RATE);
            sampleRateInitialized.store(true);
        } else {
            sampleRate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, sampleRate);
        }

        if (frequency < sampleRate/2) {
            frequency = sampleRate/2;
        }

        setFrequency(frequency);
        setSampleRate(sampleRate);

        setPPM(devConfig->getPPM());
        setOffset(devConfig->getOffset());

        t_SDR = new std::thread(&SDRThread::threadMain, sdrThread);
    }
}
Example #4
0
void SDRThread::init() {
//#warning Debug On
//    SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);
    
    SDRDeviceInfo *devInfo = deviceInfo.load();
    deviceConfig.store(wxGetApp().getConfig()->getDevice(devInfo->getDeviceId()));
    DeviceConfig *devConfig = deviceConfig.load();
    
    ppm.store(devConfig->getPPM());
    ppm_changed.store(true);
    
    std::string driverName = devInfo->getDriver();

    offset = devConfig->getOffset();
    
    SoapySDR::Kwargs args = devInfo->getDeviceArgs();
    
    wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Initializing device."));
    
    device = devInfo->getSoapyDevice();
    
    SoapySDR::Kwargs currentStreamArgs = combineArgs(devInfo->getStreamArgs(),streamArgs);
    stream = device->setupStream(SOAPY_SDR_RX,"CF32", std::vector<size_t>(), currentStreamArgs);
    
    int streamMTU = device->getStreamMTU(stream);
    mtuElems.store(streamMTU);
    
    std::cout << "Stream MTU: " << mtuElems.load() << std::endl << std::flush;
    
    deviceInfo.load()->setStreamArgs(currentStreamArgs);
    deviceConfig.load()->setStreamOpts(currentStreamArgs);
    
    wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Activating stream."));
    device->setSampleRate(SOAPY_SDR_RX,0,sampleRate.load());
    device->setFrequency(SOAPY_SDR_RX,0,"RF",frequency - offset.load());
    device->activateStream(stream);
    if (devInfo->hasCORR(SOAPY_SDR_RX, 0)) {
        hasPPM.store(true);
        device->setFrequency(SOAPY_SDR_RX,0,"CORR",ppm.load());
    } else {
        hasPPM.store(false);
    }
    if (device->hasDCOffsetMode(SOAPY_SDR_RX, 0)) {
        hasHardwareDC.store(true);
//        wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Found hardware DC offset correction support, internal disabled."));
        device->setDCOffsetMode(SOAPY_SDR_RX, 0, true);
    } else {
        hasHardwareDC.store(false);
    }
    
    device->setGainMode(SOAPY_SDR_RX,0,agc_mode.load());
    
    numChannels.store(getOptimalChannelCount(sampleRate.load()));
    numElems.store(getOptimalElementCount(sampleRate.load(), 30));
    if (!mtuElems.load()) {
        mtuElems.store(numElems.load());
    }
    inpBuffer.data.resize(numElems.load());
    overflowBuffer.data.resize(mtuElems.load());
    
    buffs[0] = malloc(mtuElems.load() * 4 * sizeof(float));
    numOverflow = 0;
    
    SoapySDR::ArgInfoList settingsInfo = device->getSettingInfo();
    SoapySDR::ArgInfoList::const_iterator settings_i;
    
    if (!setting_value_changed.load()) {
        settings.erase(settings.begin(), settings.end());
        settingChanged.erase(settingChanged.begin(), settingChanged.end());
    }
    
    { //enter scoped-lock
        std::lock_guard < std::mutex > lock(setting_busy);

        for (settings_i = settingsInfo.begin(); settings_i != settingsInfo.end(); settings_i++) {
            SoapySDR::ArgInfo setting = (*settings_i);
            if ((settingChanged.find(setting.key) != settingChanged.end()) && (settings.find(setting.key) != settings.end())) {
                device->writeSetting(setting.key, settings[setting.key]);
                settingChanged[setting.key] = false;
            } else {
                settings[setting.key] = device->readSetting(setting.key);
                settingChanged[setting.key] = false;
            }
        }
        setting_value_changed.store(false);

    } //leave lock guard scope
    
    updateSettings();
    
    wxGetApp().sdrThreadNotify(SDRThread::SDR_THREAD_INITIALIZED, std::string("Device Initialized."));
}
Example #5
0
void SDRDevicesDialog::refreshDeviceProperties() {

    SDRDeviceInfo *selDev = getSelectedDevice(devTree->GetSelection());
    if (selDev && selDev->isAvailable()) {
        dev = selDev;
        selId = devTree->GetSelection();
        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getName());
        m_propertyGrid->Clear();
        
        SoapySDR::Device *soapyDev = dev->getSoapyDevice();
        SoapySDR::ArgInfoList args = soapyDev->getSettingInfo();
        
        //A) General settings: name, offset, sample rate, agc, antennas (if > 1) 
        m_propertyGrid->Append(new wxPropertyCategory("General Settings"));
        
        devSettings.clear();

        //A-1) Name
        devSettings["name"] = m_propertyGrid->Append( new wxStringProperty("Name", wxPG_LABEL, devConfig->getDeviceName()) );
        //A-2) Offset
        devSettings["offset"] = m_propertyGrid->Append( new wxIntProperty("Offset (KHz)", wxPG_LABEL, devConfig->getOffset() / 1000) );
        
        //A-3) Antennas, is there are more than 1 RX antenna, else do not expose the setting.
        //get the saved setting
        const std::string& currentSetAntenna = devConfig->getAntennaName();

        //compare to the list of existing antennas
        SoapySDR::ArgInfo antennasArg;
        std::vector<std::string> antennaOpts = selDev->getAntennaNames(SOAPY_SDR_RX, 0);

        //only do something if there is more than 1 antenna
        if (antennaOpts.size() > 1) {

            //by default, choose the first of the list.
            std::string antennaToSelect = antennaOpts.front();

            auto found_i = std::find(antennaOpts.begin(), antennaOpts.end(), currentSetAntenna);

            if (found_i != antennaOpts.end()) {
                antennaToSelect = currentSetAntenna;
            }
            else {
                //erroneous antenna name, re-write device config with the first choice of teh list.
                devConfig->setAntennaName(antennaToSelect);
            }

            //build device settings
            for (std::string antenna : antennaOpts) {
                antennasArg.options.push_back(antenna);
                antennasArg.optionNames.push_back(antenna);
            }

            antennasArg.type = SoapySDR::ArgInfo::STRING;
            antennasArg.units = "";
            antennasArg.name = "Antenna";
            antennasArg.key = "antenna";
            antennasArg.value = antennaToSelect;

            devSettings["antenna"] = addArgInfoProperty(m_propertyGrid, antennasArg);
            deviceArgs["antenna"] = antennasArg;

        } //end if more than 1 antenna
        else {
            devConfig->setAntennaName("");
        }

        //A-4) Sample_rate:
        long currentSampleRate = wxGetApp().getSampleRate();
        long deviceSampleRate = devConfig->getSampleRate();
        
        if (!deviceSampleRate) {
            deviceSampleRate = selDev->getSampleRateNear(SOAPY_SDR_RX, 0, currentSampleRate);
        }
        
        SoapySDR::ArgInfo sampleRateArg;
        std::vector<long> rateOpts = selDev->getSampleRates(SOAPY_SDR_RX, 0);

        for (long rate : rateOpts) {
            sampleRateArg.options.push_back(std::to_string(rate));
            sampleRateArg.optionNames.push_back(frequencyToStr(rate));
        }
        
        sampleRateArg.type = SoapySDR::ArgInfo::STRING;
        sampleRateArg.units = "Hz";
        sampleRateArg.name = "Sample Rate";
        sampleRateArg.key = "sample_rate";
        sampleRateArg.value = std::to_string(deviceSampleRate);
        
        devSettings["sample_rate"] = addArgInfoProperty(m_propertyGrid, sampleRateArg);
        deviceArgs["sample_rate"] = sampleRateArg;

        

        //B) Runtime Settings:
        runtimeArgs.clear();
        runtimeProps.clear();
        streamProps.clear();
       
        if (args.size()) {
            m_propertyGrid->Append(new wxPropertyCategory("Run-time Settings"));
            
            for (SoapySDR::ArgInfoList::const_iterator args_i = args.begin(); args_i != args.end(); args_i++) {
                SoapySDR::ArgInfo arg = (*args_i);
				//We-reread the Device configuration, else we use the user settings.
				if (dev) {
					//Apply saved settings
					DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
					arg.value = devConfig->getSetting(arg.key, soapyDev->readSetting(arg.key)); //use SoapyDevice data as fallback.
				}
				else {
					//re-read the SoapyDevice
					arg.value = soapyDev->readSetting(arg.key);
				}
               
                runtimeProps[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
                runtimeArgs[arg.key] = arg;
            }
        }
        
        if (dev) {
            args = dev->getSoapyDevice()->getStreamArgsInfo(SOAPY_SDR_RX, 0);
            
            DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
            ConfigSettings devStreamOpts = devConfig->getStreamOpts();
            if (devStreamOpts.size()) {
                for (int j = 0, jMax = args.size(); j < jMax; j++) {
                    if (devStreamOpts.find(args[j].key) != devStreamOpts.end()) {
                        args[j].value = devStreamOpts[args[j].key];
                    }
                }
            }
            
            if (args.size()) {
                m_propertyGrid->Append(new wxPropertyCategory("Stream Settings"));
                
                for (SoapySDR::ArgInfo arg : args) {
                  
                    streamProps[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
                }
            }
        }
        
        if (selDev->isManual()) {
            m_addRemoteButton->SetLabel("Remove");
            removeId = selId;
        } else {
            m_addRemoteButton->SetLabel("Add");
            removeId = nullptr;
        }
        
    } else if (selDev && !selDev->isAvailable() && selDev->isManual()) {
        m_propertyGrid->Clear();

        devSettings.clear();
        runtimeArgs.clear();
        runtimeProps.clear();
        streamProps.clear();

        removeId = devTree->GetSelection();
        dev = nullptr;
        selId = nullptr;
        editId = nullptr;
        
        m_addRemoteButton->SetLabel("Remove");
    } else if (!selDev) {
        m_addRemoteButton->SetLabel("Add");
        removeId = nullptr;
    }
}
Example #6
0
void SDRDevicesDialog::OnSelectionChanged( wxTreeEvent& event ) {

    SDRDeviceInfo *selDev = getSelectedDevice(devTree->GetSelection());
    if (selDev && selDev->isAvailable()) {
        dev = selDev;
        selId = devTree->GetSelection();
        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getName());
        m_propertyGrid->Clear();

        SoapySDR::ArgInfoList args = dev->getSettingsArgInfo();
        SoapySDR::ArgInfoList::const_iterator args_i;

        m_propertyGrid->Append(new wxPropertyCategory("General Settings"));
        
        devSettings.erase(devSettings.begin(),devSettings.end());
        devSettings["name"] = m_propertyGrid->Append( new wxStringProperty("Name", wxPG_LABEL, devConfig->getDeviceName()) );
        devSettings["offset"] = m_propertyGrid->Append( new wxIntProperty("Offset (Hz)", wxPG_LABEL, devConfig->getOffset()) );

        props.erase(props.begin(), props.end());

        if (args.size()) {
            m_propertyGrid->Append(new wxPropertyCategory("Run-time Settings"));
            
            
            for (args_i = args.begin(); args_i != args.end(); args_i++) {
                SoapySDR::ArgInfo arg = (*args_i);
                props.push_back(addArgInfoProperty(m_propertyGrid, arg));
            }
        }
        
        if (dev->getRxChannel()) {
            args = dev->getRxChannel()->getStreamArgsInfo();
            
            DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
            ConfigSettings devStreamOpts = devConfig->getStreamOpts();
            if (devStreamOpts.size()) {
                for (int j = 0, jMax = args.size(); j < jMax; j++) {
                    if (devStreamOpts.find(args[j].key) != devStreamOpts.end()) {
                        args[j].value = devStreamOpts[args[j].key];
                    }
                }
            }

            if (args.size()) {
                m_propertyGrid->Append(new wxPropertyCategory("Stream Settings"));

                for (args_i = args.begin(); args_i != args.end(); args_i++) {
                    SoapySDR::ArgInfo arg = (*args_i);
                    props.push_back(addArgInfoProperty(m_propertyGrid, arg));
                }
            }
        }
        
        if (selDev->isManual()) {
            m_addRemoteButton->SetLabel("Remove");
            removeId = selId;
        } else {
            m_addRemoteButton->SetLabel("Add");
            removeId = nullptr;
        }
        
    } else if (selDev && !selDev->isAvailable() && selDev->isManual()) {
        m_propertyGrid->Clear();
        devSettings.erase(devSettings.begin(),devSettings.end());
        props.erase(props.begin(), props.end());
        removeId = devTree->GetSelection();
        dev = nullptr;
        selId = nullptr;
        editId = nullptr;
        
        m_addRemoteButton->SetLabel("Remove");
    } else if (!selDev) {
        m_addRemoteButton->SetLabel("Add");
        removeId = nullptr;
    }
    event.Skip();
}
Example #7
0
bool CubicSDR::OnInit() {
#ifdef _OSX_APP_
    CFBundleRef mainBundle = CFBundleGetMainBundle();
    CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
    char path[PATH_MAX];
    if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX))
    {
        // error!
    }
    CFRelease(resourcesURL);
    chdir(path);
#endif

    if (!wxApp::OnInit()) {
        return false;
    }

    wxApp::SetAppName("CubicSDR");

    frequency = wxGetApp().getConfig()->getCenterFreq();
    offset = 0;
    ppm = 0;
    directSamplingMode = 0;

    // Visual Data
    spectrumVisualThread = new SpectrumVisualDataThread();
    demodVisualThread = new SpectrumVisualDataThread();
    
    pipeIQVisualData = new DemodulatorThreadInputQueue();
    pipeIQVisualData->set_max_num_items(1);

    spectrumDistributor.setInput(pipeIQVisualData);
    
    pipeDemodIQVisualData = new DemodulatorThreadInputQueue();
    pipeDemodIQVisualData->set_max_num_items(1);
    
    pipeSpectrumIQVisualData = new DemodulatorThreadInputQueue();
    pipeSpectrumIQVisualData->set_max_num_items(1);
    
    pipeWaterfallIQVisualData = new DemodulatorThreadInputQueue();
    pipeWaterfallIQVisualData->set_max_num_items(128);
    
    spectrumDistributor.attachOutput(pipeDemodIQVisualData);
    spectrumDistributor.attachOutput(pipeSpectrumIQVisualData);
    
    getDemodSpectrumProcessor()->setInput(pipeDemodIQVisualData);
    getSpectrumProcessor()->setInput(pipeSpectrumIQVisualData);
    
    pipeAudioVisualData = new DemodulatorThreadOutputQueue();
    pipeAudioVisualData->set_max_num_items(1);
    
    scopeProcessor.setInput(pipeAudioVisualData);
    
    // I/Q Data
    pipeSDRIQData = new SDRThreadIQDataQueue();
    pipeSDRCommand = new SDRThreadCommandQueue();

    pipeSDRIQData->set_max_num_items(100);
    
    sdrThread = new SDRThread();
    sdrThread->setInputQueue("SDRCommandQueue",pipeSDRCommand);
    sdrThread->setOutputQueue("IQDataOutput",pipeSDRIQData);

    sdrPostThread = new SDRPostThread();
//    sdrPostThread->setNumVisSamples(BUF_SIZE);
    sdrPostThread->setInputQueue("IQDataInput", pipeSDRIQData);
    sdrPostThread->setOutputQueue("IQVisualDataOutput", pipeIQVisualData);
    sdrPostThread->setOutputQueue("IQDataOutput", pipeWaterfallIQVisualData);
    
    std::vector<SDRDeviceInfo *>::iterator devs_i;

    SDRThread::enumerate_rtl(&devs);
    SDRDeviceInfo *dev = NULL;

    if (devs.size() > 1) {
        wxArrayString choices;
        for (devs_i = devs.begin(); devs_i != devs.end(); devs_i++) {
            std::string devName = (*devs_i)->getName();
            if ((*devs_i)->isAvailable()) {
                devName.append(": ");
                devName.append((*devs_i)->getProduct());
                devName.append(" [");
                devName.append((*devs_i)->getSerial());
                devName.append("]");
            } else {
                devName.append(" (In Use?)");
            }
            choices.Add(devName);
        }

        int devId = wxGetSingleChoiceIndex(wxT("Devices"), wxT("Choose Input Device"), choices);        
        if (devId == -1) {  // User chose to cancel
            return false;
        }
        
        dev = devs[devId];

        sdrThread->setDeviceId(devId);
    } else if (devs.size() == 1) {
        dev = devs[0];
    }
    
    if (!dev) {
        wxMessageDialog *info;
        info = new wxMessageDialog(NULL, wxT("\x28\u256F\xB0\u25A1\xB0\uFF09\u256F\uFE35\x20\u253B\u2501\u253B"), wxT("RTL-SDR device not found"), wxOK | wxICON_ERROR);
        info->ShowModal();
        return false;
    }

    t_PostSDR = new std::thread(&SDRPostThread::threadMain, sdrPostThread);
    t_SDR = new std::thread(&SDRThread::threadMain, sdrThread);
    t_SpectrumVisual = new std::thread(&SpectrumVisualDataThread::threadMain, spectrumVisualThread);
    t_DemodVisual = new std::thread(&SpectrumVisualDataThread::threadMain, demodVisualThread);

    appframe = new AppFrame();
    if (dev != NULL) {
        appframe->initDeviceParams(dev->getDeviceId());
        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
        ppm = devConfig->getPPM();
        offset = devConfig->getOffset();
        directSamplingMode = devConfig->getDirectSampling();
    }

#ifdef __APPLE__
    int main_policy;
    struct sched_param main_param;

    main_policy = SCHED_RR;
    main_param.sched_priority = sched_get_priority_min(SCHED_RR)+2;

    pthread_setschedparam(pthread_self(), main_policy, &main_param);
#endif

    return true;
}
Example #8
0
void SDRDevicesDialog::refreshDeviceProperties() {
    SDRDeviceInfo *selDev = getSelectedDevice(devTree->GetSelection());
    if (selDev && selDev->isAvailable()) {
        dev = selDev;
        selId = devTree->GetSelection();
        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getName());
        m_propertyGrid->Clear();
        
        SoapySDR::Device *soapyDev = dev->getSoapyDevice();
        SoapySDR::ArgInfoList args = soapyDev->getSettingInfo();
        SoapySDR::ArgInfoList::const_iterator args_i;
        
        m_propertyGrid->Append(new wxPropertyCategory("General Settings"));
        
        devSettings.erase(devSettings.begin(),devSettings.end());
        devSettings["name"] = m_propertyGrid->Append( new wxStringProperty("Name", wxPG_LABEL, devConfig->getDeviceName()) );
        devSettings["offset"] = m_propertyGrid->Append( new wxIntProperty("Offset (Hz)", wxPG_LABEL, devConfig->getOffset()) );
        
        runtimeArgs.erase(runtimeArgs.begin(), runtimeArgs.end());
        runtimeProps.erase(runtimeProps.begin(), runtimeProps.end());
        streamProps.erase(streamProps.begin(), streamProps.end());
        
        if (args.size()) {
            m_propertyGrid->Append(new wxPropertyCategory("Run-time Settings"));
            
            for (args_i = args.begin(); args_i != args.end(); args_i++) {
                SoapySDR::ArgInfo arg = (*args_i);
                arg.value = soapyDev->readSetting(arg.key);
                runtimeProps[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
                runtimeArgs[arg.key] = arg;
            }
        }
        
        if (dev) {
            args = dev->getSoapyDevice()->getStreamArgsInfo(SOAPY_SDR_RX, 0);
            
            DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
            ConfigSettings devStreamOpts = devConfig->getStreamOpts();
            if (devStreamOpts.size()) {
                for (int j = 0, jMax = args.size(); j < jMax; j++) {
                    if (devStreamOpts.find(args[j].key) != devStreamOpts.end()) {
                        args[j].value = devStreamOpts[args[j].key];
                    }
                }
            }
            
            if (args.size()) {
                m_propertyGrid->Append(new wxPropertyCategory("Stream Settings"));
                
                for (args_i = args.begin(); args_i != args.end(); args_i++) {
                    SoapySDR::ArgInfo arg = (*args_i);
                    streamProps[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
                }
            }
        }
        
        if (selDev->isManual()) {
            m_addRemoteButton->SetLabel("Remove");
            removeId = selId;
        } else {
            m_addRemoteButton->SetLabel("Add");
            removeId = nullptr;
        }
        
    } else if (selDev && !selDev->isAvailable() && selDev->isManual()) {
        m_propertyGrid->Clear();
        devSettings.erase(devSettings.begin(),devSettings.end());
        runtimeArgs.erase(runtimeArgs.begin(), runtimeArgs.end());
        runtimeProps.erase(runtimeProps.begin(), runtimeProps.end());
        streamProps.erase(streamProps.begin(), streamProps.end());
        removeId = devTree->GetSelection();
        dev = nullptr;
        selId = nullptr;
        editId = nullptr;
        
        m_addRemoteButton->SetLabel("Remove");
    } else if (!selDev) {
        m_addRemoteButton->SetLabel("Add");
        removeId = nullptr;
    }
}