diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index 93d7f990a80f11dd2a674129d6cba14bb514ed4a..9e8303b85c3a2a835b41993f5941530eabea4491 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -15,5 +15,6 @@ install(FILES elen90089_packet_mac_tx.block.yml elen90089_constellation_decoder_cf.block.yml elen90089_costas_loop_cc.block.yml + elen90089_dsa_pu_scenario_cc.block.yml DESTINATION share/gnuradio/grc/blocks ) diff --git a/grc/elen90089_dsa_pu_scenario_cc.block.yml b/grc/elen90089_dsa_pu_scenario_cc.block.yml new file mode 100644 index 0000000000000000000000000000000000000000..1f9df7ac0465b579d5a6e656dbdabf5c95f8db67 --- /dev/null +++ b/grc/elen90089_dsa_pu_scenario_cc.block.yml @@ -0,0 +1,54 @@ +id: elen90089_dsa_pu_scenario_cc +label: DSA PU Scenario +category: '[elen90089]' +templates: + imports: import elen90089 + make: elen90089.dsa_pu_scenario_cc(${scenario}, ${random}, ${seed}, ${samp_rate}, + ${duration_ms}) + callbacks: + - set_scenario(${scenario}) + - set_random(${random}) + - set_duration_ms(${duration_ms}) + +parameters: +- id: scenario + label: Scenario + dtype: int + default: 0 +- id: random + label: Randomize Scenario + dtype: raw + default: 'False' +- id: seed + label: Seed + dtype: int + default: 1 +- id: samp_rate + label: Samp rate + dtype: int + default: 'samp_rate' +- id: duration_ms + label: Duration (ms) + dtype: float + default: 50.0 +- id: num_channels + label: Num Channels + default: '1' + dtype: int + +inputs: +- label: in + domain: stream + dtype: complex + multiplicity: ${num_channels} + +outputs: +- label: out + domain: stream + dtype: complex + multiplicity: ${num_channels} +- label: mode + domain: message + optional: True + +file_format: 1 diff --git a/include/elen90089/CMakeLists.txt b/include/elen90089/CMakeLists.txt index 50bc03873444b5c928046935011c72bbffa20c87..596b39fa2080399a36d411de0adcc135efa205a8 100644 --- a/include/elen90089/CMakeLists.txt +++ b/include/elen90089/CMakeLists.txt @@ -17,5 +17,6 @@ install(FILES header_format_cdc.h constellation_decoder_cf.h costas_loop_cc.h + dsa_pu_scenario_cc.h DESTINATION include/elen90089 ) diff --git a/include/elen90089/dsa_pu_scenario_cc.h b/include/elen90089/dsa_pu_scenario_cc.h new file mode 100644 index 0000000000000000000000000000000000000000..bc6464ac45430188cc3edab693cda00faddeb049 --- /dev/null +++ b/include/elen90089/dsa_pu_scenario_cc.h @@ -0,0 +1,83 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 University of Melbourne. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_ELEN90089_DSA_PU_SCENARIO_H +#define INCLUDED_ELEN90089_DSA_PU_SCENARIO_H + +#include <elen90089/api.h> +#include <gnuradio/sync_block.h> + +namespace gr { +namespace elen90089 { + +/*! + * \brief Implements primary user scenario control for CDC dynamic spectrum + access project. + * \ingroup elen90089 + * + */ +class ELEN90089_API dsa_pu_scenario_cc : virtual public gr::sync_block +{ +public: + typedef std::shared_ptr<dsa_pu_scenario_cc> sptr; + + /*! + * \brief DSA PU Scenario Constructor + * + * \param scenario Primary user scenario - bitmap of active channels, + * e.g., for 4 PU channels SCENARIO = 10 = 0b1010 + indicates channels 2 and 4 are active. Set to -1 + * for randomly selected scenarios. + * \param random Randomly select primary user scenario + * \param seed Seed used used in random scenario selection. + * \param samp_rate Sample rate of incoming data streams + * \param duration_ms Duration (in ms) of scenario before randomly + * selecting new scenario. + */ + static sptr make(int scenario, + bool random, + int seed, + int samp_rate, + float duration_ms); + + //! Set primary user scenario + virtual void set_scenario(int scenario) = 0; + + //! Get current primary user scenario + virtual int get_scenario(void) = 0; + + //! Set random selection of primary user scenarios + virtual void set_random(bool random) = 0; + + //! Get random selection of primary user scenarios + virtual bool get_random(void) = 0; + + //! Set duration (in ms) of random primary user scenarios + virtual void set_duration_ms(float duration_ms) = 0; + + //! Get duration (in ms) of random primary user scenarios + virtual float get_duration_ms(void) = 0; + +}; + +} // namespace elen90089 +} // namespace gr + +#endif /* INCLUDED_ELEN90089_DSA_PU_SCENARIO_H */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a7f1c7aa2cce171725233568b52b71f245344305..d77e6a9a59976ae3592cdf5f54b162c128bd611f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -18,6 +18,7 @@ list(APPEND elen90089_sources header_format_cdc.cc constellation_decoder_cf_impl.cc costas_loop_cc_impl.cc + dsa_pu_scenario_cc_impl.cc ) set(elen90089_sources "${elen90089_sources}" PARENT_SCOPE) diff --git a/lib/dsa_pu_scenario_cc_impl.cc b/lib/dsa_pu_scenario_cc_impl.cc new file mode 100644 index 0000000000000000000000000000000000000000..3ff55f79a64cd531a1533cfc5c69902e6d790089 --- /dev/null +++ b/lib/dsa_pu_scenario_cc_impl.cc @@ -0,0 +1,95 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 University of Melbourne. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <gnuradio/io_signature.h> +#include "dsa_pu_scenario_cc_impl.h" + +namespace gr { +namespace elen90089 { + +dsa_pu_scenario_cc::sptr +dsa_pu_scenario_cc::make(int scenario, + bool random, + int seed, + int samp_rate, + float duration_ms) +{ + return gnuradio::make_block_sptr<dsa_pu_scenario_cc_impl>( + scenario, random, seed, samp_rate, duration_ms); +} + +dsa_pu_scenario_cc_impl::dsa_pu_scenario_cc_impl(int scenario, + bool random, + int seed, + int samp_rate, + float duration_ms) + : gr::sync_block("dsa_pu_scenario_cc", + gr::io_signature::make(1, -1, sizeof(gr_complex)), + gr::io_signature::make(1, -1, sizeof(gr_complex))), + d_scenario(scenario), + d_random(random), + d_engine(seed), + d_samp_rate(samp_rate), + d_duration_ms(duration_ms) +{ + message_port_register_out(pmt::mp("mode")); +} + +dsa_pu_scenario_cc_impl::~dsa_pu_scenario_cc_impl() +{ + if (d_uniform) + delete d_uniform; +} + +void +dsa_pu_scenario_cc_impl::update_scenario(int n_chan) +{ + if (!d_active.size()) + { + d_active.resize(n_chan); + int n_scenarios = (0x1 << n_chan); + d_uniform = new std::uniform_int_distribution<int>(0, n_scenarios - 1); + } + + if (d_samp_cnt <= 0) + { + d_samp_cnt = int(d_duration_ms * d_samp_rate / 1000.0); + if (d_random) + d_scenario = (*d_uniform)(d_engine); + message_port_pub(pmt::mp("mode"), pmt::mp(d_scenario)); + } + + for (int i_chan=0; i_chan < n_chan; i_chan++) + d_active[i_chan] = d_scenario & (0x1 << i_chan); +} + +int +dsa_pu_scenario_cc_impl::work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + int n_chan = output_items.size(); + + update_scenario(n_chan); + + int n_samps = (d_samp_cnt < noutput_items) ? d_samp_cnt : noutput_items; + for (int i_chan = 0; i_chan < n_chan; i_chan++) + { + gr_complex *in = (gr_complex *)input_items[i_chan]; + gr_complex *out = (gr_complex *)output_items[i_chan]; + if (d_active[i_chan]) + memcpy(out, in, n_samps * sizeof(gr_complex)); + else + memset(out, 0x0, n_samps * sizeof(gr_complex)); + } + d_samp_cnt -= n_samps; + + return n_samps; +} + +} /* namespace elen90089 */ +} /* namespace gr */ diff --git a/lib/dsa_pu_scenario_cc_impl.h b/lib/dsa_pu_scenario_cc_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..7dceaed8c0013ffd428496d282c50b2d4c25fcc1 --- /dev/null +++ b/lib/dsa_pu_scenario_cc_impl.h @@ -0,0 +1,61 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 University of Melbourne. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_ELEN90089_DSA_PU_SCENARIO_IMPL_H +#define INCLUDED_ELEN90089_DSA_PU_SCENARIO_IMPL_H + +#include <elen90089/dsa_pu_scenario_cc.h> +#include <random> + +namespace gr { +namespace elen90089 { + +class dsa_pu_scenario_cc_impl : public dsa_pu_scenario_cc +{ +private: + int d_scenario; + bool d_random; + int d_samp_rate; + float d_duration_ms; + + std::default_random_engine d_engine; + std::uniform_int_distribution<int>* d_uniform = nullptr; + std::vector<bool> d_active; + int d_samp_cnt = 0; + + void update_scenario(int n_chan); + +public: + dsa_pu_scenario_cc_impl(int scenario, + bool random, + int seed, + int samp_rate, + float duration_ms); + ~dsa_pu_scenario_cc_impl(); + + void set_scenario(int scenario) { d_scenario = scenario; }; + + int get_scenario(void) { return d_scenario; }; + + void set_random(bool random) { d_random = random; }; + + bool get_random(void) { return random; }; + + void set_duration_ms(float duration_ms) { d_duration_ms = duration_ms; }; + + float get_duration_ms(void) { return d_duration_ms; }; + + int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + +}; + +} // namespace elen90089 +} // namespace gr + +#endif /* INCLUDED_ELEN90089_DSA_PU_SCENARIO_IMPL_H */ diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index a429794c9327c34a43dfb52092ab68d6673cf1a9..ad251101a607f4a33cd35f13d30aeabb438fbdf0 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -45,3 +45,4 @@ GR_ADD_TEST(qa_corr_est_cc ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_c GR_ADD_TEST(qa_moe_symbol_sync_cc ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_moe_symbol_sync_cc.py) GR_ADD_TEST(qa_symbol_mapper_c ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_symbol_mapper_c.py) GR_ADD_TEST(qa_constellation_decoder_cf ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_constellation_decoder_cf.py) +GR_ADD_TEST(qa_dsa_pu_scenario ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_dsa_pu_scenario_cc.py) diff --git a/python/bindings/CMakeLists.txt b/python/bindings/CMakeLists.txt index 9e0882b13b763a87efbcf157eeec24d4411b5f8f..c978fa913a77fd07b1e52c2a2f55256f82e6f869 100644 --- a/python/bindings/CMakeLists.txt +++ b/python/bindings/CMakeLists.txt @@ -35,6 +35,7 @@ list(APPEND elen90089_python_files header_format_cdc_python.cc constellation_decoder_cf_python.cc costas_loop_cc_python.cc + dsa_pu_scenario_cc_python.cc python_bindings.cc) GR_PYBIND_MAKE_OOT(elen90089 diff --git a/python/bindings/docstrings/dsa_pu_scenario_cc_pydoc_template.h b/python/bindings/docstrings/dsa_pu_scenario_cc_pydoc_template.h new file mode 100644 index 0000000000000000000000000000000000000000..16377c66512ba941da6ecb956fa0d41722583653 --- /dev/null +++ b/python/bindings/docstrings/dsa_pu_scenario_cc_pydoc_template.h @@ -0,0 +1,48 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ +#include "pydoc_macros.h" +#define D(...) DOC(gr,elen90089, __VA_ARGS__ ) +/* + This file contains placeholders for docstrings for the Python bindings. + Do not edit! These were automatically extracted during the binding process + and will be overwritten during the build process + */ + + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_dsa_pu_scenario_cc_0 = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_dsa_pu_scenario_cc_1 = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_make = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_set_scenario = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_get_scenario = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_set_random = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_get_random = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_set_duration_ms = R"doc()doc"; + + + static const char *__doc_gr_elen90089_dsa_pu_scenario_cc_get_duration_ms = R"doc()doc"; + + diff --git a/python/bindings/dsa_pu_scenario_cc_python.cc b/python/bindings/dsa_pu_scenario_cc_python.cc new file mode 100644 index 0000000000000000000000000000000000000000..a8e7d91b6438ed7e979da4df9627857c80aa585d --- /dev/null +++ b/python/bindings/dsa_pu_scenario_cc_python.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +/***********************************************************************************/ +/* This file is automatically generated using bindtool and can be manually edited */ +/* The following lines can be configured to regenerate this file during cmake */ +/* If manual edits are made, the following tags should be modified accordingly. */ +/* BINDTOOL_GEN_AUTOMATIC(0) */ +/* BINDTOOL_USE_PYGCCXML(0) */ +/* BINDTOOL_HEADER_FILE(dsa_pu_scenario_cc.h) */ +/* BINDTOOL_HEADER_FILE_HASH(f02a9698a03223ac99f30b7854ed37c9) */ +/***********************************************************************************/ + +#include <pybind11/complex.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + +namespace py = pybind11; + +#include <elen90089/dsa_pu_scenario_cc.h> +// pydoc.h is automatically generated in the build directory +#include <dsa_pu_scenario_cc_pydoc.h> + +void bind_dsa_pu_scenario_cc(py::module& m) +{ + using dsa_pu_scenario_cc = ::gr::elen90089::dsa_pu_scenario_cc; + + py::class_<dsa_pu_scenario_cc, + gr::sync_block, + gr::block, + gr::basic_block, + std::shared_ptr<dsa_pu_scenario_cc>>(m, "dsa_pu_scenario_cc", D(dsa_pu_scenario_cc)) + + .def(py::init(&dsa_pu_scenario_cc::make), + py::arg("scenario"), + py::arg("random"), + py::arg("seed"), + py::arg("samp_rate"), + py::arg("duration_ms"), + D(dsa_pu_scenario_cc, make)) + + .def("set_scenario", + &dsa_pu_scenario_cc::set_scenario, + py::arg("scenario"), + D(dsa_pu_scenario_cc, set_scenario)) + + .def("get_scenario", + &dsa_pu_scenario_cc::get_scenario, + D(dsa_pu_scenario_cc, get_scenario)) + + .def("set_random", + &dsa_pu_scenario_cc::set_random, + py::arg("random"), + D(dsa_pu_scenario_cc, set_random)) + + .def("get_random", + &dsa_pu_scenario_cc::get_random, + D(dsa_pu_scenario_cc, get_random)) + + .def("set_duration_ms", + &dsa_pu_scenario_cc::set_duration_ms, + py::arg("duration_ms"), + D(dsa_pu_scenario_cc, set_duration_ms)) + + .def("get_duration_ms", + &dsa_pu_scenario_cc::get_duration_ms, + D(dsa_pu_scenario_cc, get_duration_ms)); +} diff --git a/python/bindings/python_bindings.cc b/python/bindings/python_bindings.cc index 3725944e4b3c1fcd00d9f181fad9b7dfdfd7be42..c4c5d51b9fc1692aa62e1ebab3ca48e06b5015b7 100644 --- a/python/bindings/python_bindings.cc +++ b/python/bindings/python_bindings.cc @@ -27,6 +27,7 @@ namespace py = pybind11; void bind_header_format_cdc(py::module& m); void bind_constellation_decoder_cf(py::module& m); void bind_costas_loop_cc(py::module& m); + void bind_dsa_pu_scenario_cc(py::module& m); // ) END BINDING_FUNCTION_PROTOTYPES @@ -62,5 +63,6 @@ PYBIND11_MODULE(elen90089_python, m) bind_header_format_cdc(m); bind_constellation_decoder_cf(m); bind_costas_loop_cc(m); + bind_dsa_pu_scenario_cc(m); // ) END BINDING_FUNCTION_CALLS -} \ No newline at end of file +} diff --git a/python/qa_dsa_pu_scenario_cc.py b/python/qa_dsa_pu_scenario_cc.py new file mode 100755 index 0000000000000000000000000000000000000000..7efccc46e395c9fb13de77f914728efa1107e783 --- /dev/null +++ b/python/qa_dsa_pu_scenario_cc.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2022 University of Melbourne. +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +import numpy as np + +from gnuradio import gr, gr_unittest +from gnuradio import blocks +# from gnuradio import blocks +try: + from elen90089 import dsa_pu_scenario_cc +except ImportError: + import os + import sys + dirname, filename = os.path.split(os.path.abspath(__file__)) + sys.path.append(os.path.join(dirname, "bindings")) + from elen90089 import dsa_pu_scenario_cc + +class qa_dsa_pu_scenario_cc(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_instance(self): + instance = dsa_pu_scenario_cc( + scenario = 0, + random = False, + seed = 1, + samp_rate = 32000, + duration_ms = 10) + + def test_001_randomized_scenario(self): + scenario = 0 + randomize = True + seed = 1 + samp_rate = 1000 + dur_ms = 10 + src_data = np.ones(4*10, dtype=np.complex) + + # set up fg + src = blocks.vector_source_c(src_data) + dut = dsa_pu_scenario_cc(scenario, randomize, seed, samp_rate, dur_ms) + dst = blocks.vector_sink_c() + self.tb.connect(src, dut, dst) + self.tb.run() + + # check data + expected = np.array( + [0,]*10 + [0,]*10 + [1,]*10 + [0,]*10, + dtype=np.complex) + results = dst.data() + self.assertFloatTuplesAlmostEqual(results, expected) + + +if __name__ == '__main__': + gr_unittest.run(qa_dsa_pu_scenario_cc)