mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
python: Make discoverable features available
This makes all discoverable features available and creates a pattern for any future discoverable features.
This commit is contained in:
parent
05443d301d
commit
538a2e3fb3
40
host/lib/features/features_python.hpp
Normal file
40
host/lib/features/features_python.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#ifndef INCLUDED_UHD_FEATURES_PYTHON_HPP
|
||||
#define INCLUDED_UHD_FEATURES_PYTHON_HPP
|
||||
|
||||
#include <uhd/features/discoverable_feature.hpp>
|
||||
#include <uhd/features/spi_getter_iface.hpp>
|
||||
|
||||
void export_discoverable_feature(py::module& m)
|
||||
{
|
||||
using discoverable_feature = uhd::features::discoverable_feature;
|
||||
using feature_id_t = discoverable_feature::feature_id_t;
|
||||
|
||||
py::enum_<feature_id_t>(m, "discoverable_feature_id")
|
||||
.value("RESERVED0", feature_id_t::RESERVED0)
|
||||
.value("RESERVED1", feature_id_t::RESERVED1)
|
||||
.value("FPGA_LOAD_NOTIFICATION", feature_id_t::FPGA_LOAD_NOTIFICATION)
|
||||
.value("ADC_SELF_CALIBRATION", feature_id_t::ADC_SELF_CALIBRATION)
|
||||
.value("REF_CLK_CALIBRATION", feature_id_t::REF_CLK_CALIBRATION)
|
||||
.value("TRIG_IO_MODE", feature_id_t::TRIG_IO_MODE)
|
||||
.value("GPIO_POWER", feature_id_t::GPIO_POWER)
|
||||
.value("SPI_GETTER_IFACE", feature_id_t::SPI_GETTER_IFACE)
|
||||
.value("INTERNAL_SYNC", feature_id_t::INTERNAL_SYNC)
|
||||
.value("GPS", feature_id_t::GPS)
|
||||
.value("TX_COMPLEX_GAIN", feature_id_t::TX_COMPLEX_GAIN)
|
||||
.value("RX_COMPLEX_GAIN", feature_id_t::RX_COMPLEX_GAIN);
|
||||
|
||||
py::class_<uhd::features::spi_periph_config_t>(m, "spi_periph_config")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("periph_cs", &uhd::features::spi_periph_config_t::periph_cs)
|
||||
.def_readwrite("periph_sdi", &uhd::features::spi_periph_config_t::periph_sdi)
|
||||
.def_readwrite("periph_sdo", &uhd::features::spi_periph_config_t::periph_sdo)
|
||||
.def_readwrite("periph_clk", &uhd::features::spi_periph_config_t::periph_clk);
|
||||
}
|
||||
|
||||
#endif // INCLUDED_UHD_FEATURES_PYTHON_HPP
|
||||
@ -6,15 +6,86 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <uhd/features/adc_self_calibration_iface.hpp>
|
||||
#include <uhd/features/complex_gain_iface.hpp>
|
||||
#include <uhd/features/discoverable_feature.hpp>
|
||||
#include <uhd/features/internal_sync_iface.hpp>
|
||||
#include <uhd/features/spi_getter_iface.hpp>
|
||||
#include <uhd/rfnoc/block_controller_factory_python.hpp>
|
||||
#include <uhd/rfnoc/radio_control.hpp>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
using namespace uhd::rfnoc;
|
||||
|
||||
namespace {
|
||||
|
||||
using feature_id_t = uhd::features::discoverable_feature::feature_id_t;
|
||||
|
||||
bool radio_has_feature(radio_control& self, const feature_id_t feature_id)
|
||||
{
|
||||
switch (feature_id) {
|
||||
case uhd::features::discoverable_feature::INTERNAL_SYNC:
|
||||
return self.has_feature<uhd::features::internal_sync_iface>();
|
||||
case uhd::features::discoverable_feature::TX_COMPLEX_GAIN:
|
||||
return self.has_feature<uhd::features::tx_complex_gain_iface>();
|
||||
case uhd::features::discoverable_feature::RX_COMPLEX_GAIN:
|
||||
return self.has_feature<uhd::features::rx_complex_gain_iface>();
|
||||
case uhd::features::discoverable_feature::SPI_GETTER_IFACE:
|
||||
return self.has_feature<uhd::features::spi_getter_iface>();
|
||||
case uhd::features::discoverable_feature::ADC_SELF_CALIBRATION:
|
||||
return self.has_feature<uhd::features::adc_self_calibration_iface>();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
py::object radio_get_feature_obj(radio_control& self, const feature_id_t feature_id)
|
||||
{
|
||||
switch (feature_id) {
|
||||
case uhd::features::discoverable_feature::INTERNAL_SYNC:
|
||||
if (!self.has_feature<uhd::features::internal_sync_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(&self.get_feature<uhd::features::internal_sync_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
case uhd::features::discoverable_feature::TX_COMPLEX_GAIN:
|
||||
if (!self.has_feature<uhd::features::tx_complex_gain_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(&self.get_feature<uhd::features::tx_complex_gain_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
case uhd::features::discoverable_feature::RX_COMPLEX_GAIN:
|
||||
if (!self.has_feature<uhd::features::rx_complex_gain_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(&self.get_feature<uhd::features::rx_complex_gain_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
case uhd::features::discoverable_feature::SPI_GETTER_IFACE:
|
||||
if (!self.has_feature<uhd::features::spi_getter_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(&self.get_feature<uhd::features::spi_getter_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
case uhd::features::discoverable_feature::ADC_SELF_CALIBRATION:
|
||||
if (!self.has_feature<uhd::features::adc_self_calibration_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(
|
||||
&self.get_feature<uhd::features::adc_self_calibration_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
default:
|
||||
throw uhd::key_error(
|
||||
"Feature ID is not currently bound in the Python API for radio_control.");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void export_radio_control(py::module& m)
|
||||
{
|
||||
// Re-import ALL_CHANS here to avoid linker errors
|
||||
@ -37,9 +108,24 @@ void export_radio_control(py::module& m)
|
||||
py::arg("chan"),
|
||||
py::arg("time") = py::none())
|
||||
.def("get_gain_coeff", &uhd::features::rx_complex_gain_iface::get_gain_coeff);
|
||||
py::class_<uhd::features::adc_self_calibration_iface>(m, "adc_self_calibration")
|
||||
.def("run",
|
||||
static_cast<void (uhd::features::adc_self_calibration_iface::*)(
|
||||
const size_t)>(&uhd::features::adc_self_calibration_iface::run),
|
||||
py::arg("chan"))
|
||||
.def("run",
|
||||
static_cast<void (uhd::features::adc_self_calibration_iface::*)(
|
||||
const size_t, const uhd::device_addr_t)>(
|
||||
&uhd::features::adc_self_calibration_iface::run),
|
||||
py::arg("chan"),
|
||||
py::arg("params"));
|
||||
|
||||
py::class_<radio_control, noc_block_base, radio_control::sptr>(m, "radio_control")
|
||||
.def(py::init(&block_controller_factory<radio_control>::make_from))
|
||||
.def("enumerate_features",
|
||||
[](radio_control& self) { return self.enumerate_features(); })
|
||||
.def("has_feature", &radio_has_feature, py::arg("feature_id"))
|
||||
.def("get_feature", &radio_get_feature_obj, py::arg("feature_id"))
|
||||
.def("set_rate", &radio_control::set_rate)
|
||||
.def("get_rate", &radio_control::get_rate)
|
||||
.def("get_rate_range", &radio_control::get_rate_range)
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
#include <uhd/features/gpio_power_iface.hpp>
|
||||
#include <uhd/features/gps_iface.hpp>
|
||||
#include <uhd/features/ref_clk_calibration_iface.hpp>
|
||||
#include <uhd/features/spi_getter_iface.hpp>
|
||||
#include <uhd/features/trig_io_mode_iface.hpp>
|
||||
#include <uhd/rfnoc/block_id.hpp>
|
||||
#include <uhd/rfnoc/filter_node.hpp>
|
||||
#include <uhd/rfnoc/graph_edge.hpp>
|
||||
@ -22,7 +24,9 @@
|
||||
#include <uhd/rfnoc_graph.hpp>
|
||||
#include <uhd/transport/adapter_id.hpp>
|
||||
#include <uhd/types/device_addr.hpp>
|
||||
#include <uhd/types/trig_io_mode.hpp>
|
||||
#include <uhd/utils/graph_utils.hpp>
|
||||
#include <uhdlib/features/fpga_load_notification_iface.hpp>
|
||||
#include <pybind11/operators.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <memory>
|
||||
@ -33,6 +37,72 @@ using namespace uhd::rfnoc;
|
||||
|
||||
namespace { // anon
|
||||
|
||||
using feature_id_t = uhd::features::discoverable_feature::feature_id_t;
|
||||
|
||||
bool mb_has_feature(uhd::rfnoc::mb_controller& self, const feature_id_t feature_id)
|
||||
{
|
||||
switch (feature_id) {
|
||||
case uhd::features::discoverable_feature::FPGA_LOAD_NOTIFICATION:
|
||||
return self.has_feature<uhd::features::fpga_load_notification_iface>();
|
||||
case uhd::features::discoverable_feature::GPIO_POWER:
|
||||
return self.has_feature<uhd::features::gpio_power_iface>();
|
||||
case uhd::features::discoverable_feature::GPS:
|
||||
return self.has_feature<uhd::features::gps_iface>();
|
||||
case uhd::features::discoverable_feature::REF_CLK_CALIBRATION:
|
||||
return self.has_feature<uhd::features::ref_clk_calibration_iface>();
|
||||
case uhd::features::discoverable_feature::TRIG_IO_MODE:
|
||||
return self.has_feature<uhd::features::trig_io_mode_iface>();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
py::object mb_get_feature_obj(
|
||||
uhd::rfnoc::mb_controller& self, const feature_id_t feature_id)
|
||||
{
|
||||
switch (feature_id) {
|
||||
case uhd::features::discoverable_feature::FPGA_LOAD_NOTIFICATION:
|
||||
if (!self.has_feature<uhd::features::fpga_load_notification_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(
|
||||
&self.get_feature<uhd::features::fpga_load_notification_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
case uhd::features::discoverable_feature::GPIO_POWER:
|
||||
if (!self.has_feature<uhd::features::gpio_power_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(&self.get_feature<uhd::features::gpio_power_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
case uhd::features::discoverable_feature::GPS:
|
||||
if (!self.has_feature<uhd::features::gps_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(&self.get_feature<uhd::features::gps_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
case uhd::features::discoverable_feature::REF_CLK_CALIBRATION:
|
||||
if (!self.has_feature<uhd::features::ref_clk_calibration_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(&self.get_feature<uhd::features::ref_clk_calibration_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
case uhd::features::discoverable_feature::TRIG_IO_MODE:
|
||||
if (!self.has_feature<uhd::features::trig_io_mode_iface>()) {
|
||||
return py::none();
|
||||
}
|
||||
return py::cast(&self.get_feature<uhd::features::trig_io_mode_iface>(),
|
||||
py::return_value_policy::reference_internal,
|
||||
py::cast(&self));
|
||||
default:
|
||||
throw uhd::key_error(
|
||||
"Feature ID is not currently bound in the Python API for mb_controller.");
|
||||
}
|
||||
}
|
||||
|
||||
using timekeeper = mb_controller::timekeeper;
|
||||
|
||||
// Make a "trampoline" class to help Pybind11
|
||||
@ -72,6 +142,36 @@ void export_rfnoc(py::module& m)
|
||||
{
|
||||
#define RIS_FIELD(name) .def_readwrite(#name, ®ister_iface_stats::name)
|
||||
|
||||
py::class_<uhd::features::discoverable_feature,
|
||||
uhd::features::discoverable_feature::sptr>(m, "discoverable_feature")
|
||||
.def("get_feature_name", &uhd::features::discoverable_feature::get_feature_name);
|
||||
|
||||
py::class_<uhd::spi_iface, uhd::spi_iface::sptr>(m, "spi_iface")
|
||||
.def("transact_spi",
|
||||
&uhd::spi_iface::transact_spi,
|
||||
py::arg("which_slave"),
|
||||
py::arg("config"),
|
||||
py::arg("data"),
|
||||
py::arg("num_bits"),
|
||||
py::arg("readback"))
|
||||
.def("read_spi",
|
||||
&uhd::spi_iface::read_spi,
|
||||
py::arg("which_slave"),
|
||||
py::arg("config"),
|
||||
py::arg("data"),
|
||||
py::arg("num_bits"))
|
||||
.def("write_spi",
|
||||
&uhd::spi_iface::write_spi,
|
||||
py::arg("which_slave"),
|
||||
py::arg("config"),
|
||||
py::arg("data"),
|
||||
py::arg("num_bits"));
|
||||
|
||||
py::class_<uhd::features::spi_getter_iface,
|
||||
uhd::features::spi_getter_iface::sptr,
|
||||
uhd::features::discoverable_feature>(m, "spi_getter")
|
||||
.def("get_spi_ref", &uhd::features::spi_getter_iface::get_spi_ref);
|
||||
|
||||
py::class_<register_iface_stats>(m, "register_iface_stats")
|
||||
// clang-format off
|
||||
RIS_FIELD(ctrl_packets_sent)
|
||||
@ -289,6 +389,17 @@ void export_rfnoc(py::module& m)
|
||||
.def("store_ref_clk_tuning_word",
|
||||
&uhd::features::ref_clk_calibration_iface::store_ref_clk_tuning_word);
|
||||
|
||||
py::enum_<uhd::trig_io_mode_t>(m, "trig_io_mode_t")
|
||||
.value("PPS_OUTPUT", uhd::trig_io_mode_t::PPS_OUTPUT)
|
||||
.value("INPUT", uhd::trig_io_mode_t::INPUT)
|
||||
.value("OFF", uhd::trig_io_mode_t::OFF);
|
||||
|
||||
py::class_<uhd::features::fpga_load_notification_iface>(m, "fpga_load_notification")
|
||||
.def("onload", &uhd::features::fpga_load_notification_iface::onload);
|
||||
|
||||
py::class_<uhd::features::trig_io_mode_iface>(m, "trig_io_mode")
|
||||
.def("set_trig_io_mode", &uhd::features::trig_io_mode_iface::set_trig_io_mode);
|
||||
|
||||
py::class_<detail::filter_node>(m, "filter_node")
|
||||
.def("get_rx_filter_names", &detail::filter_node::get_rx_filter_names)
|
||||
.def("get_rx_filter", &detail::filter_node::get_rx_filter)
|
||||
@ -300,6 +411,10 @@ void export_rfnoc(py::module& m)
|
||||
py::class_<mb_controller, mb_controller::sptr>(m, "mb_controller")
|
||||
.def("get_num_timekeepers", &mb_controller::get_num_timekeepers)
|
||||
.def("get_timekeeper", &mb_controller::get_timekeeper)
|
||||
.def("enumerate_features",
|
||||
[](mb_controller& self) { return self.enumerate_features(); })
|
||||
.def("has_feature", &mb_has_feature, py::arg("feature_id"))
|
||||
.def("get_feature", &mb_get_feature_obj, py::arg("feature_id"))
|
||||
.def("init", &mb_controller::init)
|
||||
.def("get_mboard_name", &mb_controller::get_mboard_name)
|
||||
.def("set_time_source", &mb_controller::set_time_source)
|
||||
|
||||
@ -20,7 +20,9 @@ void export_spi_config(py::module& m)
|
||||
.value("EDGE_FALL", spi_edge_t::EDGE_FALL);
|
||||
|
||||
py::class_<spi_config_t>(m, "spi_config")
|
||||
.def(py::init<>())
|
||||
.def(py::init<spi_edge_t>())
|
||||
.def(py::init<spi_edge_t, size_t>(), py::arg("edge"), py::arg("divider"))
|
||||
|
||||
// Properties
|
||||
.def_readwrite("mosi_edge", &spi_config_t::mosi_edge)
|
||||
|
||||
@ -16,6 +16,7 @@ namespace py = pybind11;
|
||||
|
||||
#include "cal/cal_python.hpp"
|
||||
#include "device_python.hpp"
|
||||
#include "features/features_python.hpp"
|
||||
#include "include/uhdlib/usrp/common/max287x_python.hpp"
|
||||
#include "property_tree_python.hpp"
|
||||
#include "rfnoc/ddc_block_control_python.hpp"
|
||||
@ -87,6 +88,7 @@ PYBIND11_MODULE(libpyuhd, m)
|
||||
export_types(types_module);
|
||||
export_time_spec(types_module);
|
||||
export_spi_config(types_module);
|
||||
export_discoverable_feature(types_module);
|
||||
export_metadata(types_module);
|
||||
export_sensors(types_module);
|
||||
export_tune(types_module);
|
||||
|
||||
@ -34,5 +34,9 @@ SwitchboardBlockControl = lib.rfnoc.switchboard_block_control
|
||||
VectorIirBlockControl = lib.rfnoc.vector_iir_block_control
|
||||
WindowBlockControl = lib.rfnoc.window_block_control
|
||||
|
||||
# Dynamic discoverable-feature API exports (CamelCase only)
|
||||
SpiIface = lib.rfnoc.spi_iface
|
||||
SpiGetter = lib.rfnoc.spi_getter
|
||||
|
||||
connect_through_blocks = lib.rfnoc.connect_through_blocks
|
||||
get_block_chain = lib.rfnoc.get_block_chain
|
||||
|
||||
@ -33,3 +33,5 @@ TuneResult = lib.types.tune_result
|
||||
separate_device_addr = lib.types.separate_device_addr
|
||||
combine_device_addrs = lib.types.combine_device_addrs
|
||||
Direction = lib.types.direction_t
|
||||
DiscoverableFeatureID = lib.types.discoverable_feature_id
|
||||
SPIPeriphConfig = lib.types.spi_periph_config
|
||||
|
||||
Loading…
Reference in New Issue
Block a user