diff options
-rw-r--r-- | gnuradio-runtime/python/gnuradio/__init__.py | 1 | ||||
-rw-r--r-- | gr-soapy/python/soapy/bindings/CMakeLists.txt | 1 | ||||
-rw-r--r-- | gr-soapy/python/soapy/bindings/python_bindings.cc | 2 | ||||
-rw-r--r-- | gr-soapy/python/soapy/bindings/soapy_types_python.cc | 182 | ||||
-rw-r--r-- | gr-soapy/python/soapy/qa_soapy_types.py | 92 |
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) |