extension: Add extension example

Added example for extension framework which can be used as template for
own extensions.
This commit is contained in:
Martin Anderseck 2022-07-28 17:38:34 +02:00 committed by skooNI
parent 4789c32f40
commit 4f993faf13
6 changed files with 638 additions and 0 deletions

View File

@ -0,0 +1,70 @@
#
# Copyright 2014-2015 Ettus Research LLC
# Copyright 2022 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
cmake_minimum_required(VERSION 3.5.1)
project(EXTENSION_EXAMPLE CXX)
### Configure Compiler ########################################################
set(CMAKE_CXX_STANDARD 11)
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
set(CMAKE_EXE_LINKER_FLAGS "-lthr ${CMAKE_EXE_LINKER_FLAGS}")
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
### Set up build environment ##################################################
# Choose a static or shared-library build (shared is default, and static will
# probably need some special care!)
# Set this to ON in order to link a static build of UHD:
option(UHD_USE_STATIC_LIBS OFF)
# To add UHD as a dependency to this project, add a line such as this:
find_package(UHD 4.3.0 REQUIRED)
# The version in ^^^^^ here is a minimum version.
# We assume that in UHD 4.3 all requirements are met, however additional checks
# for extensions.hpp could be performed if desired.
# To specify an exact version:
#find_package(UHD 4.0.0 EXACT REQUIRED)
# This example also requires Boost.
# Set components here, then include UHDBoost to do the actual finding
set(UHD_BOOST_REQUIRED_COMPONENTS
program_options
system
thread
)
set(BOOST_MIN_VERSION 1.71)
include(UHDBoost)
# need these include and link directories for the build
include_directories(
${CMAKE_SOURCE_DIR}/include
${Boost_INCLUDE_DIRS}
${UHD_INCLUDE_DIRS}
)
link_directories(${Boost_LIBRARY_DIRS})
add_subdirectory(lib)
list(APPEND extexample_sources
${CMAKE_CURRENT_SOURCE_DIR}/lib/extension_example.cpp
)
add_subdirectory(include)
message(STATUS "UHD Module Path (to which the extension will be linked):")
message(STATUS ${UHD_MODULE_PATH})
### Make the executable #######################################################
add_library(extexample SHARED ${extexample_sources})
### Once it's built... ########################################################
# Here, you have commands to install your program.
set(extension_target ${CMAKE_INSTALL_PREFIX}/lib/libextexample.so)
install(TARGETS extexample DESTINATION lib)
install(CODE "execute_process(COMMAND bash -c \"mkdir -p ${UHD_MODULE_PATH} && ln -s -f ${extension_target} -t ${UHD_MODULE_PATH}\")")

View File

@ -0,0 +1,5 @@
#
# Copyright 2022 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#

View File

@ -0,0 +1,44 @@
//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include <uhd/extension/extension.hpp>
#include <uhd/rfnoc/radio_control.hpp>
#include <uhd/rfnoc/rf_control/core_iface.hpp>
#include <uhd/utils/noncopyable.hpp>
#include <memory>
namespace ext_example {
/*! Interface for Extension Example
*
* This interface contains all methods related directly to the Extension.
*/
class extension_example : public uhd::extension::extension
{
public:
using sptr = std::shared_ptr<extension_example>;
virtual ~extension_example() = default;
/*!
* This is a method that is directly related to this extension and not inherited from
* anywhere. Therefore it needs to be defined here.
*/
virtual void write_log(std::string& text) = 0;
/*!
* Make an example extension which is plugged into the given radio block.
*
* \param fargs Factory args that hold pointers to radio control and motherboard
* controller
* \returns Smart pointer to extension_example class object
*/
static sptr make(uhd::extension::extension::factory_args fargs);
};
} // namespace ext_example

View File

@ -0,0 +1,14 @@
#
# Copyright 2022 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
########################################################################
# This file included, use CMake directory variables
########################################################################
set(extension_example "" PARENT_SCOPE)
message(STATUS ${extension_example_sources})

View File

