diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index adfaa90b8ae8174de1914afa6f3bcbc02a4b8eb7..690c1455bb6339f02df6040a603ab8d449b969c9 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -10,5 +10,6 @@ install(FILES elen90089_corr_est_cc.block.yml elen90089_moe_symbol_sync_cc.block.yml elen90089_symbol_mapper_c.block.yml + elen90089_header_format_cdc.block.yml DESTINATION share/gnuradio/grc/blocks ) diff --git a/grc/elen90089_header_format_cdc.block.yml b/grc/elen90089_header_format_cdc.block.yml new file mode 100644 index 0000000000000000000000000000000000000000..0bee652f6d423161eb42cba630cbd3bde1c584c2 --- /dev/null +++ b/grc/elen90089_header_format_cdc.block.yml @@ -0,0 +1,24 @@ +id: header_format_cdc +label: CDC Header Format +category: '[elen90089]' +flags: [ show_id ] + +templates: + imports: import elen90089 + make: elen90089.header_format_cdc(${access_code}, ${threshold}, ${bps}) + +parameters: +- id: access_code + label: Access Code + dtype: string + default: '11001010' +- id: threshold + label: Threshold + dtype: int + default: 3 +- id: bps + label: Bits per symbol + dtype: int + default: 1 + +file_format: 1 diff --git a/include/elen90089/CMakeLists.txt b/include/elen90089/CMakeLists.txt index e47a60c06608643b7f30bf5d5de8147b3f53e85c..01411424550ddb7307cb581024c5b8d59e98eb84 100644 --- a/include/elen90089/CMakeLists.txt +++ b/include/elen90089/CMakeLists.txt @@ -14,5 +14,6 @@ install(FILES corr_est_cc.h moe_symbol_sync_cc.h symbol_mapper_c.h + header_format_cdc.h DESTINATION include/elen90089 ) diff --git a/include/elen90089/header_format_cdc.h b/include/elen90089/header_format_cdc.h new file mode 100644 index 0000000000000000000000000000000000000000..42d4e3144c4ee83713f153005ece883790ea965c --- /dev/null +++ b/include/elen90089/header_format_cdc.h @@ -0,0 +1,108 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 University of Melbourne. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_ELEN90089_HEADER_FORMAT_CDC_H +#define INCLUDED_ELEN90089_HEADER_FORMAT_CDC_H + +#include <elen90089/api.h> +#include <gnuradio/digital/header_format_default.h> +#include <pmt/pmt.h> + +namespace gr { +namespace elen90089 { + +/*! + * \brief Header formatter for CDC packets that adds payload bits/symbol + * format and a packet number counter + * + * \details + * + * Same as digital::header_format_counter but updates \p bps field of header + * based on value found in packet metadata dictionary. Packet structure is: + * + * | access_code | hdr | payload | + * + * Where the access_code is <= 64 bits and hdr is: + * + * | 0 -- 15 | 16 -- 31 | + * | pkt len | pkt len | + * | bits/sym | counter | + * + * Access code and header are formatted for network byte order (big endian) + * + */ +class ELEN90089_API header_format_cdc : public digital::header_format_default +{ +public: + typedef std::shared_ptr<header_format_cdc> sptr; + + header_format_cdc(const std::string& access_code, + int threshold, + int bps); + + ~header_format_cdc() override; + + + /*! + * Creates a header from the access code and packet length to build an + * output packet in the form: + * + * | access code | pkt len | pkt len | bps | counter | + * + * \param nbytes The length (in bytes) of the \p input payload + * \param input An array of unsigned chars of the packet payload + * \param output A pmt::u8vector with the new header prepended + * onto the input data. + * \param info A pmt::dict containing meta data and info about + * the PDU (generally from the metadata portion of the + * input PDU). Data can be extracted from this for the + * header formatting or inserted. + */ + bool format(int nbytes, + const unsigned char* input, + pmt::pmt_t& output, + pmt::pmt_t& info) override; + + /*! + * Returns the length of the formatted header in bits. + */ + size_t header_nbits() const override; + + /*! + * Make a CDC header formatter object. + * + * \param access_code Frame access code, string of 0s and 1s (<= 64 bits) + * \param threshold Bit error threshold in access code detection. + * \param bps Default payload bits per symbol. Updated based on + * bps field in metadata. + */ + static sptr make(const std::string& access_code, + int threshold, + int bps); + +protected: + uint16_t d_counter; //!< keeps track of the number of packets transmitted + + //! Verify that the header is valid + bool header_ok() override; + + /*! Get info from the header; return payload length and package + * rest of data in d_info dictionary. + * + * Extracts the header of the form: + * + * \verbatim + * | access code | pkt len | pkt len | bps | counter | payload | + * \endverbatim + */ + int header_payload() override; +}; + +} // namespace elen90089 +} // namespace gr + +#endif /* INCLUDED_ELEN90089_HEADER_FORMAT_CDC_H */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 8b8158b74b647b76cec672be3b5720e3e577d60b..e6470911aa94bebf40d3c57e3d4d1cb2aca035f7 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -15,6 +15,7 @@ list(APPEND elen90089_sources corr_est_cc_impl.cc moe_symbol_sync_cc_impl.cc symbol_mapper_c_impl.cc + header_format_cdc.cc ) set(elen90089_sources "${elen90089_sources}" PARENT_SCOPE) @@ -64,6 +65,7 @@ include(GrTest) #include_directories() # List all files that contain Boost.UTF unit tests here list(APPEND test_elen90089_sources + qa_header_format_cdc.cc ) # Anything we need to link to for the unit tests go here list(APPEND GR_TEST_TARGET_DEPS gnuradio-elen90089) diff --git a/lib/header_format_cdc.cc b/lib/header_format_cdc.cc new file mode 100644 index 0000000000000000000000000000000000000000..568f63f14cc1809cc75f018c2a8dc57a22fbb4bb --- /dev/null +++ b/lib/header_format_cdc.cc @@ -0,0 +1,94 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 University of Melbourne. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <gnuradio/io_signature.h> +#include <volk/volk_alloc.hh> +#include <elen90089/header_format_cdc.h> + +namespace gr { +namespace elen90089 { + +header_format_cdc::sptr +header_format_cdc::make(const std::string& access_code, int threshold, int bps) +{ + return header_format_cdc::sptr( + new header_format_cdc(access_code, threshold, bps)); +} + +header_format_cdc::header_format_cdc(const std::string& access_code, + int threshold, + int bps) + : header_format_default(access_code, threshold, bps) +{ + d_counter = 0; +} + +header_format_cdc::~header_format_cdc() {} + +bool header_format_cdc::format(int nbytes_in, + const unsigned char* input, + pmt::pmt_t& output, + pmt::pmt_t& info) + +{ + // update bps from metadata if present + int bps = d_bps; + if (pmt::is_dict(info) && pmt::dict_has_key(info, pmt::mp("bps"))) { + pmt::pmt_t val = pmt::dict_ref(info, pmt::mp("bps"), pmt::PMT_NIL); + bps = pmt::to_long(val); + } + assert(bps > 0 && bps < 5); + + // Creating the output pmt copies data; free our own here when done. + volk::vector<uint8_t> bytes_out(header_nbytes()); + + digital::header_buffer header(bytes_out.data()); + header.add_field64(d_access_code, d_access_code_len); + header.add_field16((uint16_t)(nbytes_in)); + header.add_field16((uint16_t)(nbytes_in)); + header.add_field16((uint16_t)(bps)); + header.add_field16((uint16_t)(d_counter)); + + // Package output data into a PMT vector + output = pmt::init_u8vector(header_nbytes(), bytes_out.data()); + + d_counter++; + + return true; +} + +size_t header_format_cdc::header_nbits() const +{ + return d_access_code_len + 8 * 4 * sizeof(uint16_t); +} + +bool header_format_cdc::header_ok() +{ + // confirm that two copies of header info are identical + uint16_t len0 = d_hdr_reg.extract_field16(0); + uint16_t len1 = d_hdr_reg.extract_field16(16); + return (len0 ^ len1) == 0; +} + +int header_format_cdc::header_payload() +{ + uint16_t len = d_hdr_reg.extract_field16(0); + uint16_t bps = d_hdr_reg.extract_field16(32); + uint16_t counter = d_hdr_reg.extract_field16(48); + + d_bps = bps; + + d_info = pmt::make_dict(); + d_info = pmt::dict_add( + d_info, pmt::intern("payload symbols"), pmt::from_long(8 * len / d_bps)); + d_info = pmt::dict_add(d_info, pmt::intern("bps"), pmt::from_long(bps)); + d_info = pmt::dict_add(d_info, pmt::intern("counter"), pmt::from_long(counter)); + return static_cast<int>(len); +} + +} /* namespace elen90089 */ +} /* namespace gr */ diff --git a/lib/qa_header_format_cdc.cc b/lib/qa_header_format_cdc.cc new file mode 100644 index 0000000000000000000000000000000000000000..4896e160e9247aea970650db4b27cabf18585e16 --- /dev/null +++ b/lib/qa_header_format_cdc.cc @@ -0,0 +1,60 @@ + + +#include <elen90089/header_format_cdc.h> +#include <gnuradio/attributes.h> +#include <boost/test/unit_test.hpp> + +namespace gr { +namespace elen90089 { + +BOOST_AUTO_TEST_CASE(test_header_format_cdc_bps) +{ + static const int N = 4; + std::string ac = "10110010"; // 0xB2 + std::vector<unsigned char> data{ 0x01, 0x02, 0x03, 0x04 }; + int threshold = 3; + int bps = 1; + header_format_cdc::sptr hdr_format; + hdr_format = header_format_cdc::make(ac, threshold, bps); + + pmt::pmt_t output; + pmt::pmt_t info = pmt::make_dict(); + info = pmt::dict_add(info, pmt::mp("bps"), pmt::from_long(2)); + + bool ret = hdr_format->format(N, data.data(), output, info); + size_t length = pmt::length(output); + + BOOST_REQUIRE(ret); + BOOST_REQUIRE_EQUAL(length, hdr_format->header_nbytes()); + BOOST_REQUIRE_EQUAL(8 * length, hdr_format->header_nbits()); + + // Test access code + unsigned char h0 = pmt::u8vector_ref(output, 0); + BOOST_REQUIRE_EQUAL(0xB2, (int)h0); + + // Test packet length - 0x0404 + unsigned char h1 = pmt::u8vector_ref(output, 1); + unsigned char h2 = pmt::u8vector_ref(output, 2); + unsigned char h3 = pmt::u8vector_ref(output, 3); + unsigned char h4 = pmt::u8vector_ref(output, 4); + BOOST_REQUIRE_EQUAL(0x00, (int)h1); + BOOST_REQUIRE_EQUAL(0x04, (int)h2); + BOOST_REQUIRE_EQUAL(0x00, (int)h3); + BOOST_REQUIRE_EQUAL(0x04, (int)h4); + + // Test bits/symbol - 0x0002 + unsigned char h5 = pmt::u8vector_ref(output, 5); + unsigned char h6 = pmt::u8vector_ref(output, 6); + BOOST_REQUIRE_EQUAL(0x00, (int)h5); + BOOST_REQUIRE_EQUAL(0x02, (int)h6); + + // Test counter - 0x0000 + unsigned char h7 = pmt::u8vector_ref(output, 7); + unsigned char h8 = pmt::u8vector_ref(output, 8); + BOOST_REQUIRE_EQUAL(0x00, (int)h7); + BOOST_REQUIRE_EQUAL(0x00, (int)h8); +} + +} /* namespace elen90089 */ +} /* namespace gr */ + diff --git a/python/bindings/CMakeLists.txt b/python/bindings/CMakeLists.txt index 6c6cd4df39a1d296aa4aaaf1bdd78be62e903cdf..95bcf56bf41e4fec2d69113d490e0eee00815ebd 100644 --- a/python/bindings/CMakeLists.txt +++ b/python/bindings/CMakeLists.txt @@ -32,6 +32,7 @@ list(APPEND elen90089_python_files corr_est_cc_python.cc moe_symbol_sync_cc_python.cc symbol_mapper_c_python.cc + header_format_cdc_python.cc python_bindings.cc) GR_PYBIND_MAKE_OOT(elen90089 diff --git a/python/bindings/docstrings/header_format_cdc_pydoc_template.h b/python/bindings/docstrings/header_format_cdc_pydoc_template.h new file mode 100644 index 0000000000000000000000000000000000000000..48b65de9b9f19a1c26e9a6c68473c8209367f104 --- /dev/null +++ b/python/bindings/docstrings/header_format_cdc_pydoc_template.h @@ -0,0 +1,36 @@ +/* + * 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_header_format_cdc = R"doc()doc"; + + + static const char *__doc_gr_elen90089_header_format_cdc_header_format_cdc_0 = R"doc()doc"; + + + static const char *__doc_gr_elen90089_header_format_cdc_header_format_cdc_1 = R"doc()doc"; + + + static const char *__doc_gr_elen90089_header_format_cdc_format = R"doc()doc"; + + + static const char *__doc_gr_elen90089_header_format_cdc_header_nbits = R"doc()doc"; + + + static const char *__doc_gr_elen90089_header_format_cdc_make = R"doc()doc"; + + diff --git a/python/bindings/header_format_cdc_python.cc b/python/bindings/header_format_cdc_python.cc new file mode 100644 index 0000000000000000000000000000000000000000..5ac77bc8d69570f12fc385cec9ac1e688d562f87 --- /dev/null +++ b/python/bindings/header_format_cdc_python.cc @@ -0,0 +1,58 @@ +/* + * 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(header_format_cdc.h) */ +/* BINDTOOL_HEADER_FILE_HASH(a83bb767baf65cce828e12e815183c6a) */ +/***********************************************************************************/ + +#include <pybind11/complex.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + +namespace py = pybind11; + +#include <elen90089/header_format_cdc.h> +// pydoc.h is automatically generated in the build directory +#include <header_format_cdc_pydoc.h> + +void bind_header_format_cdc(py::module& m) +{ + + using header_format_cdc = ::gr::elen90089::header_format_cdc; + + py::class_<header_format_cdc, + gr::digital::header_format_default, + std::shared_ptr<header_format_cdc>>(m, "header_format_cdc", D(header_format_cdc)) + + .def(py::init(&header_format_cdc::make), + py::arg("access_code"), + py::arg("threshold"), + py::arg("bps"), + D(header_format_cdc, make)) + + .def("format", + &header_format_cdc::format, + py::arg("nbytes"), + py::arg("input"), + py::arg("output"), + py::arg("info"), + D(header_format_cdc, format)) + + .def("header_nbits", + &header_format_cdc::header_nbits, + D(header_format_cdc, header_nbits)); + +} + diff --git a/python/bindings/python_bindings.cc b/python/bindings/python_bindings.cc index a6f73a6a9618aff5ff266d5f636df852b18314a9..d46dcdfb7afb3b71c81cfa25552e3060e5807ed1 100644 --- a/python/bindings/python_bindings.cc +++ b/python/bindings/python_bindings.cc @@ -24,6 +24,7 @@ namespace py = pybind11; void bind_corr_est_cc(py::module& m); void bind_moe_symbol_sync_cc(py::module& m); void bind_symbol_mapper_c(py::module& m); + void bind_header_format_cdc(py::module& m); // ) END BINDING_FUNCTION_PROTOTYPES @@ -45,6 +46,7 @@ PYBIND11_MODULE(elen90089_python, m) // Allow access to base block methods py::module::import("gnuradio.gr"); + py::module::import("gnuradio.digital"); /**************************************/ // The following comment block is used for @@ -55,5 +57,6 @@ PYBIND11_MODULE(elen90089_python, m) bind_corr_est_cc(m); bind_moe_symbol_sync_cc(m); bind_symbol_mapper_c(m); + bind_header_format_cdc(m); // ) END BINDING_FUNCTION_CALLS -} \ No newline at end of file +}