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)