@ -0,0 +1,150 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include "extension_example.hpp"
#include <uhd/experts/expert_container.hpp>
#include <uhd/extension/extension.hpp>
#include <uhd/utils/log.hpp>
#include <cassert>
using namespace ext_example;
// This method is required to create the instance of the extension
extension_example::sptr extension_example::make(
uhd::extension::extension::factory_args fargs)
{
auto tree = fargs.radio_ctrl->get_tree();
return std::make_shared<extension_example_impl>(
fargs.radio_ctrl, fargs.radio_ctrl->get_tree());
}
// Experts are triggered by changing parameters and can then set other parameters. In
// the extension example this is demonstrated for setting the TX and RX gains. Furthermore
// they can take the task to write values to the actual extension device via every kind of
// interface.
// The gain_expert is called when the trx gain setter has changed the gain value in the
// property tree. As a result it can write a coerced value to the same item in the tree
// again, it can change other properties in the tree or it can run any functionality.
class gain_expert : public uhd::experts::worker_node_t
{
public:
gain_expert(const uhd::experts::node_retriever_t& db,
const uhd::fs_path overall_fe_path,
const uhd::fs_path ext_example_fe_path,
const size_t channel,
const uhd::direction_t trx,
uhd::rfnoc::radio_control::sptr radio)
: uhd::experts::worker_node_t(ext_example_fe_path / "example_gain_expert")
, _overall_fe_path(overall_fe_path)
, _radio(radio)
, _trx(trx)
, _chan(channel)
, _gain_in(db, overall_fe_path / "gains" / "all" / "value" / "desired")
, _gain_out(db, overall_fe_path / "gains" / "all" / "value" / "coerced")
{
bind_accessor(_gain_in);
bind_accessor(_gain_out);
}
private:
void resolve() override
{
UHD_LOG_DEBUG("extension_example::gain_expert", "Resolving");
if (_trx == RX_DIRECTION) {
// In the RX direction we'll manipulate the gain value by 3dB up:
_gain_out = _gain_in + 3;
UHD_LOG_DEBUG("extension_example::gain_expert",
"Adding 3dB for demonstration purposes.");
} else {
// In the TX direction we'll manipulate the gain value by 3dB down:
_gain_out = _gain_in - 3;
UHD_LOG_DEBUG("extension_example::gain_expert",
"Subtracting 3dB for demonstration purposes.");
}
}
const uhd::fs_path _overall_fe_path;
uhd::rfnoc::radio_control::sptr _radio;
const uhd::direction_t _trx;
const size_t _chan;
// Inputs from user/API
uhd::experts::data_reader_t<double> _gain_in;
// Output to user/API
uhd::experts::data_writer_t<double> _gain_out;
};
extension_example_impl::extension_example_impl(
uhd::rfnoc::radio_control::sptr radio, uhd::property_tree::sptr tree)
: nameless_gain_mixin([](uhd::direction_t, size_t) { return "all"; })
, _radio(radio)
, _tree(tree)
{
_expert_container =
uhd::experts::expert_factory::create_container("extension_example_radio");
constexpr size_t NUM_CHANNELS = 2;
for (size_t chan = 0; chan < NUM_CHANNELS; chan++) {
init_path(chan, TX_DIRECTION);
init_path(chan, RX_DIRECTION);
}
}
// Initialize the property tree paths that are required for demonstrating the experts
void extension_example_impl::init_path(const size_t chan, const uhd::direction_t trx)
{
const auto frontend_name = trx == TX_DIRECTION ? fs_path("tx_frontends")
: fs_path("rx_frontends");
const auto fe_path = fs_path("ext_example") / frontend_name / chan;
const auto overall_fe_path = frontend_name / chan;
const auto dboard_fe_path = fs_path("dboard") / frontend_name / chan;
// Gain properties
uhd::experts::expert_factory::add_dual_prop_node<double>(_expert_container,
_tree,
fe_path / "gains" / "all" / "value",
0,
uhd::experts::AUTO_RESOLVE_ON_WRITE);
uhd::experts::expert_factory::add_dual_prop_node<double>(_expert_container,
_tree,
overall_fe_path / "gains" / "all" / "value",
0,
uhd::experts::AUTO_RESOLVE_ON_WRITE);
uhd::experts::expert_factory::add_worker_node<gain_expert>(_expert_container,
_expert_container->node_retriever(),
overall_fe_path,
fe_path,
chan,
trx,
_radio);
}
// By using custom implementations of UHD methods return values but also functionality can
// be influenced.
freq_range_t extension_example_impl::get_tx_frequency_range(const size_t) const
{
return freq_range_t(0.0, 100.0);
}
freq_range_t extension_example_impl::get_rx_frequency_range(const size_t) const
{
return freq_range_t(0.0, 200.0);
}
/*!
* Register the extension example in the extension registry of UHD. The first argument
* is the name under which the extension can be loaded during initialization of MultiUSRP,
* the second argument is the class that implements the extension API.
*/
UHD_REGISTER_EXTENSION(extension_example, ext_example::extension_example);

View File

