Can a trained model be converted to a Tensorflow Lite model?

I'm trying to convert a trained SSD7 model (trained on the bundled Udacity training set created by @pierluigiferrari) to a Tensorflow Lite (tflite, tf.lite) model. Has anyone tried and had success?
Here's what I'm using:
Ubuntu 16.04
Tensorflow 1.13.1
Keras 2.2.4
I removed the custom AnchorBoxes layers (just commented out the relevant code in the keras_ssd7.py file, and replaced the anchors_concat tensor in predictions  with two concatenated copies of the boxes_concat tensor, just as filler, since these not used by SSDLoss during training. [Edit addition on 6/7/19]: I also converted the ELU layers to ReLU layers. I then trained. I then built a new model with the build_model function, copied weights from the trained model, and saved that new model (thus excluding the loss function) with or without compiling first (as I'll explain momentarily).
I tried to convert the model with this, which includes compilation:
model.compile(optimizer=adam)
model.save('source_model_for_conversion.h5')
converter = tf.lite.TFLiteConverter.from_keras_model_file('source_model_for_conversion.h5')
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)

and alternatively with this, which excludes model compilation:
model.save('source_model_for_conversion.h5')
converter = tf.lite.TFLiteConverter.from_keras_model_file('source_model_for_conversion.h5')
tflite_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_model)

In the first version, in which I compile first, I get an error message from the from_keras_model_file() call, complaining about missing gradients:
WARNING:tensorflow:Output "predictions" missing from loss dictionary. We assume this was done on purpose. The fit and evaluate APIs will not be expecting any data to be passed to "predictions".

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-17-7814781bb395> in <module>
      3 #                                               'compute_loss': ssd_loss.compute_loss})
      4 # # converter = tf.lite.TFLiteConverter.from_keras_model_file(filename_model)
----> 5 converter = tf.lite.TFLiteConverter.from_keras_model_file('model_no_loss.h5')
      6 #flite_model = converter.convert()
      7 # open("converted_model.tflite", "wb").write(tflite_model)

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/lite/python/lite.py in from_keras_model_file(cls, model_file, input_arrays, input_shapes, output_arrays)
    368     _keras.backend.clear_session()
    369     _keras.backend.set_learning_phase(False)
--> 370     keras_model = _keras.models.load_model(model_file)
    371     sess = _keras.backend.get_session()
    372

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/python/keras/engine/saving.py in load_model(filepath, custom_objects, compile)
    273         # weights.
    274         if model._is_graph_network:  # pylint: disable=protected-access
--> 275           model._make_train_function()
    276           optimizer_weights_group = f['optimizer_weights']
    277           optimizer_weight_names = [

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py in _make_train_function(self)
   1917     ]
   1918     self._make_train_function_helper('train_function',
-> 1919                                      [self.total_loss] + metrics_tensors)
   1920
   1921   def _make_fit_function(self):

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py in _make_train_function_helper(self, fn_name, outputs, metric_updates)
   1893             # Training updates
   1894             updates = self.optimizer.get_updates(
-> 1895                 params=self._collected_trainable_weights, loss=self.total_loss)
   1896       # Unconditional updates
   1897       updates += self.get_updates_for(None)

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/python/keras/optimizers.py in get_updates(self, loss, params)
    483
    484   def get_updates(self, loss, params):
--> 485     grads = self.get_gradients(loss, params)
    486     self.updates = []
    487

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/python/keras/optimizers.py in get_gradients(self, loss, params)
     93     grads = K.gradients(loss, params)
     94     if None in grads:
---> 95       raise ValueError('An operation has `None` for gradient. '
     96                        'Please make sure that all of your ops have a '
     97                        'gradient defined (i.e. are differentiable). '

ValueError: An operation has `None` for gradient. Please make sure that all of your ops have a gradient defined (i.e. are differentiable). Common ops without gradient: K.argmax, K.round, K.eval.

In the second version, in which I do not compile the model, I get this error during the converter.convert() call.
WARNING:tensorflow:No training configuration found in save file: the model was *not* compiled. Compile it manually.
INFO:tensorflow:Froze 58 variables.
INFO:tensorflow:Converted 58 variables to const ops.

---------------------------------------------------------------------------
ConverterError                            Traceback (most recent call last)
<ipython-input-12-2bab112a9a26> in <module>
      4 # # converter = tf.lite.TFLiteConverter.from_keras_model_file(filename_model)
      5 converter = tf.lite.TFLiteConverter.from_keras_model_file('model_no_loss.h5')
----> 6 tflite_model = converter.convert()
      7 # open("converted_model.tflite", "wb").write(tflite_model)
      8

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/lite/python/lite.py in convert(self)
    453           input_tensors=self._input_tensors,
    454           output_tensors=self._output_tensors,
--> 455           **converter_kwargs)
    456     else:
    457       result = _toco_convert_graph_def(

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/lite/python/convert.py in toco_convert_impl(input_data, input_tensors, output_tensors, *args, **kwargs)
    440   data = toco_convert_protos(model_flags.SerializeToString(),
    441                              toco_flags.SerializeToString(),
--> 442                              input_data.SerializeToString())
    443   return data
    444

~/python_envs/env_ssd_keras/lib/python3.6/site-packages/tensorflow/lite/python/convert.py in toco_convert_protos(model_flags_str, toco_flags_str, input_data_str)
    203       stderr = _try_convert_to_unicode(stderr)
    204       raise ConverterError(
--> 205           "TOCO failed. See console for info.\n%s\n%s\n" % (stdout, stderr))
    206   finally:
    207     # Must manually cleanup files.

ConverterError: TOCO failed. See console for info.
Traceback (most recent call last):
  File "/usr/local/bin/toco_from_protos", line 7, in <module>
    from tensorflow.contrib.lite.toco.python.toco_from_protos import main
  File "/home/mroos/.local/lib/python3.6/site-packages/tensorflow/contrib/__init__.py", line 40, in <module>
    from tensorflow.contrib import distribute
  File "/home/mroos/.local/lib/python3.6/site-packages/tensorflow/contrib/distribute/__init__.py", line 27, in <module>
    from tensorflow.contrib.distribute.python.collective_all_reduce_strategy import CollectiveAllReduceStrategy
  File "/home/mroos/.local/lib/python3.6/site-packages/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py", line 29, in <module>
    class CollectiveAllReduceStrategy(distribute_lib.StrategyV1):
AttributeError: module 'tensorflow.python.distribute.distribute_lib' has no attribute 'StrategyV1'

I just converted it to tflite completely following your instructions. Additional I had to change the ELU activations in the convolutional layers to relu.
It worked with and without compiling the model. It was a complete new model so no training before.
I wil try to convert a trained model in a next step. Just wanted to share it with you and keep this thread active

Thanks for the update, @ogruhlke94! Can you share the versions you're using of the critical software (tensorflow, keras, cuda, etc.)?  I had switched from ELU to ReLU as well, but forgot to mention that in my original post.

Sure:
Ubuntu 18.04
Python 3.5
Tensorflow 1.13.1
Keras 2.2.4
No cuda because I'm only testing the conversion on my notebook without any gpu

Interesting. I doubt my different version of Python (3.6.7) or Ubuntu (16.04) is the problem, but I have access to an 18.04 machine, so will give it a try there in the next couple days and report back.

Is your training with the changes to the model working fine? The predicted boxes are really bad for me (negative values for the coordinates and max confidence of 0.05)

I haven't truly trained it to convergence. I just trained it a few epochs and then tried the conversion to tflite. I wanted to make sure the conversion was possible before working to train a high-performing model. Obviously the switch to ReLU from ELU could have an impact. @pierluigiferrari, do you have a sense of the importance of ELU layers versus ReLU layers?

I switched to a machine with Ubuntu 18.04 and then the conversion worked for my (that is, there were no errors, just warnings), as it did for @ogruhlke94. Both with and without model compilation. Still not clear why it didn't work on my 16.04 machine. I have not yet fully trained a model and measured the performance before or after conversion.

@mattroos is possible to share your edit on ssd7 for tflite?

hi @mattroos @ogruhlke94 have you try to convert this model for opencv?

Hi, I want to implement this in Flutter using tflite_flutter plugin, but it requires .tflite model outputs to be in this form:

in a working example .tflite file that is accepted by Flutter, the model outputs TFLite_Detection_PostProcess, TFLite_Detection_PostProcess:1, TFLite_Detection_PostProcess:2, TFLite_Detection_PostProcess:3 as seen below:

but when I see the outputs of the keras_ssd7.py in this repo, the output shape is different:
After conversion to .tflite:

Via Python (before conversion):
y_pred = model.predict(batch_images)
print('y_pred.shape is : '+ str(y_pred.shape))
>> y_pred.shape is : **(1, 7160, 20)**

Really appreciate if anyone could help, cheers!

Hi, I want to implement this in Flutter using tflite_flutter plugin, but it requires .tflite model outputs to be in this form: https://www.tensorflow.org/lite/models/object_detection/overview#output_signature
that form is the same with a working .tflite file that is accepted by Flutter, the model outputs TFLite_Detection_PostProcess, TFLite_Detection_PostProcess:1, TFLite_Detection_PostProcess:2, TFLite_Detection_PostProcess:3 as seen below:
but when I see the outputs of the keras_ssd7.py in this repo, the output shape is different:
After conversion to .tflite, the output shape is different, i.e. (1, 1, 20):
model_path = 'model_expw_ssd7_300x300_epoch-07_loss-3.2785_val_loss-3.6043.h5'
ssd_loss = SSDLoss(neg_pos_ratio=3, alpha=1.0)
K.clear_session() # Clear previous models from memory.
model = load_model(model_path, custom_objects={'AnchorBoxes': AnchorBoxes,
                                               'compute_loss': ssd_loss.compute_loss})
# Common conversion
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
with open('tflite_expw_300x300.tflite', 'wb') as f:
  f.write(tflite_model)

Via Python (before conversion):
y_pred = model.predict(batch_images)
print('y_pred.shape is : '+ str(y_pred.shape))
>> y_pred.shape is : **(1, 7160, 20)**

Is there any way to change the output to the appropriate shape? Really appreciate if anyone could help, cheers!

I know this can be done with the TensorFlow Object Detection API, but I would like to avoid adding a dependency to it. Did you find a way to do it?


Update tutorial.md

fix typo that included undefined G
Checklist

[x] Use a separate branch in your local repo (not master).
[x] Test coverage is 100% (or you have a story for why it's ok).

I signed it!

Coverage remained the same at 100.0% when pulling ca5c2e28ea1c3901b8c0e2ebe76bcba8d9da744b on jb-1980:master into d03d34c7c08314a9dc71c4e743a637b18bf05805 on google:master.

Thank you!


EIN Keybindings not working

I enabled and set up the EIN module. Most of it works fine but the default bindings don't seem to work.
I don't use the custom evil bindings that are provided with doom. I continuously face this issue of default bindings not working in modules.
Is this the intended behavior? Are there any workarounds, so that I can get the default emacs bindings globally. Currently, I have defined a lot of custom bindings in config.el but it is getting tedious to do for each new module.

If you are not an evil user, I recommend disabling the :editor evil module by commenting it out in your doom! block in ~/.doom.d/init.el. Remember to run bin/doom refresh to ensure everything is set up correctly before restarting Emacs.

I have done so, yet the default bindings of EIN still dont work. This happens for nearly every module, including the ones that come with doom.

Example C-c C-c for execute cell.

Ah, then it's possible this is a bug with the emacs keybinds. Try adding this to your ~/.doom.d/config.el:
(setq doom-leader-alt-key "C-c d")

Solved it!. Thanks a lot for bearing with me. I was stuck on it for so long. Especially for something so trivial. I am closing this now... Really made my day!


createProject > Project is created with "Consultant" user role

Describe the bug
createProject > Project is created with Consultant user role
Steps to Reproduce

Register the user with the Consultant role
Login
Click on Create New Item > Project > Enter project data
Click on the Submit button

Expected Result
Project should not be created with Consultant user role.
Actual Result
Project is created with Consultant user role
See video -  https://drive.google.com/file/d/1LtSKhH2BiTbZplpMrnPTxiqDJRmcPp20/view?usp=drivesdk
cord-field# 1772f10d

@iPiyushPatel are you saying consultants shouldn't be able to create projects?


Modify NodeJS codegen to fit with gcloud-node design.

The new style exports a function instead of the class. The
exported function will take the auth context as a parameter
and returns builder function.
Files will be under a sub directory with versioned name,
so that it can be exposed as a sub package of the API package.
A few misc changes and style changes made to pass lint
checker in gcloud.

The generated result is reviewed at https://github.com/GoogleCloudPlatform/gcloud-node/pull/1476 and probably some details will change later.

It seems the auto-generated code would be fine. @geigerj please take a look (@swcloud is added FYI)

Current coverage is 81.15% (diff: 80.00%)

Merging #392 into master will decrease coverage by <.01%

@@             master       #392   diff @@
==========================================
  Files           183        185     +2
  Lines          6904       6918    +14
  Methods           0          0
  Messages          0          0
  Branches        900        904     +4
==========================================
+ Hits           5603       5614    +11
- Misses         1026       1027     +1
- Partials        275        277     +2

Powered by Codecov. Last update f71215b...4bfde02

Modified the code a bit. Please take another look.
Here are the key points:
The API class is now not exposed and users are expected to create an instance through a builder function, like this.
var pubsubV1 = require('@google-cloud/pubsub').v1();
var publisher = pubsubV1.publisherApi();
publisher.createTopic(...);

So now, publisherApi (not a capitalized PublisherApi) is a function and it actually takes the all optional parameters. Thus,

define a Builder class, and let this builder function (like publisherApi) to be a method of this class. This way, JSDoc will pick up the builder function in the generated HTML.
move the documentation of parameters to the builder function. The parameters for the constructor is intentionally blank, because it's not expected to be built directly.
add an example clause in the constructor document of how to create an instance.

Two nits, otherwise LGTM

LGTM if build passes


Proposal - move resources into separate folder

not sure if we want all the resources to be in core. might be easier for the user if there was a separate folder in core where all the resources are kept.
core -> resources -> aws_resource, azure_resource, docker_resource ...
thoughts?

It would be consistent with how we structure particles. However, would we ever need to have more than one file per resource? If not, it might be an overkill.

yeah thats true. not sure how many other resources we will have. will close for now. if we have a few more maybe we can revisit


responsive-web-design

now it's look good on mobile

Thank you for your awesome contribution! It does indeed look good, but as of now, sadly, I can't merge it because it looks more like a single-page website than an actual mobile application. In the issue related to this Pull Request I added some advice regarding this, maybe you can take a look at that and adjust the new update to fit that? If it's not possible, then it's okay, let me know if it's too much of a hassle.

I did't understand what you mean you can't make anything else on mobile screens because it small if you have and idea tell me about it and I can help

I meant that you can always use breadcrumbs and slideable menus that look full-size and can be moved away using buttons. (Burger / More buttons for both the preview panel and the upload panel) It might require more than a @media query and might work with some JS, but in general it shouldn't be that hard.

Do you mean you need each table in page?

Do you mean you need each table in page?

Not quite. I'm trying to say that it's possible to add transform (translate) properties to menus and let the user hide/show them using small buttons that may appear on the right / left. That can save a lot of space and make everything relatively interactive. I'm sorry if it sounds complex, let me know if you need any more clarification.

No, of course not. On desktop it should look the same way it currently is. I mean that on mobile or any other small screens the menus (left/right) can be hidden using Transform properties. This also means that we need to load a few more toggle buttons that return the menus (for interpolation, transition along with the Translate rule should work) to the screen. These can be added to the screen edges on the right/left, so users will be comfortable trying to set things up.

Can you do in figma what type of layout do you want?  I thought the friend above was cool.  Would it be one with a side menu drawer?

Can you do in figma what type of layout do you want? I thought the friend above was cool. Would it be one with a side menu drawer?

I'm sorry, I'm currently not home, but the general problem with the layout above is probably that it's vertical, and that seems slightly inconvenient in a phone since you're not scrolling through content and are actually using an app.
Here's a quick showcase I made as an animation:
https://github.com/LyubomirT/lookaround/assets/127299159/aa9d6d78-e546-43e5-a71a-4b58314b8b38
So, in this case, there are two sidebars which are the control panels of Lookaround. In the middle, there is supposed to be the rendered output, I replaced it with some sort of a placeholder for now. Those sidebars should basically be hidden all the time until you show them using small buttons placed at the edges of the screen of a user.

I could do this easily with nextjs, wanna try out ?

I could do this easily with nextjs, wanna try out ?

I'd rather stick with Flask and Jinja2 templating instead of switching entirely to serverless, there will be a lot of hassle on Heroku regarding hosting. If possible, please follow that style. 😅

Can you do in figma what type of layout do you want? I thought the friend above was cool. Would it be one with a side menu drawer?

I'm sorry, I'm currently not home, but the general problem with the layout above is probably that it's vertical, and that seems slightly inconvenient in a phone since you're not scrolling through content and are actually using an app.
Here's a quick showcase I made as an animation:
bandicam.2024-05-17.14-02-46-941.mp4
So, in this case, there are two sidebars which are the control panels of Lookaround. In the middle, there is supposed to be the rendered output, I replaced it with some sort of a placeholder for now. Those sidebars should basically be hidden all the time until you show them using small buttons placed at the edges of the screen of a user.

If you want it like this i can do it

Which one do you prefer?

the first pushes the page content to the side, the second opens the drawer on top of the page

the first pushes the page content to the side, the second opens the drawer on top of the page

Second one better

the first pushes the page content to the side, the second opens the drawer on top of the page

I think it's better, and easier at some point, to make the sidebar overlay the content. Will also look kind of more intuitive in my opinion.


Debian Bookworm openssl-legacy-provider error

When starting the app with npm run build, I get an error from the NODE_OPTIONS:

<EMAIL_ADDRESS>build
NODE_OPTIONS=--openssl-legacy-provider next build

sh: 1: next: not found
Any ideas? Thank you!

Have you tried npm install first?

@vibrantlee  If you haven't figured it out yet, I was able to get it up and running by running npm install --save-dev and removing the references to NODE_OPTIONS=--openssl-legacy-provider from package.json

Closing this is a nodejs versioning issue.
You can use https://github.com/nvm-sh/nvm so you can switch between versions as your building this.
nvm install
nvm use
or nvm install 18
nvm use


RSPduo support

I made a fork under my account and created a branch called 'RSPduo_fixes' (https://github.com/fventuri/SoapySDRPlay/tree/RSPduo_fixes).
I cleaned up a couple of warnings during compile and I also straightened up the code for the RSPduo mode/tunes and antennas (hopefully).
I tested my changes this afternoon using 'SoapySDRUtil' with the following commands:
SoapySDRUtil --probe="driver=sdrPlay, rspduo_mode=Tuner A (Single Tuner)"
SoapySDRUtil --probe="driver=sdrPlay, rspduo_mode=Tuner B (Single Tuner)"
SoapySDRUtil --probe="driver=sdrPlay, rspduo_mode=Tuner A (Master/Slave)"
SoapySDRUtil --probe="driver=sdrPlay, rspduo_mode=Tuner B (Master/Slave)"
SoapySDRUtil --probe="driver=sdrPlay, rspduo_mode=Dual Tuner"
and in each case 'SoapySDRUtil' returned what I expected (including 2 RX channels in the last case with 'Dual Tuner').
Please give it a try and if it looks OK to you, I'll go ahead and send a pull request.
Franco
Originally posted by @fventuri in https://github.com/pothosware/SoapySDRPlay/issues/58#issuecomment-566337373

Hi @fventuri, this is a good start and only works for probing and not in an application - master mode has certain limits... must be only Low-IF mode, sample rate must be either 6 or 8 MHz only - IF Freq must be either 1.62 MHz or 2.048 correspondingly.
Once the first tuner is in master mode, then probe should only return or allow slave mode. If I start one instance of CubicSDR and then run probe again I get a map::at error
Slave mode cannot have any sample rate control, or IF frequency. It can have independent bw and decimation control however.

Thanks for your feedback.
I committed several changes to the 'RSPduo_fixes' branch in my repo that should address some of the problems with the application.
The application I used for my tests is CubicSDR (version 0.2.4) - I was able to have the driver run in single tuner, dual tuner, and master mode; while in master mode, I was able to launch a second instance of CubicSDR and use the slave instance.
While I was testing the code for SoapySDRPlay, I noticed that CubicSDR has a couple of quirks:

it ignores the return value when calling activateStream() (in CubicSDR source code look at the file src/sdr/SoapySDRThread.cpp line 119); therefore it would ignore when the activateStream() in SoapySDRPlay returned the error SOAPY_SDR_NOT_SUPPORTED. I changed the behavior of activateStream() in the driver to throw an exception instead; this makes CubicSDR crash, but at least this way I could see there was a problem with sdrplay_api_Init()

somehow CubicSDR seems to be unable to deal with a list of antennas dependent on the rspDuoMode and tuner (i.e. only 'Tuner 1 50 ohm' and 'Tuner 1 Hi-Z' should be listed if the tuner selected is Tuner A, while only 'Tuner 2 50 ohm' should be listed for Tuner B). In order for listAntennas() to list all the antennas (independent of the tuner) when using CubicSDR, I added (for now) a boolean called 'CubicSDR', which is set to true to address this quirk. In the final version of the driver, I'd like to find a better way to handle this issue.

Also the code for the driver has become more messy to handle the dual tuner and master/slave cases, but at least I was able to make it run without crashing for the most common use cases I could think of.
Please give it a try when you get a chance and let me know how it goes for you.
Franco

Hi, I tride the new Franco's branch for API v3 & RSP Duo support with a fresh built CubicSDR.
When I start CubicSDR it scans for Soapy devices and finds my RSP Duo.
I select it and the app hang and exits.
On the console windo I can see:
SDR enumerator done. [INFO] devIdx: 0 [INFO] hwVer: 3 [ERROR] Invalid RSPduo mode change: 7 -> 8 Terminating SDR thread.. ERROR: thread '9SDRThread' has not terminated in time ! (> 3000 ms)
I don't know if it's a CubicSDR issue or a SoapySDRPlay one.

Thanks for the feedback, nmaster2042.
What this line:
     [ERROR] Invalid RSPduo mode change: 7 -> 8

means is more or less the following.
RSPduo mode 7 (7=1+2+4) means that the RSPduo can run in 'single tuner', 'dual tuner' or 'master' mode (these codes are described in the enum 'sdrplay_api_RspDuoModeT' in the include file 'sdrplay_api_rspDuo.h') - this is typically the list of modes that the RSPduo accepts when no other application is using it.
On the other hand mode 8 means CubicSDR tries to run that RSPduo in 'slave' mode, which is the mode you would choose when another application is already using the same RSPduo in 'master' mode. Typically this happens because CubicSDR on exit saves its last used configuration in a config file called $HOME/.CubicSDR/config.xml.
If you look at that contents of that file, you'll see a stanza that looks more or less like this:
        <device>
            <id>SDRplay Dev0 RSPduo SERIALNUM</id>
...
            <settings>
                <agc_setpoint>-30</agc_setpoint>
                <biasT_ctrl>false</biasT_ctrl>
                <dabnotch_ctrl>false</dabnotch_ctrl>
                <extref_ctrl>false</extref_ctrl>
                <if_mode>1620kHz</if_mode>
                <iqcorr_ctrl>true</iqcorr_ctrl>
                <rfgain_sel>4</rfgain_sel>
                <rfnotch_ctrl>false</rfnotch_ctrl>
                <rspduo_mode>Tuner A (Master)</rspduo_mode>
            </settings>
...
        </device>

When you restart CubicSDR it reads that file and tries to ask the driver for the mode previously saved in the 'rspduo_mode' element. If in your case, you might have something like 'Tuner B (Slave)', that can trigger the error you saw.
A quick workaround (for now) is to edit that config file and change the 'rspduo_mode' line to something like 'Tuner A (Master)' - after that CubicSDR should work.
A better way would be to exit the CubicSDR in slave mode first and then exit the CubicSDR in master mode last - this way the config file should contain an entry like mine (i.e. 'Tuner A (Master)').
Finally I should probably address this issue by making a simple change in the driver, such that in a scenario like yours it would select 'single tuner' or 'master' mode (with the same tuner in the config, or the other tuner, or always 'Tuner A'?).
Hope this helps,
Franco

it ignores the return value when calling activateStream() (in CubicSDR source code look at the file src/sdr/SoapySDRThread.cpp line 119); therefore it would ignore when the activateStream() in SoapySDRPlay returned the error SOAPY_SDR_NOT_SUPPORTED. I changed the behavior of activateStream() in the driver to throw an exception instead; this makes CubicSDR crash, but at least this way I could see there was a problem with sdrplay_api_Init()
somehow CubicSDR seems to be unable to deal with a list of antennas dependent on the rspDuoMode and tuner (i.e. only 'Tuner 1 50 ohm' and 'Tuner 1 Hi-Z' should be listed if the tuner selected is Tuner A, while only 'Tuner 2 50 ohm' should be listed for Tuner B). In order for listAntennas() to list all the antennas (independent of the tuner) when using CubicSDR, I added (for now) a boolean called 'CubicSDR', which is set to true to address this quirk. In the final version of the driver, I'd like to find a better way to handle this issue.

That is the problem with the "pull the rug under the SDR feet" problem of the Duo. Cubic expects that Settings are obeyed and don't change dynamically because of another instance in particular.
That is why I suggested in #58 that the device only present "automagically" settings that stay relatively static afterwards depending of the starting order.

Maybe presenting a reduced capability "CubicSDR"

@SDRplay suggested in #58 presenting different devices in the selection panel:

It's not as simple as always starting the first instance as the master. In master mode, Low-IF is only possible and so therefore only 2 MHz of bandwidth. So what we do in SDRuno is to always start in single tuner mode, but have a switch to allow the changing of the first instance into master mode. Then the 2nd instance would automatically start in slave mode. Then there is dual tuner mode, where both tuners are controlled by the same instance. I guess this could all be handled by a mode option in the device selection panel.
Whichever instance is using tuner 1 will still be able to switch between the HiZ port and port A in master/slave mode.

I'm not even sure other API layers using Soapy underneath like gr-osmo or such in GQRX and others would interface that dynamic settings any more kindly, so presenting different capability classes as pseudo-"Devices" may be the solution.

I must admit I'm a bit confused as to what the intention is with the driver. In SDRuno, we always start the RSPduo in single tuner mode EXCEPT when the RSPduo is already being used in master mode, then slave mode must be used. It's very simple, there are no variables required.
Why can't it be done like that?...
scenario 1: RSPduo connected, but neither tuner in use

in the CubicSDR device selection panel you can select between single tuner (default) or master (ignore dual for now, can CubicSDR deal with multiple streams from a single driver instance?)

scenario 2: RSPduo connected and master mode is already in use by CubicSDR

in the 2nd CubicSDR instance ONLY slave is available and therefore the sample rate and IF mode should be hidden.

scenario 3: RSPduo connected and single tuner is in use by CubicSDR

then the 2nd CubicSDR instance should see NO RSPduo available.

This to me seems very simple - is there a problem with implementing it this way? By allowing variables to be passed, there seems to be an assumption that they override existing behaviour - for example if the RSPduo is already being used in master mode, would you expect that another instance of CubicSDR where "single tuner" is being passed in via the rspduo_mode variable would work - because it won't and shouldn't be expected to either.
Unless I've missed something about the way CubicSDR and the SoapySDR framework behave?

@SDRplay summurizes it right I think, the SoapySDR driver should only present the settings appropriate to each of the 3 scenarios above.
Once in a given "mode", the list of settings is indeed frozen and CubicSDR or others should be able to run normally, being either Single, Master, or Slave exclusively. We should not try to override the auto-detected or manually chosen mode by other means like this persistent rspduo_mode.

can CubicSDR deal with multiple streams from a single driver instance ?

Alas no. Only 1 device, 1 readStream thread active at any given time. The Devices menu is always available though, and allows switching of devices without restarting CubicSDR.

Thanks for your comments, vsonnier and SDRplay.
My perspective on the SoapySDRplay driver was to write a "general purpose" SoapySDR driver, following more or less the guidelines here: https://github.com/pothosware/SoapySDR/wiki/DriverGuide (and more specifically the API contract specified here: https://github.com/pothosware/SoapySDR/blob/master/include/SoapySDR/Device.hpp). From this point of view CubicSDR is just one of the applications using the SoapySDRplay driver (for instance there could be other applications where they would want the 'Dual Tuner' option, since the API shows that a device could have multiple channels).
This point also might help explain the confusion for SDRplay, where I think the application (SDRuno) and the driver are one thing and therefore there's no 'line of demarcation' between the two.
This said, I have no problem implementing the driver as presenting multiple "virtual devices" in the RSPduo case (I don't think it should be too hard) - I have just a few high level questions to avoid possible misunderstandings in the future:

what should be the naming convention for the RSPduo virtual devices?  Would something like 'SDRplay Dev0 RSPduo <SERIAL NUMBER> - Single Tuner',  'SDRplay Dev0 RSPduo <SERIAL NUMBER> - Master', and 'SDRplay Dev0 RSPduo <SERIAL NUMBER> - Slave' work?
also is it possible that the user might want to use tuner B as master and tuner A as slave? in this case should the list of virtual devices above have something like '... - Tuner A Master', and '.... - Tuner B Master'?
in a similar way what about sampling rates in the case of Master (or Dual Tuner)? Should the 6MHz/8Mhz sampling rate be specified at the virtual device level too?

As per my initial comment about CubicSDR ignoring the return value from 'activateStream()', there could be several reasons why the call to 'sdrplay_api_Init()' fails besides the case of a wrong RSPduo mode (see the list at the bottom of page 26 in https://www.sdrplay.com/docs/SDRplay_API_Specification_v3.06.pdf) - I am not sure how's CubicSDR expects the driver to communicate this failure.
Franco

I don't understand the significance of the naming. If that means something to the end application then I don't have a problem with whatever it needs to be. Note that once the RSPduo is in use, then the driver (for the 2nd instance of the end application) gets all the information it needs from the API about what that instance can or can't do. So the driver will not need to query any virtual device name for that information.
It's possible for the master tuner to be either tuner, the slave is just whichever one isn't in use. Again, you can get that information from the API.
The importance about the sample rate is...
Single Tuner - sample rate can be 2 - 10 MHz in Zero-IF mode and 6 MHz (which produces a final sample rate of 2 MHz) in Low-IF mode. Other sample rates can be used in Low-IF mode but with less efficiency so we tend not to recommend them now. Delivers 1 IQ stream.
Master Tuner - Low-IF mode only either 6 MHz or 8 MHz (both produce a final sample rate of 2 MHz) - you would ONLY use 8 MHz if the other tuner was using an application like dump1090 which expects 8 MHz sample rate. Delivers 1 IQ stream.
Slave Tuner - Neither sample rate or IF mode can be set - follows whatever the Master Tuner is using. Delivers 1 IQ stream.
Dual Tuner - Low-IF mode only and only 6 MHz sample rate (produces a final sample rate of 2 MHz) - delivers 2 IQ streams.
Note that for all of these scenarios, parameters like decimation and IF Bandwidth are independent and can be used by either tuner (except dual tuner mode where you would want those parameters to be consistent).
Gain control for each tuner is also independent of these scenarios.

Forgot to mention that when using 6 MHz Low-IF, the IF mode should be set to 1.62 MHz and when using the 8 MHz Low-IF, the IF mode should be set to 2.048 MHz - if not this could cause the incorrect tuner mode to be selected.

I made the changes you suggested - now the SoapySDRPlay driver can present multiple 'virtual devices' for the RSPduo depending on the available RSPduo modes.
I quickly ran a couple of tests using CubicSDR a few minutes ago and I was able to run two instances, one as a master, and one as a slave.
I'll run a few tests tomorrow morning, but in the meanwhile you can give it a try to see if it works as you described above.
Franco

Hi Franco.
I just rebuilt your SoapySDRPlay api3 branch.
I removed the .CubicSDR folder in order to reinitialize CubicSDR user configuration, as you explained.
Now when I start CubicSDR I can see 5 devices. From a user point of view it's more understandable like this.
But, I can't start a SDR session with any of these devices: CubicSDR crashes and I get this error in the console:
SDR thread starting.
device init()
[INFO] Using format CF32.
[ERROR] *** error in activateStream() - Init() failed: sdrplay_api_Fail
terminate called after throwing an instance of 'std::runtime_error'
what():  *** error in activateStream() - Init() failed
Abandon (core dumped)
One other detail, When selecting Tuner A single or master, Bias tee option has to be removed.

OK, it seems I have a small issue with the sdrplay 3 API on my system.
I'm investigating this issue and I'll make more tests.

5 devices?? I'm not at my desk but will take a look as soon as I can. Why is it not just 1 device and then deal with the different modes like another IF mode?...
i.e.
RSPduo = 1 device
Mode (dropdown like IF mode) = Single Tuner, Master Tuner, Dual Tuner
The thing I didn't like about the rspduo_mode variable was that it was required to be set to do anything. What should happen is that the API should be queried and then the functionality presented as dropdown options to the user.
Same goes for Tuner #
Is that not possible? I'll take a look asap and provide some feedback.

Mode (dropdown like IF mode) = Single Tuner, Master Tuner, Dual Tuner

Can't work. Make the Mode a setting that is RW with readSetting/writeSetting like the others (RF notch, antenna choice, IF modes....etc.) will make it possible to change the Mode at any time at runtime, which we don't want.
Presenting different "pseudo-Device " (ex RSPDuo-Single, RSPDuo-Master, RSPDuo-Slave) that stay stable once selected is the only way, unless I'm missing something there.

These are virtual devices.
When starting the firs CubicSDR, and no tuners are actually in use you get 5 devices (Tuner A single, Tuner B single, Dual tuner, Tuner A master and tuner B master).
When you chose device tuner A Master for example, when you start another instance of CubicSDR you only get Tnuer B slave with good IF and sample rate settings.
Let's give a try.
It's probably different than on SDRUno I never used but this way is better than the rspduo_mode option.

My point was I don't understand why there is 5, but there should be 3. Why is Tuner in the virtual name?
Are you saying that when you select the device, that you then can't customise the other options??
If so, then what is to stop me from selecting Tuner A master and 10 MHz sample rate with Zero IF - this is obviously not a valid combination.
If you can deal with that, then why can't the tuner also be like antenna select?

In the actual implementation, when you select RSP Duo Tuner A master you simply can't select 10 mhz sample rate and zero-if because sample rate is fixed to 6 mhz and if mode = 1620 khz to prevent user to select wrong values as I made and crashing the app.
Instead you can select Tuner A single to put 10 Mhz sample rate and Zero IF.
I think the reason why there is 5 virtual devices and not 3 is because it seems to be the way to deal with options of each tuner considering the mode (single, dual, master/slave).
In Cubic SDR, when you select a device, there is different options, but one can't be dynamically changed depending of the value of an other option.
In the first implementation with the rspduo_mode, you could select the mode but you had to manually set sample rate and if mode correctly because options couldn't be automatically modified.
It's my understanding.
I'm testing this, for now it seems to work ok.

In master mode, the sample rate can be either 6 MHz (1.62 MHz IF) or 8 MHz (2.048 MHz IF) - so is 8 MHz not an option?
I guess what I'm not understanding is that if you can select sample rate depending on whether you've selected master or single (as you've stated above), then I don't understand why the same is also not true of what antenna/tuner is used.
I'll try it myself later today when I'm at my desk and I'll probably get a better feel for it and get back to you.

For now, in master mode, only 6 Mhz SR with IF mode of 1620 khz is selectable.
But it probably can be added.
The master / slave mode is actually working I can use 2 instances of CubicSDR.
Yes, the best way is to get a try by yourself to give a feedback.

But if 8 Mhz / 2048 khz is added in master mode, user will pay attention to adjusct the 2 parameters correctly because it will be possible to select for example 6 Mhz SR and 2048 khz IF mod witch will be incorrect.
OR, for master mode it would be better to have 2 options combining the 2 parameters:

6 mhz / 1620 khz
8 mhz / 2048 khz

This would prevent users (like me lol) to chose incorrect combination.

Thanks everyone for trying it and especially their feedback.
A couple of comments:

if the tuner (A or B) was selectable as a dropdown like IF mode, then we would go back to the problem with the antenna selection; i.e. if tuner A was selected, only antennas 'Tuner 1 50ohm' and 'Tuner 1 Hi-Z' should be selectable, while if the user had switched to tuner B, only the 'Tuner 2 50ohm' option should be presented. If I understand correctly, CubicSDR doesn't dynamically retrieve the list of available antennas every time, and therefore the driver would have to present all the antennas regardless of the selected tuner (which is is what the previous version was doing).
a similar reasoning also applies to the choice of the sample rate for dual tuner and master mode (I'll talk about slave mode in a moment) - if the user was allowed to choose between 6Ms/s and 8Ms/s, then the IF mode choice would be dependent on that . True, I could add another couple of pseudo devices to allow for that, as nmaster2042 suggested, but I also based my decision on this sentence by SDRplay:

Master Tuner - Low-IF mode only either 6 MHz or 8 MHz (both produce a final sample rate of 2 MHz) - you would ONLY use 8 MHz if the other tuner was using an application like dump1090 which expects 8 MHz sample rate. Delivers 1 IQ stream.

As per when the driver is used in slave mode, in this case the driver uses the sample rate already set by the master (be it 6Ms/s or 8Ms/s), so if someone were to start say dump1090 in master mode (which would set the sample rate to 8Ms/s) and then run CubicSDR in slave mode, CubicSDR would use an 8Ms/s sample rate (and an IF mode of 2048 kHz).
Franco

You are right Franco, the simplest is the best I think.
In the particular case of app such as dump1090, the only thing to remember is to start it in master mode so it can get its 8 Mhz SR.
As we can chose witch tuner will be the master, it can serve all uses cases I think.

OK, I think I figured out the issue with CubicSDR and the list of antennas shown. I took a look at the source code for CubicSDR and I found this code(see https://github.com/cjcliffe/CubicSDR/blob/master/src/forms/SDRDevices/SDRDevices.cpp#L136):
        //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();

This explains why when I was selecting any of the tuner B cases (either in single tuner mode or in master mode), the antenna entry was not there (because there's only one choice). After understanding this, I may have to revisit my comments about the way CubicSDR handles the antenna selection (and therefore the tuner choice discussion above).
Franco

This is the initial Device dialog choosing. The other place where list of antennas are read and displayed is this: https://github.com/cjcliffe/CubicSDR/blob/57454e4b32040e98a6c296cbfeb1335b108a940b/src/AppFrame.cpp#L1036
But the principle is the same: there is no point, in principle, of displaying a list of antennas made of 1 entry because there is no choice. This is to have a cleaner UX, nothing more, nothing less.

Thanks vsonnier for the clarification about the antenna thing.
I fixed another bug I found this afternoon where in CubicSDR switching from one pseudo device (for instance Tuner A - Single) to a different pseudo device (for instance Tuner B - Single), and then back again to the first pseudo device (Tuner A - Single) causes some problems because CubicSDR tries to re-use the initial instance of that pseudo device (instead of reinitializing it), while I think the SDRplay 3.x API expects that the client goes through a full ReleaseDevice/SelectDevice cycle (+ GetDeviceParams). I added a check in readSettings() so that, if the device is not the one selected (i.e. if CubicSDR didn't initialize it), then a new function called 'reselectDevice()' is invoked, that does exactly that.
I also found out this afternoon that, when streaming in Single Tuner mode, there are quite a number of times where the time elapsed between two successive calls to 'rx_callback' is above 100,000us, which then causes timeouts in 'acquireReadBuffer()', which show up in the console as messages: 'SoapySDR read failed with code: -1'.
To understand better this problem, I lowered the threshold, and I logged all the cases where the time elapsed between two successive calls to 'rx_callback' is above 90,000us - there were a lot of them, so it seems this issue needs some more research.
I am not sure if you too see these messages in Single Tuner mode (I didn't try to see if it happens in Dual Tuner mode), or if it just something going on on my computer.
Franco

I fixed another bug I found this afternoon where in CubicSDR switching from one pseudo device (for instance Tuner A - Single) to a different pseudo device (for instance Tuner B - Single), and then back again to the first pseudo device (Tuner A - Single) causes some problems because CubicSDR tries to re-use the initial instance of that pseudo device (instead of reinitializing it), while I think the SDRplay 3.x API expects that the client goes through a full ReleaseDevice/SelectDevice cycle (+ GetDeviceParams). I added a check in readSettings() so that, if the device is not the one selected (i.e. if CubicSDR didn't initialize it), then a new function called 'reselectDevice()' is invoked, that does exactly that.

Truth be told, the Devices selection is rarely used after the intial CubicSDR startup, so it is small wonder a clean Device switch is probably not done. Now the changes you made are surely interesting for all, so a PR for CubicSDR would be greatly appreciated once the Duo problems are sorted out 👍
I'll have a look (later) at the Streaming code of the SoapySDR driver to see if there are obvious concurrency or performance problems, it has become my specialty for Cubic or Soapy drivers, so to speak (https://github.com/pothosware/SoapyPlutoSDR/pull/14).

The code won't build for me...
/usr/include/c++/5/bits/hashtable_policy.h:85:34: error: no match for call to ‘(const std::hash<sdrplay_api_RspDuoModeT>) (const sdrplay_api_RspDuoModeT&)’
noexcept(declval<const _Hash&>()(declval<const _Key&>()))>
Amongst many other errors, which I wasn't getting before. Someone point me in the right direction please.

@vsonnier - I'll definitely plan to submit these changes as a PR once the design part is sorted out - so far everything is very 'experimental'
@SDRplay - I just tried pulling the code directly from github and recompiling and it worked for me:
$ git clone --single-branch --branch RSPduo_fixes https://github.com/fventuri/SoapySDRPlay.git
Cloning into 'SoapySDRPlay'...
remote: Enumerating objects: 39, done.
remote: Counting objects: 100% (39/39), done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 459 (delta 24), reused 23 (delta 10), pack-reused 420
Receiving objects: 100% (459/459), 147.69 KiB | 1.64 MiB/s, done.
Resolving deltas: 100% (306/306), done.
$ cd SoapySDRPlay/
$ mkdir build
$ cd build
$ cmake ../
-- The CXX compiler identification is GNU 9.2.1
-- Check for working CXX compiler: /bin/c++
-- Check for working CXX compiler: /bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Build type not specified: defaulting to release.
-- Found libsdrplay: /usr/local/include, /usr/local/lib64/libsdrplay_api.so
-- LIBSDRPLAY_INCLUDE_DIRS - /usr/local/include
-- LIBSDRPLAY_LIBRARIES - /usr/local/lib64/libsdrplay_api.so
-- Performing Test HAS_STD_CXX11
-- Performing Test HAS_STD_CXX11 - Success
-- Found Git: /bin/git (found version "2.21.0")
-- Module sdrPlaySupport configured with version: 0.3.0-5e8a451
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/SoapySDRPlay/build
$ make
Scanning dependencies of target sdrPlaySupport
[ 20%] Building CXX object CMakeFiles/sdrPlaySupport.dir/Registration.cpp.o
[ 40%] Building CXX object CMakeFiles/sdrPlaySupport.dir/Settings.cpp.o
[ 60%] Building CXX object CMakeFiles/sdrPlaySupport.dir/Streaming.cpp.o
[ 80%] Building CXX object CMakeFiles/sdrPlaySupport.dir/Version.cpp.o
[100%] Linking CXX shared module libsdrPlaySupport.so
[100%] Built target sdrPlaySupport

Here I am running Linux Fedora 30 with gcc/g++ version 9.2.1:
$ cat /etc/os-release
NAME=Fedora
VERSION="30 (Thirty)"
ID=fedora
VERSION_ID=30
VERSION_CODENAME=""
PLATFORM_ID="platform:f30"
PRETTY_NAME="Fedora 30 (Thirty)"
ANSI_COLOR="0;34"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:30"
HOME_URL="https://fedoraproject.org/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f30/system-administrators-guide/"
SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=30
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=30
PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"

$ g++ --version
g++ (GCC) 9.2.1 20190827 (Red Hat 9.2.1-1)
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Do you mind running the same commands (git clone, ..., cmake, and make) on your system?
If the make step still fails for you, you can email me (my username at comcast dot net) the full output from make so I can take a look.
Franco

The code won't build for me...
/usr/include/c++/5/bits/hashtable_policy.h:85:34: error: no match for call to ‘(const std::hash<sdrplay_api_RspDuoModeT>) (const sdrplay_api_RspDuoModeT&)’
noexcept(declval<const _Hash&>()(declval<const _Key&>()))>

I would bet a C++ compatibility level. @fventuri, try to stick to C++11 only, maybe you used C++14 or C++17 contructs and recent GCCs maybe default to these recent standards. @guruofquality, what is the expected C++ standard supposed to be supported in the Pothosware eccosystem ?

@SDRplay I rebuilt with success too the rspduo_fixes branch from Franco's git repo.

Nope, won't build for me on Ubuntu 18.04.3 LTS - I've done everything you suggested @fventuri
gcc -v
gcc version 5.5.0 20171010 (Ubuntu 5.5.0-12ubuntu1)
Updated to latest version using apt update apt upgrade etc.

gcc version 5.5.0

I suspect this GCC version is way too old, to support modern C++ features.

what do you do to update gcc?

bear in mind that almost all of our customers that want to use Linux, don't want to build anything, so to tell them that they first have to get a new compiler is going to be interesting lol

Let's try with this: https://linuxize.com/post/how-to-install-gcc-compiler-on-ubuntu-18-04/ installing GCC 7.xx as bare minimum.

bear in mind that almost all of our customers that want to use Linux, don't want to build anything, so to tell them that they first have to get a new compiler is going to be interesting lol

Well GCC 5.0 is an antique version w.r.t recent Ubuntus lile v18.04, so they won't have to install anything most likely.

Interestingly build-essential has been marked for "kept back" - no idea why
Going to brute force upgrade it now

@SDRplay On my ubuntu 1910 I have:
gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2)

Just managed to get updated to gcc 7.4 - that wasn't pretty - had to uninstall packages, remove files and the contents of the status file just to get it to install build-essential correctly - nasty
Anyway, I've now built it and I can see it working.
Couple of things...
Master 6 MHz - the starting sample rate is 6 MHz, but it is 2 MHz by the time it gets to the application - CubicSDR is showing a 6 MHz sample rate, which is wrong - it should be 2 MHz
Now that there is only a single sample rate, decimation should be an option so that smaller sample rates can be used.
Master 8 MHz sample rate should also be an option.
Not sure I like the virtual devices but I understand it's a way to get this done.
I'll keep playing with it.

std::hash<sdrplay_api_RspDuoModeT>

Isnt this just a matter of the hash not being defined for a custom type, and not a feature greater than c++11.

I would bet a C++ compatibility level. @fventuri, try to stick to C++11 only, maybe you used C++14 or C++17 contructs and recent GCCs maybe default to these recent standards. @guruofquality, what is the expected C++ standard supposed to be supported in the Pothosware ecosystem ?

Ive tried not to use any C++14 and up in the common libraries, but its not really a big deal. Honestly when I started this, C++11 was just as recent as C++14 and 17 are now to the current date. Its pretty easy to have C++14 support in the latest ubuntu LTS and C++17 is mostly there.

Thanks SDRplay and guruofquality - I just pushed a new commit that removes any use of hashes/unordered_maps from the code and uses instead 'stringTo...' and '...toString' methods (similar to the existing methods 'stringToIF' and IFtoString').
Hopefully this latest code should compile without warnings or errors on any version of gcc/g++ compiler.
As per the sample rate, I see that the exiting code for the the SoapySDRplay driver has (and I think it comes from the original code) this method called 'getInputSampleRateAndDecimation', that selects a decimation factor based on the IF mode and on the input sample rate.
I assume this code is still valid when the RSPduo is running in single tuner mode, but I suspect it might need to be revisited for master (and slave mode).
To make sure I understand how the RSPduo works, you are saying that in master mode, in case of a 'rspDuoSampleFreq' of 6MHz, the sample rate is actually 2 MHz with no decimation, but the user should be allowed to select a sample rate of 1 MHz with decimation factor of 2; or a sample rate of 500 kHz with a decimation factor of 4; and son on?
Finally I am also wrote (and I currently debugging) an 'hybrid' version of the driver where in the RSPduo case there's a pseudo device for each of the RSPduo modes (i.e. typically three pseudo devices - single tuner, dual tuner, master - when started first, or just one pseudo device - slave - when started as a slave), and the tuner choice (for the single tuner and master modes) is a drop down like the IF mode selection. Do you think you would like this approach better?
Franco

As mentioned earlier, I created a new branch called 'pseudo_devices_by_RSPduo_mode' (https://github.com/fventuri/SoapySDRPlay/tree/pseudo_devices_by_RSPduo_mode), which presents a new pseudo device for each of the RSPduo modes that are available when the client application (CubicSDR in this case) is run, while the selection of the tuner is via dropdown.
I ran only a couple of tests (single tuner, and one instance of CubicSDR as a master, and another instance as a slave), and they worked fine on my computer.
Please give it a try and let me know if this way looks better for you.
Franco

Hi @fventuri, the pseudo branch is much more what I thought it would look like - I think it looks better to the end user. Some comments...

By definition, if single or master modes are available, then ALL 3 antennas should be available to be selected. Then depending on what antenna is selected, you can then automatically select the tuner - so I would be in favour of having all 3 antennas available, but removing tuner as an option. I think, again, this makes it easier for the end user.

In dual tuner mode, in practice you wouldn't have any antenna select and just make tuner 1 50 Ohm and Tuner 2 50 ohm ports the ones to use. This is the way we have it set for SDRuno. For diversity reception you would want the two tuner inputs to be the same otherwise there is a mismatch in the gain control.

In master mode, dual tuner mode and single tuner Low-IF mode, these are the options for sample rate...

Input: 6 MHz, Output: 2 MHz, decimation = 1, dec_enabled = false
Input: 6 MHz, Output: 1 MHz, decimation = 2, dec_enabled = true
Input: 6 MHz, Output: 500 kHz, decimation = 4, dec_enabled = true
Input: 6 MHz, Output: 250 kHz, decimation = 8, dec_enabled = true
Input: 6 MHz, Output: 125 kHz, decimation = 16, dec_enabled = true
For master mode ONLY, you can also have:
Input: 8 MHz, Output: 2 MHz, decimation = 1, dec_enabled = false
Input: 8 MHz, Output: 1 MHz, decimation = 2, dec_enabled = true
Input: 8 MHz, Output: 500 kHz, decimation = 4, dec_enabled = true
Input: 8 MHz, Output: 250 kHz, decimation = 8, dec_enabled = true
Input: 8 MHz, Output: 125 kHz, decimation = 16, dec_enabled = true
Note: this is for compatibility with dump1090 ONLY and shouldn't be used in normal circumstances.
For slave mode, the input sample rate is fixed by the master, but the output sample rate can be...
Output: 2 MHz, decimation = 1, dec_enabled = false
Output: 1 MHz, decimation = 2, dec_enabled = true
Output: 500 kHz, decimation = 4, dec_enabled = true
Output: 250 kHz, decimation = 8, dec_enabled = true
Output: 125 kHz, decimation = 16, dec_enabled = true
Note: this is independent of what decimation is being used in the master.

By the way, the output sample rate should be what CubicSDR uses - not the input sample rate.

I've edited the comment above to add in the relevant IFBW values for each output sample rate.

By the way, the output sample rate should be what CubicSDR uses - not the input sample rate.

Yes indeed, it is crucial that the Sample rate the user has access to really corresponds to the final I/Q samples per second the whole processing is expecting.
Now to achieve the correct behaviour of setSampleRate and getSampleRate API the right combinations of "input rate + decimation" as listed by @SDRplay has to be used for instance, and of course Decimation must not be exposed as a setting for the final  user.

By definition, if single or master modes are available, then ALL 3 antennas should be available to be selected. Then depending on what antenna is selected, you can then automatically select the tuner - so I would be in favour of having all 3 antennas available, but removing tuner as an option. I think, again, this makes it easier for the end user.

I agree 100% here. If the 3 antennas can be switched at runtime, it is a perfect candidate for an Antenna selection setting, as it exists for the RSP2. At the driver level, calling setAntenna API could indeed also be used to switch to the appropriate tuner if necessary, hiding all the complexity behind the SoapySDR API.

Thanks for the very useful feedback SDRplay and vsonnier.
I just implemented all your recommendations to the SoapySDRPlay driver (branch 'pseudo_devices_by_RSPduo_mode' - https://github.com/fventuri/SoapySDRPlay/tree/pseudo_devices_by_RSPduo_mode).
A couple of notes:

I added another pseudo device, so that the user can choose between running in master mode with a sample rate of 6MHz (the default), and 8Mhz (if they want to run it at the same time as say dump1090).
I used the API function 'sdrplay_api_SwapRspDuoActiveTuner' to switch between the tuners when the RSPduo is in single tuner mode, but I am not sure what is the right way to switch between the tuners when the RSPduo is in master mode (assuming no other client application is using the slave instance, of course).

I just ran a couple of tests with CubicSDR (single user and master/slave case), and it seems to work here; give it a try and let me know how it goes.
Franco

Happy new year to all !
I made some tests on your last pseudo_devices_by_RSPduo_mode branch Franco.
It's working for most, I noticed 2 small things:
In single tuner mode now, if I select Zero IF mode I can't use sample rate more than 2 Mhz.
It's only while starting because during a SDR session in CubicSDR I can actually change and select other sample rate directly in CubicSDR menu (not in device selection dialog box).
So in Cubic it's not an issue but it may be for other Apps using SoapySDR ?
Second thing is about gain control. When I disable AGC I get  IFGR and RFGR.
I can change RFGR value is changed and it modify IFGR value.
If I change IFGR it doesn't stay at the selected level and cursor is going to another value.
It's like it's reversed, I don't remember this was working like this on older soapy driver but I may be wrong.

Happy new year to you too!
I looked into the issue about the Zero IF mode and I made a couple of changes to the SoapySDRPlay driver that should address it.
I also noticed in CubicSDR these lines (https://github.com/cjcliffe/CubicSDR/blob/master/src/sdr/SDREnumerator.cpp#L281):
                for (size_t j = 0; j < settingsInfo.size(); j++) {
                    if (deviceArgs.find(settingsInfo[j].key) != deviceArgs.end()) {
                        settingsInfo[j].value = deviceArgs[settingsInfo[j].key];
                    }
                }

If my understanding is correct, this means that whatever CubicSDR finds in its config file ($HOME/.CubicSDR/config.xml) prevails over the values returned by the call to the function 'getSettinInfo()' from the SoapySDR driver - in other words, even after I changed the driver to return 'Zero-IF' for the argument 'if_mode' when in single tuner mode, I kept seeing 1,620kHz under the 'SoapySDR Device Options' header in CubicSDR; it wasn't until I manually edited the config file, found the entry for the RSPduo device in single user mode, and changed the value for 'if_mode' there, that I was able to see 'Zero-IF' under the 'SoapySDR Device Options' header in CubicSDR.
Not sure if this is something that affects you too, but I thought you may want to know, just in case.
As per the question about gain control, I looked at the code for the SoapySDR driver (adapting the code for the various RSPduo modes didn't require any changes there, so what it does should be identical to the original driver), and I noticed the following:

'IFGR' affects the value of tunerParams.gain.gRdB, while 'RFGR' affects the value of 'tuneParams.gain.LNAstate'
while the LNAstate (i.e. RFGR) is pretty straighforward, for gRdB (i.e. IFGR) I noticed that the 'setGain()' method assigns the requested value to tunerParams.gain.gRdB, while the 'getGain()' method reads the value from tunerParams.gain.gainVals.curr - I am not exactly sure how these values are related ('gRdB' is the input value from the user, 'gainVals.curr' is the output value from the RSP device), but perhaps what you are seeing in CubicSDR is related to that (and I am fairly confident it was like that in the original driver).

During the holidays I also found out about another Linux SDR client: 'linhpsdr' by John Melton, G0ORX/N6LYT (his presentation at the SDR Academy 2018 is here: https://www.youtube.com/watch?v=GP0OgxfyCm0).
Since it looks like linhpsdr can make use of the SoapySDR driver (https://github.com/g0orx/linhpsdr/blob/master/Makefile#L20), I thought it would be a good candidate to make sure the SoapySDRPlay driver changes I am making play nice with other clients.
I made a few changes to the driver to also support linhpsdr (mainly I had the getHardwareKey() method return a good description of the RSPduo mode, not just the serial number), and I also made some changes to the linhpsdr source code itself to be able to support the 'sdrPlay' SoapySDR driver.
I still have some issues to figure out with the sdrplay_api_Init() function and the value for gRdB (I am sure I am passing 0, however sdrplay_api_Init() fails complaining that gRdB is set to 59 and therefore outside its allowed range). I plan to put my changes to the linhpsdr source code out on github tonight or tomorrow in case you want to give it a try.
Franco

Thank you very much Franco, no more issue with Zero FI mode.
I'll get a try with linhpsdr I tried when soapysdr was added, but it was not a great success.
I never managed to get it work with sdrplay.
I'll be happy to test your changes on it.

Hi @fventuri , @SDRplay  and all, I wish you a very Happy New Year ! I've not had time to test @fventuri  module, which don't stop me at delivering generic remarks.

I looked into the issue about the Zero IF mode and I made a couple of changes to the SoapySDRPlay driver that should address it.
I also noticed in CubicSDR these lines ...etc....
Not sure if this is something that affects you too, but I thought you may want to know, just in case.

The Devices initial dialog reads the config, checks it is within ranges, and try to apply it, that is all it can do.
At the risk of repeating myself, there is some core concept to understand :
Soapy settings are meant to be essentially independent options, where changing one setting don't affect the others.
If it is not the case, an application can only work properly if it has this feedback loop at its core:
change setting A (set) ==> Re-read all settings (get) ==> refresh all UI / inform user ==> repeat.
Else, the application is victim of the "pull the rug from under the feet" I mentionned earlier where a given setting change can affect other settings (or worse, the sample rate !!!) unknowingly of the application.
For Cubic we are lucky because the runtime Settings menu is actually completely refreshed each time a user change one setting. On the other hand, the other place it doesn't happen for a variety of reasons is the initial Devices. Do not  even try to fix it, It has a chicken-and-egg problem because it manage the first initialization, I've been there.
My personnal opinion is to only expose 'Sample rate' and 'Antenna' and such super-generic parameters there to prevent those problems, but I have no time available to work on that.
Now what about other apps than Cubic ? Could we expect them to re-scan their settings at a single option change to work properly ? Probably not.
To sum up IMHO Soapy API should make a best effort to only expose independent settings. Which means for instance removing access to Low IF / Zero-IF settings from the driver because it affects the sample rate. (on all RSP models).
I there drawbacks using the following ?

Sample rate < 2048 MHz => Make the driver use the best appropriate Low-IF mode
Sample rate > 2048 MHz ==> Zero-IF is the only option anyway (?)

As for the 'Devices' initial dialog, just admit that all the settings you put there won't necessarily be applied as is, because they may be inconsistent and the driver will eventually apply other things. Who cares ? Once started, the 'Sample rate' and 'Settings' menus will show the user the actual settings anyway.
Sorry if it sounds like a rant :)

'IFGR' affects the value of tunerParams.gain.gRdB, while 'RFGR' affects the value of 'tuneParams.gain.LNAstate'
while the LNAstate (i.e. RFGR) is pretty straighforward, for gRdB (i.e. IFGR) I noticed that the 'setGain()' method assigns the requested value to tunerParams.gain.gRdB, while the 'getGain()' method reads the value from tunerParams.gain.gainVals.curr - I am not exactly sure how these values are related ('gRdB' is the input value from the user, 'gainVals.curr' is the output value from the RSP device), but perhaps what you are seeing in CubicSDR is related to that (and I am fairly confident it was like that in the original driver).

RF Gain Select is also exposed as an artificial 'gain' using the listGains / setGain / getGain API so that a 'RFGR' gain slider is displayed on Cubic, because it is waaaay cooler than switching to the Settings menu. It is guarded by proper #ifdef for users who want only 'true' gains controls in dB, see here:  https://github.com/pothosware/SoapySDRPlay/pull/61
Speaking of a 'feedback loop', this a perfect example: Because I had to make Cubic generic, I couldn't hardcode the reciprocal update between  'RFGR' and 'RF Select setting', so I could only make the hypothesis that somehow changing any Settings could affect Gains, so I made the code to re-read the current gains using setGain and that it why you see it updating in realtime.
The fun part was that the Gain controls were indeed used as both command and control at the same time.  Don't do this at home.

Thanks for your comments, nmaster2042 and vsonnier.
I am perfectly OK with the way CubicSDR works and my comment earlier was just in the spirit of understanding why it behaves the way it does.
As suggested by you and SDRplay, I removed any reference to the tuner A or B in the public interface and public settings of the SoapySDRPlay driver - the user just chooses an antenna in CubicSDR, and the driver takes care of selecting the appropriate tuner.
As per the choice of the IF mode, I don't have any problem removing that too, and making it dependent on the sample rate, if SDRplay is OK with it.
When I started working on this SoapySDR driver, I thought it would be a good idea to make it "as close as possible" to the old SoapySDRPlay driver, and just add the code to make it work with the SDRplay API version 3.x and the various modes of the RSPduo, to avoid 'surprises' to the user; since the 'if_mode' was one of the settings in the old driver, I just kept it (and made the changes for the RSPduo).
As per my initial work on making the SoapySDRplay driver work with 'linhpsdr', if you want to try it, you can find my fork of 'linhpsdr' with a few changes to make it work here: https://github.com/fventuri/linhpsdr/tree/SDRplay-RSPs-with-SoaypSDR - I found that in order to avoid those problems I was having yesterday with 'sdrplay_api_Init()' and the value of gRdB being out of range, you have to create a linhpsdr property file called 'sdrPlay.props' and copy it under '$HOME/.local/share/linhpsdr'; the file in the github repo above is the one I created tonight, and the good news is that linhpsdr doesn't crash here now with that file. I saw another of those .props file in the same directory and it has an additional 700 or so properties, so I'll probably have to understand better this file in the next days, but at least it seems that linhpsdr could be another Linux client for the RSPs.
Franco

Hi @fventuri,

As per the choice of the IF mode, I don't have any problem removing that too, and making it dependent on the sample rate, if SDRplay is OK with it.
When I started working on this SoapySDR driver, I thought it would be a good idea to make it "as close as possible" to the old SoapySDRPlay driver, and just add the code to make it work with the SDRplay API version 3.x and the various modes of the RSPduo, to avoid 'surprises' to the user; since the 'if_mode' was one of the settings in the old driver, I just kept it (and made the changes for the RSPduo).

Well that was a mistake in the first place to introduce IF-control. A mistake I only fully understood by thinking about your recent hard work you made on this RSPDuo can of worms. I think we would better fix this misatke by removing the IF-control from the module entirely.
As for the Low-IF mode as implemented in the API v2.x, I switched to it once, Cubic did 'something' (wrong) and I quickly get back to "Zero-IF" never to touch IF again. I'm just taking care of not puting a demod in the Central Frequency because it get muffled because of DC-romoval, a drawback the Low-IF modes don't have, if I remenber correctly.
As to 'avoid surprises', the IF  modes control is by itself, a suprise in what it does related to the sample rate.
I suppose that if we want to plug the square of RSP into the round of a Soapy module, we must cut corners eventually.

The point of the API is to make all of the parameters available to the developer because the RSP chipset is so flexible. It's ok to simplify this to fit the SoapySDR framework from my point of view.
For API 3 you could offer just sample rate (as per my post above) - <= 2 MHz (not 2.048 MHz) can be Low-IF, > 2 MHz can be Zero-IF
Apply the IFBW as I suggested above for Low-IF and then for Zero-IF (2 - 10 MHz)...

2 MHz < 5 MHz sample rate, use 1.536 MHz
= 5 MHz < 6 MHz sample rate, use 5 MHz
= 6 MHz < 7 MHz sample rate, use 6 MHz
= 7 MHz < 8 MHz sample rate, use 7 MHz
= 8 MHz use 8 MHz

So that way you can just provide Sample Rate and hide IFBW and IFMode

I just want to clarify the gain control system. There are 2 independent gain control systems that combine to give an overall gain value. We use IFGR (IF gain reduction) and RFGR (RF gain reduction) because that is actually what is happening in the system. Depending on the GR setting, a certain amount of attenuation is applied from the maximum signal gain.
The IFGR has a range (by default) of 20 to 59 dB (in SDRuno we allow the user to flip the sense between IFGR or IF attenuation to IF gain) - The IFGR is the only gain system in the RSP to be controlled by AGC.
The RFGR has a varying range dependent on RSP type and LO frequency. It always goes from 0 (min RFGR / max RF gain) to N. We pad out the ranges so that a single maximum value (max RFGR / min RF gain) can be used for each RSP. The RFGR range is referred to as LNAstate. It has a varying step size and each state represents a certain dB value.
Again, in SDRuno, the vast majority of people will leave the IFGR to be controlled by the IFAGC and just control the RF gain slider. For each RF gain step, the IFGR has a 40 dB control range.
As all RSPs are calibrated back to the antenna port, there is a currGain parameter that is available in the event callback. Whenever there is a gain update (either by manual change or by the IFAGC) the event callback will be called with an updated currGain value. This is the total system GAIN. It is the combination of the current RFGR AND IFGR values and then the gain is calculated from the maximum system gain at the specified LO frequency for the current RSP.
I understand the desire to reduce everything down to a simplified version so that it can be compared to how existing SDR products work, but I would say that just because something works in a different way to what has gone before it, it doesn't mean that it's wrong. The gain system works the way it does for a reason.
I hope that helps.

Thanks for all your useful commends, vsonnier and SDRplay.
I created a new branch called 'IF_mode_dependent_on_sample_rate' (https://github.com/fventuri/SoapySDRPlay/tree/IF_mode_dependent_on_sample_rate), and I made the changes as requested.
Couple of comments:

to select the IF type based on the sample rate, I wrote this simple method that should choose it according to your criteria listed above:

sdrplay_api_If_kHzT SoapySDRPlay::getIfEnumForRate(const double rate) const
{
    // 1. RSPduo in dual tuner/master/slave mode
    if (device.hwVer == SDRPLAY_RSPduo_ID) {
        if (fabs(device.rspDuoSampleFreq - 6000000) < 10) {
            return sdrplay_api_IF_1_620;
        } else if (fabs(device.rspDuoSampleFreq - 8000000) < 10) {
            return sdrplay_api_IF_2_048;
        }
    }

    // everything else
    if (rate <= 2000000) {
        return sdrplay_api_IF_0_450;
    } else {
        return sdrplay_api_IF_Zero;
    }
}

Please review it and make sure it matches your requirements (I tried it quickly with the RSPduo both in single tuner mode and in master/slave configuration, and sdrplay_api_Init() didn't complain, which is a good sign). With this method I was able to remove the 'if_mode' argument, and now the user shouldn't have to worry about it in CubicSDR.

as per the automatic selection of the bandwidth, the original SoapySDRplay driver contained the method getBwEnumForRate(), which is already doing that (to this point, I ran CubicSDR and I couldn't see that the user can change/set the bandwidth anywhere, although the SoapySDR driver guide does mention a 'Bandwidth API' - https://github.com/pothosware/SoapySDR/wiki/DriverGuide#bandwidth-api). What I did tonight is simply add the case for the RSPduo in Dual Tuner/Master/Slave mode, and for that case I used the same values that the original driver was using for the Zero-IF case. Anyhow this is the code for that method with my change:

sdrplay_api_Bw_MHzT SoapySDRPlay::getBwEnumForRate(double rate, sdrplay_api_If_kHzT ifType, bool rspDuoNonSingleMode)
{
   if (ifType == sdrplay_api_IF_Zero || rspDuoNonSingleMode)
   {
      if      ((rate >= 200000)  && (rate < 300000))  return sdrplay_api_BW_0_200;
      else if ((rate >= 300000)  && (rate < 600000))  return sdrplay_api_BW_0_300;
      else if ((rate >= 600000)  && (rate < 1536000)) return sdrplay_api_BW_0_600;
      else if ((rate >= 1536000) && (rate < 5000000)) return sdrplay_api_BW_1_536;
      else if ((rate >= 5000000) && (rate < 6000000)) return sdrplay_api_BW_5_000;
      else if ((rate >= 6000000) && (rate < 7000000)) return sdrplay_api_BW_6_000;
      else if ((rate >= 7000000) && (rate < 8000000)) return sdrplay_api_BW_7_000;
      else                                            return sdrplay_api_BW_8_000;
   }
   else if ((ifType == sdrplay_api_IF_0_450) || (ifType == sdrplay_api_IF_1_620))
   {
      if      ((rate >= 200000)  && (rate < 500000))  return sdrplay_api_BW_0_200;
      else if ((rate >= 500000)  && (rate < 1000000)) return sdrplay_api_BW_0_300;
      else                                            return sdrplay_api_BW_0_600;
   }
   else
   {
      if      ((rate >= 200000)  && (rate < 500000))  return sdrplay_api_BW_0_200;
      else if ((rate >= 500000)  && (rate < 1000000)) return sdrplay_api_BW_0_300;
      else if ((rate >= 1000000) && (rate < 1536000)) return sdrplay_api_BW_0_600;
      else                                            return sdrplay_api_BW_1_536;
   }
}

Please review this one too to make sure it follows your recommendations.
Franco

Hi Franco,
Thanks for this - I think we can make this easier because in the new API we only need...
sdrplay_api_IF_Zero = Zero-IF
sdrplay_api_IF_1_620 = 6 MHz Low-IF
sdrplay_api_IF_2_048 = 8 MHz Low-IF
rspDuoSampleFreq is only important for Low-IF master/slave mode and it must be exactly 6 or 8, so I would say...
if master/slave then
if rspDuoSampleFreq == 6000000 then sdrplay_api_IF_1_620
else sdrplay_api_IF_2_048
For the IFBW this can also be made easier as it's not dependent on IFmode...
Note, rate is the FINAL sample rate, not the starting sample rate (also after any decimation, down conversion, etc.)
if      ((rate < 300000))  return sdrplay_api_BW_0_200;
else if ((rate >= 300000)  && (rate < 600000))  return sdrplay_api_BW_0_300;
else if ((rate >= 600000)  && (rate < 1536000)) return sdrplay_api_BW_0_600;
else if ((rate >= 1536000) && (rate < 5000000)) return sdrplay_api_BW_1_536;
else if ((rate >= 5000000) && (rate < 6000000)) return sdrplay_api_BW_5_000;
else if ((rate >= 6000000) && (rate < 7000000)) return sdrplay_api_BW_6_000;
else if ((rate >= 7000000) && (rate < 8000000)) return sdrplay_api_BW_7_000;
else                                            return sdrplay_api_BW_8_000;
Best regards,
Andy

Thanks Andy - I simplified those two methods as you suggested and this is what they reduced to:
sdrplay_api_If_kHzT SoapySDRPlay::getIfEnumForRate(const double rate) const
{
    // 1. RSPduo in dual tuner/master/slave mode
    if (device.hwVer == SDRPLAY_RSPduo_ID &&
            (device.rspDuoMode == sdrplay_api_RspDuoMode_Dual_Tuner ||
             device.rspDuoMode == sdrplay_api_RspDuoMode_Master ||
             device.rspDuoMode == sdrplay_api_RspDuoMode_Slave)) {
        if (device.rspDuoSampleFreq == 6000000) {
            return sdrplay_api_IF_1_620;
        } else {
            return sdrplay_api_IF_2_048;
        }
    }

    // everything else
    return sdrplay_api_IF_Zero;
}

sdrplay_api_Bw_MHzT SoapySDRPlay::getBwEnumForRate(double rate)
{
   if                            (rate < 300000)   return sdrplay_api_BW_0_200;
   else if ((rate >= 300000)  && (rate < 600000))  return sdrplay_api_BW_0_300;
   else if ((rate >= 600000)  && (rate < 1536000)) return sdrplay_api_BW_0_600;
   else if ((rate >= 1536000) && (rate < 5000000)) return sdrplay_api_BW_1_536;
   else if ((rate >= 5000000) && (rate < 6000000)) return sdrplay_api_BW_5_000;
   else if ((rate >= 6000000) && (rate < 7000000)) return sdrplay_api_BW_6_000;
   else if ((rate >= 7000000) && (rate < 8000000)) return sdrplay_api_BW_7_000;
   else                                            return sdrplay_api_BW_8_000;
}

Let me know if this what you meant.
If you are satisfied with the driver changes to handle the RSPduo and use the SDRplay API version 3.x, I would like to post something to the SDRplay Users Forum to see if a couple of people want to build it and use it, to try to get the major bugs/problems out of the way; after that I'll submit a PR to get the code in the api3 branch of the SoapySDRplay driver.
Franco

Hello Franco,
We'll be running internal tests with this starting today and I'll come back to you with feedback.
Thank you and to all for your efforts.
Best regards,
Andy

Hi @fventuri (and @fventuri ) I noticed on your latest proposed change:
sdrplay_api_If_kHzT SoapySDRPlay::getIfEnumForRate(const double rate) const
{
    // 1. RSPduo in dual tuner/master/slave mode
  ...(etc.)

    // everything else
    return sdrplay_api_IF_Zero;
}

Does it mean the IF-Mode won't be used ever internally for the other RSP models ?  I wondered if we could keep it in case of the final requested Sample Rate < 2MHz not to have the DC removal problem as a bonus for such lower sample rates.
Now, if it is more trouble than its worth in terms of driver complexity I understand completly.
Either way, for the final user we already had to explain in case of Zero-IF not ot stick a VFO in the Central Frequency so it won't change much in terms of explanation.
@fventuri  I really want to test your driver, but don't have time yet. I'll be able test it on Windows10 with a RSP2 and Cubic of course.

Very good point, vsonnier.
If I understand correctly the part after the comment 'everything else' should be replaced by something like this:
    if (rate < 2000000) {
        return sdrplay_api_IF_1_620;
    } else {
        return sdrplay_api_IF_Zero;
    }

I have no problem with that - one thing I noticed is that, if I run the RSPduo in single tuner mode with Zero-IF, I see no warnings, however if I run it in Low-IF mode, every few seconds I see warnings coming from acquireReadBuffer returning SOAPY_SDR_TIMEOUT (meaning that it takes more than 100ms for a new buffer to become available); I am not sure if it is my computer or something else ('top' doesn't show the CPU being very busy), but I thought I would mention it.
As per CubicSDR related changes, the only issue that I think is left is that CubicSDR ignores the return value from activateStream (https://github.com/cjcliffe/CubicSDR/blob/master/src/sdr/SoapySDRThread.cpp - line 455), which makes it impossible for a SoapySDR driver to inform CubicSDR that activateStream failed (this can happen in the SoapySDRPlay driver if there's any problem with sdrplay_api_Init()) - for now the driver will throw a runtime error to make sure this problem is addressed by the user, but I think it would be much better if CubicSDR were to handle failures coming from activateStream.
Finally I spent the last several days getting linhpsdr work with the SoapySDRPlay driver - I had to make several changes to linhpsdr and my modified version is here: https://github.com/fventuri/linhpsdr/tree/SDRplay-RSPs-with-SoaypSDR
One thing I was able to do with linhpsdr is to run the RSPduo in dual tuner mode and have linhpsdr display one receiver for each of the channels; it was while working on this task that I realized that I had the understanding of how streams work in SoapySDR all wrong; in other words I realized that client applications like linhsdr expect to have a different stream for each channel (while before I thought the usage for multiple receivers was to have one stream with multiple sets of buffers). Based on this assumption (one stream <-> one channel), I was able to clean up a lot of the code in the Streaming API section of the SoapySDRPlay driver.
I ran a few tests with both CubicSDR and linhpsdr and I didn't see any major problem (linhpsdr probably needs some more changes to work 100% right with two receivers), so I pushed these changes to the 'IF_mode_dependent_on_sample_rate' branch (https://github.com/fventuri/SoapySDRPlay/tree/IF_mode_dependent_on_sample_rate).
Franco

Hi @fventuri ,

I have no problem with that - one thing I noticed is that, if I run the RSPduo in single tuner mode with Zero-IF, I see no warnings, however if I run it in Low-IF mode, every few seconds I see warnings coming from acquireReadBuffer returning SOAPY_SDR_TIMEOUT (meaning that it takes more than 100ms for a new buffer to become available); I am not sure if it is my computer or something else

The important point is that in Zero-IF only, can you properly stream without timeouts between the whole range of advertised Sample rate  250Khz - 10 MHz ? (if applicable of course). If yes, then a suggest leaving Zero-IF as you did with a TODO comment in the code that explains this situation, so that Low-IF is not re-activated afterwards.
On the other hand, if not this is a real show-stopper IMO if the driver can't stream properly with high sample rates in particular. (I really, really need to start testing your driver !)
@SDRplay I suppose you can test it on the whole range of RSPs devices and various sample rates ?  Did you observe such behaviour ?

As per CubicSDR related changes, the only issue that I think is left is that CubicSDR ignores the return value from activateStream (https://github.com/cjcliffe/CubicSDR/blob/master/src/sdr/SoapySDRThread.cpp - line 455), which makes it impossible for a SoapySDR driver to inform CubicSDR that activateStream failed

Well at that point Cubic is supposed to have properly setup the device, and if it refuses to stream not much can't be done, return value or not. The best behaviour would be simply returning 0 I/Q samples down the road of DSP (like a 'blocking' situation) so the user can see that nothing is happening on the Waterfall and Spectrum side.
Throwing runtime execptions is certainly a bit harsh. I'll see what I can do on Cubic side.
@fventuri Thank you again for your investment on this, I can easily imagine how time-consuming it can be.

It's taken me a bit of time to get a proper installer sorted out for the Mac platform and now we've started testing so I'll get back to you shortly with feedback.
Just one comment on the sample rate note above. It should be <= 2 MHz and note this is the final sample rate, not the starting sample rate.
if (rate <= 2000000) {
    return sdrplay_api_IF_1_620;
} else {
    return sdrplay_api_IF_Zero;
}

We'll try all RSPs in varying modes, etc. including the RSPdx and RSPduo master/slave mode. Thanks for your efforts guys. If anyone is interested in testing with the Mac Installers I've built for the API, Soapy and CubicSDR (0.2.6) let me know here and I'll post the links to them. The usual health warnings apply :-)

I just pushed a few code changes to the 'API3+RSPduo' branch to take care of the following:

refactor reselectDevice() into deselectDevice() and selectDevice(); this way the code to select the device, get the device parameters, select the channel parameters pointer based on the active tuner, etc, is all in one place
I also found out that attempting to select a different mode for the RSPduo (through the 'SDR Devices' menu in CubicSDR) while streaming is active, causes CubicSDR to freeze (I think because the internal SDRplay API code is waiting for a mutex) - in order to notify the user about this issue, I added a warning message in findSDRPlay(), that lets the user know that the device is streaming.

One last thing - I am still trying to figure out how to prevent sdrplay_api_Init() to fail in the following scenario:

the user selects single tuner mode, and starts streaming from the RSPduo
after some time they stop streaming, switch to master mode, and stream again for some time
finally they stop streaming in master mode, go back to single tuner mode, and try to stream again

when they try to stream again in single tuner mode, I see that sdrplay_api_Init() fails and the logs show a message like this:
Feb 16 18:22:25 fvdesktop CubicSDR[10979]: [11050]: sdrplay_api: sdrplay_api_Init: ERROR: Failed to lock mutex

It might be just a corner case that no user will actually encounter, but I wanted to let you know in case you think it is worth investigating.
Franco

@fventuri I haven't checked the code, but are you releasing the device and selecting it again when switching between modes, because you need to. In SDRuno, when switching from single tuner to master, I release the device, set rspDuoMode to master and then select the device again. Same when going back to single tuner - of course you need to make sure the slave isn't in use before allowing the master mode to switch back to single tuner mode.
Does that help?
Andy

Good catch, Andy.
I wasn't reselecting the device (i.e. calling the API functions ReleaseDevice() + SelectDevice()) when switching back to the previous RSPduo mode.
I just pushed out the code with the fix to the API3+RSPduo branch; I ran a couple of quick tests and I was able to switch mode back and forth without any issue.
Thanks,
Franco

Hi.
I just made tests with the last fixes in API3+RSPduo branch and CubicSDR
with vsonier PR cjcliffe/CubicSDR#786.
If I  start in signe tuner mode, stop, select master mode and start again,
CubicSDR crashes and I have this error in the console:
SDR thread done.
[INFO] devIdx: 2
[INFO] hwVer: 3
[INFO] rspDuoMode: 4
[INFO] tuner: 1
[INFO] rspDuoSampleFreq: 6000000,000000
SDR thread starting.
device init()
[INFO] Using format CF32.
[ERROR] error in activateStream() - Init() failed: sdrplay_api_Fail
terminate called after throwing an instance of 'std::runtime_error'
what():  error in activateStream() - Init() failed
Abandon (core dumped)
Le lun. 17 févr. 2020 à 04:54, Franco Venturi<EMAIL_ADDRESS>a
écrit :

Good catch, Andy.
I wasn't reselecting the device (i.e. calling the API functions
ReleaseDevice() + SelectDevice()) when switching back to the previous
RSPduo mode.
I just pushed out the code with the fix to the API3+RSPduo branch; I ran a
couple of quick tests and I was able to switch mode back and forth without
any issue.
Thanks,
Franco
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/pothosware/SoapySDRPlay/issues/62?email_source=notifications&email_token=AFNR5KFH7FNA3VEVFFC2VJTRDIC7PA5CNFSM4J3UQHQKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEL47MDQ#issuecomment-586806798,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AFNR5KFYMAIXV7SYIUNMPXTRDIC7PANCNFSM4J3UQHQA
.

This issue occurs only when, at first streaming start in single tuner mode I set sample rate = 2048 khz (default option on my setup). The same test starting single tuner mode with sample rate = 2 mhz is ok.
Now if I start the first streaming in signle tuner mode with sample rate = 6 Mhz, when I change to master mode I get this on Cubic:

To get a proper working stream again, I have to go to the sample rate menu and change it for ex to 1 mhz and again to 2 mhz to fix.

The sample rate for master mode in the dropdown CANNOT be greater than 2 MHz - in fact, the library shouldn't accept or ideally not give any option to select a sample rate greater than 2 MHz in master mode. Internally the SoapySDRPlay library will use 6 MHz, but that is to deliver a final sample rate of 2 MHz - if you are able to attempt to switch from single tuner mode to master mode using a final sample rate of 6 MHz, then that's where the issue is.

@SDRplay  no, there is no issue about master mode and 2 mhz max sample rate.
I start in single tuner, I can chose whatever sample rate (ex: 6 mhz or 2.048 mhz).
I start streaming for few seconds like franco wrote.
Then I stop streaming, select device and chose master mode. Here I only get max of 2 mhz selectable values and I let default 2 mhz.
Finally I start streaming.
The issue is at this stage is that cubic is crashing if in the first start in single tuner mode has been done with sample rate of 2.048 mhz.

ok, well there is nothing fundamentally wrong with doing that, so something wrong in the sequencing - if you are able to capture a debug log, that would help. Maybe @fventuri can help with that?

Here is a strace while reproducing the issue I mentioned, I hope it can be usable.
cubic-trace.log

Unfortunately not, I need the DebugEnable statement enabled in the SoapySDRPlay library and then the output from that.

Thanks for the feedback nmaster2042 and Andy.
I just pushed a code change to the 'API3+RSPduo' branch that validates the output sample rate when the RSPduo is in master or slave mode and, if it is > 2MHz, sends a warning message and forces it to 2MHz.
This problem was happening because CubicSDR was calling the SoapySDRPlay driver method 'setSampleRate()' with a value of 2.048MHz for the output sample rate, and I was blindly passing it through to the device parameter structure without checking it first; this issue should be fixed now and CubicSDR should work as expected.
If a similar issue happens again and you are able to repeat it consistently, I'd like you do do the following:

edit the source files Settings.cpp and Streaming.cpp; in each of them search for the string 'Debug'; comment out the line with the parameter 'sdrplay_api_DbgLvl_Disable', and uncomment the line below with the parameter 'sdrplay_api_DbgLvl_Verbose' - you should have something like this:

    //sdrplay_api_DebugEnable(device.dev, sdrplay_api_DbgLvl_Disable);
    sdrplay_api_DebugEnable(device.dev, sdrplay_api_DbgLvl_Verbose);

build and install the SoapySDRPLay driver with debug enabled:

cd build
make
sudo make install

run CubicSDR and make the problem happen again
get the lines containing the string 'sdrplay_api' from the log file '/var/log/messages':

sudo grep sdrplay_api /var/log/messages > /tmp/sdrplay_api.txt'

the file '/tmp/sdrplay_api.txt' should contain a few hundreds/thousand lines; toward the end you should see something like this (posssibly with a different error message):

Feb 17 20:04:36 fvdesktop sdrplay_apiService[1836]: [2323]: devIdx0: sdrplay_apiService_rsp: checkDeviceParamLimits: ERROR: fsFreq fsHz out of range for master/slave mode(6000000.000000 or 8000000.000000) = 2048000.000000
Feb 17 20:04:36 fvdesktop sdrplay_apiService[1836]: [2323]: devIdx0: sdrplay_apiService_rsp: Init: ERROR: Tuner1: checkDeviceParamLimits returns error 3

please cut&paste those lines (and anything you see interesting in that '/tmp/sdrplay_api.txt' file), so we can troubleshoot why the SoapySDRPlay driver failed

to disable debug mode in the driver, you just have to revert back the two changes you made above to the source files Settings.cpp and Streaming.cpp, rebuild and reinstall the driver using the same commands I showed above.

Franco

Since I start seeing some 'git clone' activity on the repo, I just enabled the 'Issues' tab there (https://github.com/fventuri/SoapySDRPlay/issues), to be able to track the remaining issues before the pull request for the 'API3+RSPduo' branch.
Franco

@fventuri: I rebuilt the SoapySDRPlay with your last changes, the issue I pointed is now gone. I'll continue making other tests.


Switch from radio buttons to tables with selectable rows.

Motivated primarily by #489, but I think we should be consistent across all the components which need to offer this kind of interface.

@keller-mark : I think #510 fixed this? Closing.


Remove texture from side of raised platform

Would be good to make the sides a blank colour

No longer relevant - we now have a different arena.


fix: add named export for memoize

some bundlers confuse with default export in non-ESM build.

closing in favor of #27.


Make zstd_safe really safe

One part of being safe is to check error codes. But current implementation always return usize and return code must be checked in zstd-rs crate. This commit changes the behaviour that zstd-safe functions returns SafeResult.

Sure, I guess we can make this check in zstd-safe (the "safe" was for memory safety, which is not violated by returning raw codes). Just a few comments regarding the result type and the parse function.

Looks cool, thanks!


Fixes minor in PortugueseBrazilian locale

This fixes a minor grammar mistake in BrazilianPortuguese locale, past should not be fazem but faz.

Codecov Report

Merging #457 into master will not change coverage.
The diff coverage is 100%.

@@          Coverage Diff          @@
##           master   #457   +/-   ##
=====================================
  Coverage     100%   100%
=====================================
  Files          14     14
  Lines        3111   3111
  Branches      226    226
=====================================
  Hits         3111   3111

Impacted Files
Coverage Δ

arrow/locales.py
100% <100%> (ø)
:arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4205ece...e2dac95. Read the comment docs.


Host Unreachable error

On third attempt, host unreachable is displayed.
https://discord.com/channels/332511223536943105/1234574186161377363/1272234980998647859

Issue caused by joining a server with a password, then joining a server without a password - resolved in release <IP_ADDRESS>