summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gnuradio-runtime/python/gnuradio/__init__.py1
-rw-r--r--gr-soapy/python/soapy/bindings/CMakeLists.txt1
-rw-r--r--gr-soapy/python/soapy/bindings/python_bindings.cc2
-rw-r--r--gr-soapy/python/soapy/bindings/soapy_types_python.cc182
-rw-r--r--gr-soapy/python/soapy/qa_soapy_types.py92
5 files changed, 278 insertions, 0 deletions
diff --git a/gnuradio-runtime/python/gnuradio/__init__.py b/gnuradio-runtime/python/gnuradio/__init__.py
index 45bc91bf4d..7e8633b746 100644
--- a/gnuradio-runtime/python/gnuradio/__init__.py
+++ b/gnuradio-runtime/python/gnuradio/__init__.py
@@ -59,3 +59,4 @@ if path.endswith(path_ending):
__path__.append(os.path.join(build_path, 'gr-pdu', 'python'))
__path__.append(os.path.join(build_path, 'gr-network', 'python'))
__path__.append(os.path.join(build_path, 'gr-zeromq', 'python'))
+ __path__.append(os.path.join(build_path, 'gr-soapy', 'python'))
diff --git a/gr-soapy/python/soapy/bindings/CMakeLists.txt b/gr-soapy/python/soapy/bindings/CMakeLists.txt
index a91f9c60d4..902cf9c42b 100644
--- a/gr-soapy/python/soapy/bindings/CMakeLists.txt
+++ b/gr-soapy/python/soapy/bindings/CMakeLists.txt
@@ -13,6 +13,7 @@ include(GrPybind)
list(APPEND soapy_python_files
block_python.cc
+ soapy_types_python.cc
source_python.cc
sink_python.cc
python_bindings.cc)
diff --git a/gr-soapy/python/soapy/bindings/python_bindings.cc b/gr-soapy/python/soapy/bindings/python_bindings.cc
index a2f84d1139..26a6f36453 100644
--- a/gr-soapy/python/soapy/bindings/python_bindings.cc
+++ b/gr-soapy/python/soapy/bindings/python_bindings.cc
@@ -14,6 +14,7 @@
namespace py = pybind11;
+void bind_soapy_types(py::module& m);
void bind_block(py::module& m);
void bind_source(py::module& m);
void bind_sink(py::module& m);
@@ -37,6 +38,7 @@ PYBIND11_MODULE(soapy_python, m)
// Allow access to base block methods
py::module::import("gnuradio.gr");
+ bind_soapy_types(m);
bind_block(m);
bind_source(m);
bind_sink(m);
diff --git a/gr-soapy/python/soapy/bindings/soapy_types_python.cc b/gr-soapy/python/soapy/bindings/soapy_types_python.cc
new file mode 100644
index 0000000000..886011a19b
--- /dev/null
+++ b/gr-soapy/python/soapy/bindings/soapy_types_python.cc
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2021 Free Software Foundation, Inc.
+ *
+ * This file is part of GNU Radio
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include <pybind11/complex.h>
+#include <pybind11/operators.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+#include <SoapySDR/Types.hpp>
+#include <SoapySDR/Version.h>
+
+// SoapySDR doesn't have an API define for SettingToString, so we need
+// to check the version. 0.8 is the first tagged version to have this
+// functionality.
+#if SOAPY_SDR_API_VERSION >= 0x080000
+
+template <typename T>
+static inline T string_to_setting(const std::string& str)
+{
+ return SoapySDR::StringToSetting<T>(str);
+}
+
+template <typename T>
+static inline std::string setting_to_string(const T& setting)
+{
+ return SoapySDR::SettingToString<T>(setting);
+}
+
+#else
+
+// Copied from SoapySDR 0.8
+#define SOAPY_SDR_TRUE "true"
+#define SOAPY_SDR_FALSE "false"
+
+#include <sstream>
+
+template <typename T>
+static inline T string_to_setting(const std::string& str)
+{
+ std::stringstream sstream(str);
+ T setting;
+
+ sstream >> setting;
+
+ return setting;
+}
+
+// Copied from SoapySDR 0.8
+template <>
+inline bool string_to_setting<bool>(const std::string& str)
+{
+ if (str == SOAPY_SDR_TRUE)
+ return true;
+ if (str == SOAPY_SDR_FALSE)
+ return false;
+
+ // zeros and empty strings are false
+ if (str == "0")
+ return false;
+ if (str == "0.0")
+ return false;
+ if (str == "")
+ return false;
+
+ // other values are true
+ return true;
+}
+
+template <typename T>
+static inline std::string setting_to_string(const T& setting)
+{
+ return std::to_string(setting);
+}
+
+template <>
+inline std::string setting_to_string<bool>(const bool& setting)
+{
+ return setting ? SOAPY_SDR_TRUE : SOAPY_SDR_FALSE;
+}
+
+#endif
+
+void bind_soapy_types(py::module& m)
+{
+ py::class_<SoapySDR::Range>(m, "range")
+ // Constructors
+ .def(py::init<>())
+ .def(py::init<double, double>())
+ .def(py::init<double, double, double>())
+
+ // Methods
+ .def("minimum", &SoapySDR::Range::minimum)
+ .def("maximum", &SoapySDR::Range::maximum)
+ .def("step", &SoapySDR::Range::step)
+
+ .def("__str__", [](const SoapySDR::Range& range) -> std::string {
+ std::string ret = "(minimum: )";
+ ret += std::to_string(range.minimum());
+ ret += ", maximum: ";
+ ret += std::to_string(range.maximum());
+ ret += ", step: ";
+ ret += std::to_string(range.step());
+ ret += ")";
+
+ return ret;
+ });
+
+ // decltype needed because the "type" field is an anonymous enum
+ py::enum_<decltype(SoapySDR::ArgInfo::type)>(m, "argtype")
+ .value("BOOL", SoapySDR::ArgInfo::BOOL)
+ .value("INT", SoapySDR::ArgInfo::INT)
+ .value("FLOAT", SoapySDR::ArgInfo::FLOAT)
+ .value("STRING", SoapySDR::ArgInfo::STRING)
+ .export_values();
+
+ py::class_<SoapySDR::ArgInfo>(m, "arginfo")
+ // Constructors
+ .def(py::init<>())
+
+ // Properties
+ .def_readwrite("key", &SoapySDR::ArgInfo::key)
+ .def_property(
+ "value",
+ [](const SoapySDR::ArgInfo& arginfo) -> py::object {
+ py::object ret;
+ switch (arginfo.type) {
+ case SoapySDR::ArgInfo::BOOL:
+ ret = py::bool_(string_to_setting<bool>(arginfo.value));
+ break;
+
+ case SoapySDR::ArgInfo::INT:
+ ret = py::int_(string_to_setting<int>(arginfo.value));
+ break;
+
+ case SoapySDR::ArgInfo::FLOAT:
+ ret = py::float_(string_to_setting<double>(arginfo.value));
+ break;
+
+ default:
+ ret = py::str(arginfo.value);
+ break;
+ }
+
+ return ret;
+ },
+ // So we can implicitly convert to Soapy's convention
+ [](SoapySDR::ArgInfo& arginfo, py::object obj) -> void {
+ if (py::isinstance<py::bool_>(obj)) {
+ arginfo.value = setting_to_string(bool(py::cast<py::bool_>(obj)));
+ arginfo.type = SoapySDR::ArgInfo::BOOL;
+ } else if (py::isinstance<py::int_>(obj)) {
+ arginfo.value = setting_to_string(int(py::cast<py::int_>(obj)));
+ arginfo.type = SoapySDR::ArgInfo::INT;
+ } else if (py::isinstance<py::float_>(obj)) {
+ arginfo.value = setting_to_string(double(py::cast<py::float_>(obj)));
+ arginfo.type = SoapySDR::ArgInfo::FLOAT;
+ } else {
+ arginfo.value = py::str(obj);
+ arginfo.type = SoapySDR::ArgInfo::STRING;
+ }
+ })
+
+ .def_readwrite("name", &SoapySDR::ArgInfo::name)
+ .def_readwrite("description", &SoapySDR::ArgInfo::description)
+ .def_readwrite("units", &SoapySDR::ArgInfo::units)
+ .def_readwrite("type", &SoapySDR::ArgInfo::type)
+ .def_readwrite("range", &SoapySDR::ArgInfo::range)
+ .def_readwrite("options", &SoapySDR::ArgInfo::options)
+ .def_readwrite("option_names", &SoapySDR::ArgInfo::optionNames)
+
+ .def("__str__", [](const SoapySDR::ArgInfo& arginfo) -> std::string {
+ return (arginfo.key + "=" + arginfo.value);
+ });
+}
diff --git a/gr-soapy/python/soapy/qa_soapy_types.py b/gr-soapy/python/soapy/qa_soapy_types.py
new file mode 100644
index 0000000000..5b7c7d2987
--- /dev/null
+++ b/gr-soapy/python/soapy/qa_soapy_types.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+#
+# Copyright 2021 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from gnuradio import gr, gr_unittest, blocks
+from gnuradio import soapy
+
+class test_soapy_types(gr_unittest.TestCase):
+
+ def test_range(self):
+ test_range = soapy.range()
+ self.assertAlmostEqual(test_range.minimum(), 0.0, 9)
+ self.assertAlmostEqual(test_range.maximum(), 0.0, 9)
+ # Don't check step, uninitialized in old Soapy versions
+
+ test_range = soapy.range(-1.23456, 1.23456)
+ self.assertAlmostEqual(test_range.minimum(), -1.23456, 9)
+ self.assertAlmostEqual(test_range.maximum(), 1.23456, 9)
+ self.assertAlmostEqual(test_range.step(), 0.0, 9)
+
+ test_range = soapy.range(-7.89012, 7.89012, 0.2468)
+ self.assertAlmostEqual(test_range.minimum(), -7.89012, 9)
+ self.assertAlmostEqual(test_range.maximum(), 7.89012, 9)
+ self.assertAlmostEqual(test_range.step(), 0.2468, 9)
+
+ def test_arginfo(self):
+ test_arginfo = soapy.arginfo()
+
+ test_arginfo.key = "testkey"
+ self.assertEqual(test_arginfo.key, "testkey")
+
+ test_arginfo.name = "testname"
+ self.assertEqual(test_arginfo.name, "testname")
+
+ test_arginfo.description = "testdescription"
+ self.assertEqual(test_arginfo.description, "testdescription")
+
+ test_arginfo.units = "testunits"
+ self.assertEqual(test_arginfo.units, "testunits")
+
+ for val in [soapy.argtype.BOOL, soapy.argtype.INT, soapy.argtype.FLOAT, soapy.argtype.STRING]:
+ test_arginfo.type = val
+ self.assertEqual(test_arginfo.type, val)
+
+ test_arginfo.range = soapy.range(1,2,0.5)
+ self.assertAlmostEqual(test_arginfo.range.minimum(), 1, 9)
+ self.assertAlmostEqual(test_arginfo.range.maximum(), 2, 9)
+ self.assertAlmostEqual(test_arginfo.range.step(), 0.5, 9)
+
+ test_arginfo.options = ["opt1", "opt2", "opt3"]
+ self.assertEqual(len(test_arginfo.options), 3)
+ self.assertEqual(test_arginfo.options[0], "opt1")
+ self.assertEqual(test_arginfo.options[1], "opt2")
+ self.assertEqual(test_arginfo.options[2], "opt3")
+
+ test_arginfo.option_names = ["Option1", "Option2", "Option3"]
+ self.assertEqual(len(test_arginfo.option_names), 3)
+ self.assertEqual(test_arginfo.option_names[0], "Option1")
+ self.assertEqual(test_arginfo.option_names[1], "Option2")
+ self.assertEqual(test_arginfo.option_names[2], "Option3")
+
+ #
+ # Test all value types
+ #
+
+ test_arginfo.value = "testvalue"
+ self.assertEqual(test_arginfo.type, soapy.argtype.STRING)
+ self.assertEqual(type(test_arginfo.value), str)
+ self.assertEqual(test_arginfo.value, "testvalue")
+
+ test_arginfo.value = False
+ self.assertEqual(test_arginfo.type, soapy.argtype.BOOL)
+ self.assertEqual(type(test_arginfo.value), bool)
+ self.assertFalse(test_arginfo.value)
+
+ test_arginfo.value = 100
+ self.assertEqual(test_arginfo.type, soapy.argtype.INT)
+ self.assertEqual(type(test_arginfo.value), int)
+ self.assertEqual(test_arginfo.value, 100)
+
+ test_arginfo.value = 1.23
+ self.assertEqual(test_arginfo.type, soapy.argtype.FLOAT)
+ self.assertEqual(type(test_arginfo.value), float)
+ self.assertAlmostEqual(test_arginfo.value, 1.23, 6)
+
+
+if __name__ == '__main__':
+ gr_unittest.run(test_soapy_types)