@ -0,0 +1,355 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include <uhd/experts/expert_factory.hpp>
#include <uhd/property_tree.hpp>
#include <uhd/rfnoc/radio_control.hpp>
#include <uhd/rfnoc/rf_control/antenna_iface.hpp>
#include <uhd/rfnoc/rf_control/nameless_gain_mixin.hpp>
#include <extension_example/extension_example.hpp>
#include <string>
#include <vector>
namespace ext_example {
using namespace uhd;
class extension_example_impl : public extension_example,
public uhd::rfnoc::rf_control::antenna_radio_control_mixin,
public uhd::rfnoc::rf_control::nameless_gain_mixin
{
public:
using sptr = std::shared_ptr<extension_example_impl>;
extension_example_impl(
uhd::rfnoc::radio_control::sptr radio, uhd::property_tree::sptr tree);
~extension_example_impl() = default;
// This is our custom method that is specific to the example extension. It was
// declared in the extension_example.hpp under include/.
void write_log(std::string& text) override
{
UHD_LOG_INFO("EXTENSION_EXAMPLE", text);
}
// The extension may just get values from the radio or it can implement its own logic.
double get_tx_frequency(const size_t chan) override
{
return _radio->get_tx_frequency(chan);
}
// For the sake of demonstration let the extension manipulate the frequency (multiply
// by 2)
double set_tx_frequency(const double freq, size_t chan) override
{
return _radio->set_tx_frequency(2.0 * freq, chan);
}
void set_tx_tune_args(const device_addr_t& args, const size_t chan) override
{
_radio->set_tx_tune_args(args, chan);
}
// Let the implementation do more advanced functionality
freq_range_t get_tx_frequency_range(const size_t) const override;
double get_rx_frequency(const size_t chan) override
{
return _radio->get_rx_frequency(chan);
}
double set_rx_frequency(const double freq, const size_t chan) override
{
return _radio->set_rx_frequency(freq, chan);
}
void set_rx_tune_args(const device_addr_t& args, const size_t chan) override
{
_radio->set_rx_tune_args(args, chan);
}
freq_range_t get_rx_frequency_range(const size_t) const override;
std::vector<std::string> get_tx_gain_names(const size_t) const override
{
return {"all"};
}
gain_range_t get_tx_gain_range(
const std::string& name, const size_t chan) const override
{
return _radio->get_tx_gain_range(name, chan);
}
// This demonstrates the usage of the experts: Here we write the gain into the
// property tree. In extension_example.cpp this change will be received by an expert
// which will then call its resolve method which can manipulate the data.
double set_tx_gain(
const double gain, const std::string& name_, const size_t chan) override
{
const std::string name = name_.empty() ? "all" : name_;
const fs_path gains_path = _get_frontend_path(TX_DIRECTION, chan) / "gains";
return _tree->access<double>(gains_path / name / "value").set(gain).get();
}
double get_tx_gain(const std::string& name_, const size_t chan) override
{
const std::string name = name_.empty() ? "all" : name_;
const fs_path gains_path = _get_frontend_path(TX_DIRECTION, chan) / "gains";
return _tree->access<double>(gains_path / name / "value").get();
}
std::vector<std::string> get_rx_gain_names(const size_t) const override
{
return {"all"};
}
gain_range_t get_rx_gain_range(
const std::string& name, const size_t chan) const override
{
return _radio->get_rx_gain_range(name, chan);
}
// Like the tx variant of this method this will demonstrate the usage of the experts.
double set_rx_gain(
const double gain, const std::string& name_, const size_t chan) override
{
const std::string name = name_.empty() ? "all" : name_;
const fs_path gains_path = _get_frontend_path(RX_DIRECTION, chan) / "gains";
return _tree->access<double>(gains_path / name / "value").set(gain).get();
}
double get_rx_gain(const std::string& name_, const size_t chan) override
{
const std::string name = name_.empty() ? "all" : name_;
const fs_path gains_path = _get_frontend_path(RX_DIRECTION, chan) / "gains";
return _tree->access<double>(gains_path / name / "value").get();
}
// If methods are not available they may throw errors. Use with caution as this may
// interrupt the functionality of an application.
void set_rx_agc(const bool, const size_t) override
{
throw uhd::not_implemented_error("set_rx_agc is not supported on this radio");
}
meta_range_t get_tx_bandwidth_range(const size_t chan) const override
{
return _radio->get_tx_bandwidth_range(chan);
}
double get_tx_bandwidth(const size_t chan) override
{
return _radio->get_tx_bandwidth(chan);
}
double set_tx_bandwidth(const double bandwidth, const size_t chan) override
{
return _radio->set_tx_bandwidth(bandwidth, chan);
}
meta_range_t get_rx_bandwidth_range(const size_t chan) const override
{
return _radio->get_rx_bandwidth_range(chan);
}
double get_rx_bandwidth(const size_t chan) override
{
return _radio->get_rx_bandwidth(chan);
}
double set_rx_bandwidth(const double bandwidth, const size_t chan) override
{
return _radio->set_rx_bandwidth(bandwidth, chan);
}
// Example extension doesn't have LOs, so we return an empty vector.
std::vector<std::string> get_rx_lo_names(const size_t) const override
{
return {};
}
std::vector<std::string> get_rx_lo_sources(
const std::string&, const size_t) const override
{
return {};
}
freq_range_t get_rx_lo_freq_range(const std::string&, const size_t) const override
{
throw uhd::not_implemented_error(
"get_rx_lo_freq_range is not supported on this radio");
}
void set_rx_lo_source(const std::string&, const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"set_rx_lo_source is not supported on this radio");
}
const std::string get_rx_lo_source(const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"get_rx_lo_source is not supported on this radio");
}
void set_rx_lo_export_enabled(bool, const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"set_rx_lo_export_enabled is not supported on this radio");
}
bool get_rx_lo_export_enabled(const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"get_rx_lo_export_enabled is not supported on this radio");
}
double set_rx_lo_freq(double, const std::string&, const size_t) override
{
throw uhd::not_implemented_error("set_rx_lo_freq is not supported on this radio");
}
double get_rx_lo_freq(const std::string&, const size_t) override
{
throw uhd::not_implemented_error("get_rx_lo_freq is not supported on this radio");
}
std::vector<std::string> get_tx_lo_names(const size_t) const override
{
return {};
}
std::vector<std::string> get_tx_lo_sources(
const std::string&, const size_t) const override
{
return {};
}
freq_range_t get_tx_lo_freq_range(const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"get_tx_lo_freq_range is not supported on this radio");
}
void set_tx_lo_source(const std::string&, const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"set_tx_lo_source is not supported on this radio");
}
const std::string get_tx_lo_source(const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"get_tx_lo_source is not supported on this radio");
}
void set_tx_lo_export_enabled(const bool, const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"set_tx_lo_export_enabled is not supported on this radio");
}
bool get_tx_lo_export_enabled(const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"get_tx_lo_export_enabled is not supported on this radio");
}
double set_tx_lo_freq(const double, const std::string&, const size_t) override
{
throw uhd::not_implemented_error("set_tx_lo_freq is not supported on this radio");
}
double get_tx_lo_freq(const std::string&, const size_t) override
{
throw uhd::not_implemented_error("get_tx_lo_freq is not supported on this radio");
}
// power_reference_iface
bool has_rx_power_reference(const size_t chan) override
{
return _radio->has_rx_power_reference(chan);
}
void set_rx_power_reference(const double power_dbm, const size_t chan) override
{
_radio->set_rx_power_reference(power_dbm, chan);
}
double get_rx_power_reference(const size_t chan) override
{
return _radio->get_rx_power_reference(chan);
}
std::vector<std::string> get_rx_power_ref_keys(const size_t) override
{
throw uhd::not_implemented_error(
"get_rx_power_ref_keys is not supported on this radio");
}
meta_range_t get_rx_power_range(const size_t) override
{
throw uhd::not_implemented_error(
"get_rx_power_range is not supported on this radio");
}
bool has_tx_power_reference(const size_t chan) override
{
return _radio->has_tx_power_reference(chan);
}
void set_tx_power_reference(const double power_dbm, const size_t chan) override
{
_radio->set_tx_power_reference(power_dbm, chan);
}
double get_tx_power_reference(const size_t chan) override
{
return _radio->get_tx_power_reference(chan);
}
std::vector<std::string> get_tx_power_ref_keys(const size_t) override
{
throw uhd::not_implemented_error(
"get_tx_power_ref_keys is not supported on this radio");
}
meta_range_t get_tx_power_range(const size_t) override
{
throw uhd::not_implemented_error(
"get_tx_power_range is not supported on this radio");
}
std::string get_name() override
{
return "Example_Extension";
}
private:
uhd::rfnoc::radio_control::sptr _radio;
uhd::experts::expert_container::sptr _expert_container;
mutable uhd::property_tree::sptr _tree;
// Necessary for writing into and reading from the property tree.
fs_path _get_frontend_path(const direction_t dir, const size_t chan_idx) const
{
const std::string frontend = dir == TX_DIRECTION ? "tx_frontends"
: "rx_frontends";
return frontend / chan_idx;
}
// Inits a path in the property tree to be able to use it later on.
void init_path(const size_t chan, const uhd::direction_t trx);
};
} // namespace ext_example