diff options
author | Jacob Gilbert <jacob.gilbert@protonmail.com> | 2021-03-21 13:51:42 -0700 |
---|---|---|
committer | mormj <34754695+mormj@users.noreply.github.com> | 2021-04-05 08:28:44 -0400 |
commit | 6c75d18485933f26e1ef545ae01779c132b49398 (patch) | |
tree | 94dac5734a78f05fb9fa1db9db4ff0fba41ebb55 | |
parent | 8b23f906844c9784c784934ae06dfdfe10d31a1f (diff) |
gr-pdu: add time blocks and examples
Adds blocks from gr-timing_utils for time measurement of PDU blocks
and an example highlighting many of the general utility PDU tools.
Signed-off-by: Jacob Gilbert <jacob.gilbert@protonmail.com>
25 files changed, 1108 insertions, 18 deletions
diff --git a/gr-pdu/examples/CMakeLists.txt b/gr-pdu/examples/CMakeLists.txt index c3f1df7c0d..cd0750ca63 100644 --- a/gr-pdu/examples/CMakeLists.txt +++ b/gr-pdu/examples/CMakeLists.txt @@ -7,6 +7,7 @@ install( FILES + pdu_tools_demo.grc DESTINATION ${GR_PKG_DATA_DIR}/examples/pdu ) diff --git a/gr-pdu/examples/pdu_tools_demo.grc b/gr-pdu/examples/pdu_tools_demo.grc new file mode 100644 index 0000000000..9e5c628a3a --- /dev/null +++ b/gr-pdu/examples/pdu_tools_demo.grc @@ -0,0 +1,377 @@ +options: + parameters: + author: J. Gilbert + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: 2021 J. Gilbert + description: Example usage of a range of PDU tools. + gen_cmake: 'On' + gen_linking: dynamic + generate_options: qt_gui + hier_block_src_path: '.:' + id: pdu_tools_demo + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: prompt + sizing_mode: fixed + thread_safe_setters: '' + title: PDU Tools Example + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [24, 20.0] + rotation: 0 + state: enabled + +blocks: +- name: interval + id: variable + parameters: + comment: '' + value: '0.400' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [456, 20.0] + rotation: 0 + state: true +- name: timekey + id: variable + parameters: + comment: '' + value: pmt.intern("SYS_TIME") + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [240, 460.0] + rotation: 0 + state: true +- name: val + id: variable_qtgui_range + parameters: + comment: '' + gui_hint: '' + label: KEY1 Metadata Value + min_len: '200' + orient: QtCore.Qt.Horizontal + rangeType: float + start: '0' + step: '0.1' + stop: '500.0' + value: '123.4' + widget: counter_slider + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [216, 20.0] + rotation: 0 + state: true +- name: blocks_message_debug_0 + id: blocks_message_debug + parameters: + affinity: '' + alias: '' + comment: Print out the metadata dictionary. + en_uvec: 'True' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [584, 728.0] + rotation: 0 + state: true +- name: blocks_message_strobe_0 + id: blocks_message_strobe + parameters: + affinity: '' + alias: '' + comment: Emits pmt.PMT_T + maxoutbuf: '0' + minoutbuf: '0' + msg: pmt.PMT_T + period: int(interval*1000) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [56, 252.0] + rotation: 0 + state: true +- name: note_0 + id: note + parameters: + alias: '' + comment: 'Normally, some PDU processing + + would be done here for the time + + benchmarks to be applied over.' + note: README + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [512, 532.0] + rotation: 0 + state: true +- name: note_0_0 + id: note + parameters: + alias: '' + comment: 'This flowgraph highlights a number of useful PDU tools together. + + This flowgraph is not intended to do anything particularily useful + + other than serve as a reference / demonstration.' + note: SUMMARY + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [616, 20.0] + rotation: 0 + state: true +- name: pdu_add_system_time_0 + id: pdu_add_system_time + parameters: + affinity: '' + alias: '' + comment: 'This block will add the system + + time to the PDU metadata as a + + double precision float. The time + + key can be set by the user.' + key: timekey + maxoutbuf: '0' + minoutbuf: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [240, 532.0] + rotation: 0 + state: true +- name: pdu_pdu_set_0 + id: pdu_pdu_set + parameters: + affinity: '' + alias: '' + comment: 'This block will add a key/value pair + + to the PDU metadata dictionary.' + k: pmt.intern("KEY1") + maxoutbuf: '0' + minoutbuf: '0' + v: pmt.from_double(val) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [720, 252.0] + rotation: 0 + state: true +- name: pdu_pdu_split_0 + id: pdu_pdu_split + parameters: + affinity: '' + alias: '' + comment: 'The PDU will be split into its metadata dictionary + + and uniform vector, which will then be emitted as + + dict/uvec PMT messages respectively.' + maxoutbuf: '0' + minoutbuf: '0' + pass_empty: 'False' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [280, 808.0] + rotation: 0 + state: true +- name: pdu_random_pdu_0 + id: pdu_random_pdu + parameters: + affinity: '' + alias: '' + comment: 'This block will emit a random PDU with + + an empty dictionary each time it + + receives an input message. The input + + message composition does not matter.' + length_modulo: '256' + mask: '0xFF' + maxoutbuf: '0' + maxsize: '4096' + minoutbuf: '0' + minsize: '2048' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [320, 236.0] + rotation: 0 + state: true +- name: pdu_time_delta_0 + id: pdu_time_delta + parameters: + affinity: '' + alias: '' + comment: "This block calculates the ms that have \nelapsed between when the time\ + \ key\nwas added and now, and print statistics\nwhen the flowgraph stops." + delta_key: pmt.intern("time_delta_ms") + maxoutbuf: '0' + minoutbuf: '0' + time_key: timekey + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [752, 524.0] + rotation: 0 + state: true +- name: qtgui_time_raster_sink_x_0 + id: qtgui_time_raster_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + axislabels: 'True' + color1: '0' + color10: '0' + color2: '0' + color3: '0' + color4: '0' + color5: '0' + color6: '0' + color7: '0' + color8: '0' + color9: '0' + comment: Colors for fun. + grid: 'False' + gui_hint: '' + label1: '' + label10: '' + label2: '' + label3: '' + label4: '' + label5: '' + label6: '' + label7: '' + label8: '' + label9: '' + mult: '[]' + name: '"This Plot Is Not Meaningful"' + ncols: '128' + nconnections: '1' + nrows: '256' + offset: '[]' + samp_rate: '1' + type: msg_byte + update_time: '0.10' + x_end_value: '0.0' + x_label: '""' + x_start_value: '0.0' + y_end_value: '0.0' + y_label: '""' + y_start_value: '0.0' + zmax: '127' + zmin: '-128' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [904, 820.0] + rotation: 0 + state: true +- name: virtual_sink_0 + id: virtual_sink + parameters: + alias: '' + comment: '' + stream_id: x + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1048, 260.0] + rotation: 0 + state: true +- name: virtual_sink_1 + id: virtual_sink + parameters: + alias: '' + comment: '' + stream_id: y + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1056, 532.0] + rotation: 0 + state: true +- name: virtual_source_0 + id: virtual_source + parameters: + alias: '' + comment: '' + stream_id: x + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [48, 532.0] + rotation: 0 + state: true +- name: virtual_source_1 + id: virtual_source + parameters: + alias: '' + comment: '' + stream_id: y + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [32, 820.0] + rotation: 0 + state: true + +connections: +- [blocks_message_strobe_0, strobe, pdu_random_pdu_0, generate] +- [pdu_add_system_time_0, pdu, pdu_time_delta_0, pdu] +- [pdu_pdu_set_0, pdus, virtual_sink_0, '0'] +- [pdu_pdu_split_0, dict, blocks_message_debug_0, print] +- [pdu_pdu_split_0, vec, qtgui_time_raster_sink_x_0, in] +- [pdu_random_pdu_0, pdus, pdu_pdu_set_0, pdus] +- [pdu_time_delta_0, pdu, virtual_sink_1, '0'] +- [virtual_source_0, '0', pdu_add_system_time_0, pdu] +- [virtual_source_1, '0', pdu_pdu_split_0, pdu] + +metadata: + file_format: 1 diff --git a/gr-pdu/grc/pdu.tree.yml b/gr-pdu/grc/pdu.tree.yml index da988d007d..2c1d2a2c9c 100644 --- a/gr-pdu/grc/pdu.tree.yml +++ b/gr-pdu/grc/pdu.tree.yml @@ -1,5 +1,6 @@ '[Core]': -- Message Tools: +- PDU Tools: + - pdu_add_system_time - pdu_pdu_filter - pdu_pdu_remove - pdu_pdu_set @@ -7,3 +8,4 @@ - pdu_pdu_to_tagged_stream - pdu_tagged_stream_to_pdu - pdu_random_pdu + - pdu_time_delta diff --git a/gr-pdu/grc/pdu_add_system_time.block.yml b/gr-pdu/grc/pdu_add_system_time.block.yml new file mode 100644 index 0000000000..e0c9be1aee --- /dev/null +++ b/gr-pdu/grc/pdu_add_system_time.block.yml @@ -0,0 +1,27 @@ +id: pdu_add_system_time +label: Add System Time +flags: [ python ] + +parameters: +- id: key + label: Key + dtype: raw + default: pmt.intern("system_time") + +inputs: +- domain: message + id: pdu + optional: true + +outputs: +- domain: message + id: pdu + optional: true + +templates: + imports: |- + from gnuradio import pdu + import pmt + make: pdu.add_system_time(${key}) + +file_format: 1 diff --git a/gr-pdu/grc/pdu_pdu_split.block.yml b/gr-pdu/grc/pdu_pdu_split.block.yml index 1f9ca1a75f..6dcd8f76e4 100644 --- a/gr-pdu/grc/pdu_pdu_split.block.yml +++ b/gr-pdu/grc/pdu_pdu_split.block.yml @@ -1,6 +1,6 @@ id: pdu_pdu_split label: PDU Split -category: '[Core]/PDU' +flags: [ python ] parameters: - id: pass_empty @@ -11,14 +11,14 @@ parameters: inputs: - domain: message - id: pdus + id: pdu outputs: - domain: message id: dict optional: true - domain: message - id: data + id: vec optional: true templates: diff --git a/gr-pdu/grc/pdu_time_delta.block.yml b/gr-pdu/grc/pdu_time_delta.block.yml new file mode 100644 index 0000000000..98aa8f3d63 --- /dev/null +++ b/gr-pdu/grc/pdu_time_delta.block.yml @@ -0,0 +1,31 @@ +id: pdu_time_delta +label: Time Delta +flags: [ python ] + +parameters: +- id: delta_key + label: Time Delta Key + dtype: raw + default: pmt.intern("time_delta_ms") +- id: time_key + label: Time Key + dtype: raw + default: pmt.intern("system_time") + +inputs: +- domain: message + id: pdu + optional: true + +outputs: +- domain: message + id: pdu + optional: true + +templates: + imports: |- + from gnuradio import pdu + import pmt + make: pdu.time_delta(${delta_key}, ${time_key}) + +file_format: 1 diff --git a/gr-pdu/include/gnuradio/pdu/CMakeLists.txt b/gr-pdu/include/gnuradio/pdu/CMakeLists.txt index 987a54f45b..c3d626a3be 100644 --- a/gr-pdu/include/gnuradio/pdu/CMakeLists.txt +++ b/gr-pdu/include/gnuradio/pdu/CMakeLists.txt @@ -10,6 +10,7 @@ ######################################################################## install(FILES api.h + add_system_time.h pdu_filter.h pdu_remove.h pdu_set.h @@ -17,6 +18,7 @@ install(FILES pdu_to_tagged_stream.h random_pdu.h tagged_stream_to_pdu.h + time_delta.h DESTINATION ${GR_INCLUDE_DIR}/gnuradio/pdu ) diff --git a/gr-pdu/include/gnuradio/pdu/add_system_time.h b/gr-pdu/include/gnuradio/pdu/add_system_time.h new file mode 100644 index 0000000000..b8a6037bad --- /dev/null +++ b/gr-pdu/include/gnuradio/pdu/add_system_time.h @@ -0,0 +1,43 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_PDU_ADD_SYSTEM_TIME_H +#define INCLUDED_PDU_ADD_SYSTEM_TIME_H + +#include <gnuradio/block.h> +#include <gnuradio/pdu/api.h> + +namespace gr { +namespace pdu { + +/*! + * \brief Adds system time to a PDU's metadata + * \ingroup debug_tools_blk + * \ingroup pdu_blk + * + * Adds a user specified key to PDU dict containing the boost system time in seconds + * since unix epoch. + */ +class PDU_API add_system_time : virtual public gr::block +{ +public: + typedef std::shared_ptr<add_system_time> sptr; + + /*! + * \brief Return a shared_ptr to a new instance of pdu::add_system_time. + * + * @param key - key to use for system time metadata field + */ + static sptr make(const pmt::pmt_t key); +}; + +} // namespace pdu +} // namespace gr + +#endif /* INCLUDED_PDU_ADD_SYSTEM_TIME_H */ diff --git a/gr-pdu/include/gnuradio/pdu/time_delta.h b/gr-pdu/include/gnuradio/pdu/time_delta.h new file mode 100644 index 0000000000..963877f5db --- /dev/null +++ b/gr-pdu/include/gnuradio/pdu/time_delta.h @@ -0,0 +1,50 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_PDU_TIME_DELTA_H +#define INCLUDED_PDU_TIME_DELTA_H + +#include <gnuradio/block.h> +#include <gnuradio/pdu/api.h> + +namespace gr { +namespace pdu { + +/*! + * \brief Compute system time differences and provide statistics upon completion. + * \ingroup debug_tools_blk + * \ingroup pdu_blk + * + * This block computes the difference between the current system time and a defined PDU + * key previously added (e.g.: by by the add_system_time block). This is useful for + * benchmarking PDU operations. The block also maintians running estimates of mean and + * variance, accessible by getters and printed when the flowgraph is stopped. + */ +class PDU_API time_delta : virtual public gr::block +{ +public: + typedef std::shared_ptr<time_delta> sptr; + + /*! + * \brief Return a shared_ptr to a new instance of pdu::time_delta. + * + * @param delta_key - key to use for time delta metadata field + * @param time_key - key to use as reference time for delta + */ + static sptr make(const pmt::pmt_t delta_key, const pmt::pmt_t time_key); + + virtual double get_variance(void) = 0; + virtual double get_mean(void) = 0; + virtual void reset_stats(void) = 0; +}; + +} // namespace pdu +} // namespace gr + +#endif /* INCLUDED_PDU_TIME_DELTA_H */ diff --git a/gr-pdu/lib/CMakeLists.txt b/gr-pdu/lib/CMakeLists.txt index 7de8a52bf8..cd071cf73f 100644 --- a/gr-pdu/lib/CMakeLists.txt +++ b/gr-pdu/lib/CMakeLists.txt @@ -9,6 +9,7 @@ # Setup library ######################################################################## add_library(gnuradio-pdu + add_system_time_impl.cc pdu_filter_impl.cc pdu_remove_impl.cc pdu_set_impl.cc @@ -16,6 +17,7 @@ add_library(gnuradio-pdu pdu_to_tagged_stream_impl.cc random_pdu_impl.cc tagged_stream_to_pdu_impl.cc + time_delta_impl.cc ) #Add Windows DLL resource file if using MSVC diff --git a/gr-pdu/lib/add_system_time_impl.cc b/gr-pdu/lib/add_system_time_impl.cc new file mode 100644 index 0000000000..50cebc634a --- /dev/null +++ b/gr-pdu/lib/add_system_time_impl.cc @@ -0,0 +1,68 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "add_system_time_impl.h" +#include <gnuradio/io_signature.h> +#include <gnuradio/pdu.h> + +namespace gr { +namespace pdu { + +add_system_time::sptr add_system_time::make(const pmt::pmt_t key) +{ + return gnuradio::make_block_sptr<add_system_time_impl>(key); +} + +/* + * The private constructor + */ +add_system_time_impl::add_system_time_impl(const pmt::pmt_t key) + : gr::block("add_system_time", + gr::io_signature::make(0, 0, 0), + gr::io_signature::make(0, 0, 0)), + d_name(pmt::symbol_to_string(key)), + d_epoch(boost::gregorian::date(1970, 1, 1)), + d_key(key) +{ + // boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); + // d_epoch = epoch; + + message_port_register_in(msgport_names::pdu()); + set_msg_handler(msgport_names::pdu(), + [this](const pmt::pmt_t& msg) { this->handle_pdu(msg); }); + message_port_register_out(msgport_names::pdu()); +} + +/* + * Our virtual destructor. + */ +add_system_time_impl::~add_system_time_impl() {} + +void add_system_time_impl::handle_pdu(const pmt::pmt_t& pdu) +{ + // make sure the message is a PDU + if (!(pmt::is_pdu(pdu))) { + GR_LOG_WARN(d_logger, "Message received is not a PDU, dropping"); + return; + } + + pmt::pmt_t meta = pmt::car(pdu); + + // append time and publish + double t_now((boost::get_system_time() - d_epoch).total_microseconds() / 1000000.0); + meta = pmt::dict_add(meta, d_key, pmt::from_double(t_now)); + message_port_pub(msgport_names::pdu(), pmt::cons(meta, pmt::cdr(pdu))); +} + +} /* namespace pdu */ +} /* namespace gr */ diff --git a/gr-pdu/lib/add_system_time_impl.h b/gr-pdu/lib/add_system_time_impl.h new file mode 100644 index 0000000000..722bd112af --- /dev/null +++ b/gr-pdu/lib/add_system_time_impl.h @@ -0,0 +1,35 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_PDU_ADD_SYSTEM_TIME_IMPL_H +#define INCLUDED_PDU_ADD_SYSTEM_TIME_IMPL_H + +#include <gnuradio/pdu/add_system_time.h> + +namespace gr { +namespace pdu { + +class add_system_time_impl : public add_system_time +{ +private: + std::string d_name; + const boost::posix_time::ptime d_epoch; + pmt::pmt_t d_key; + + void handle_pdu(const pmt::pmt_t& pdu); + +public: + add_system_time_impl(const pmt::pmt_t key); + ~add_system_time_impl() override; +}; + +} // namespace pdu +} // namespace gr + +#endif /* INCLUDED_PDU_ADD_SYSTEM_TIME_IMPL_H */ diff --git a/gr-pdu/lib/pdu_split_impl.cc b/gr-pdu/lib/pdu_split_impl.cc index 678e650ee2..8eb14bfffb 100644 --- a/gr-pdu/lib/pdu_split_impl.cc +++ b/gr-pdu/lib/pdu_split_impl.cc @@ -28,18 +28,18 @@ pdu_split_impl::pdu_split_impl(const bool pass_empty_data) : gr::block("pdu_split", io_signature::make(0, 0, 0), io_signature::make(0, 0, 0)), d_pass_empty_data(pass_empty_data) { - message_port_register_in(msgport_names::pdus()); - set_msg_handler(msgport_names::pdus(), - [this](pmt::pmt_t msg) { this->handle_pdu(msg); }); + message_port_register_in(msgport_names::pdu()); + set_msg_handler(msgport_names::pdu(), + [this](const pmt::pmt_t& msg) { this->handle_pdu(msg); }); message_port_register_out(msgport_names::dict()); message_port_register_out(msgport_names::vec()); } pdu_split_impl::~pdu_split_impl() {} -void pdu_split_impl::handle_pdu(pmt::pmt_t pdu) +void pdu_split_impl::handle_pdu(const pmt::pmt_t& pdu) { - // make sure the data is a PDU + // make sure the message is a PDU if (!(pmt::is_pdu(pdu))) { GR_LOG_WARN(d_logger, "Message received is not a PDU, dropping"); return; diff --git a/gr-pdu/lib/pdu_split_impl.h b/gr-pdu/lib/pdu_split_impl.h index cf26b5055e..d198e2c8d0 100644 --- a/gr-pdu/lib/pdu_split_impl.h +++ b/gr-pdu/lib/pdu_split_impl.h @@ -27,7 +27,7 @@ private: * * \param pdu A PDU message passed from the scheduler's message handling. */ - void handle_pdu(pmt::pmt_t pdu); + void handle_pdu(const pmt::pmt_t& pdu); public: pdu_split_impl(const bool pass_empty_data); diff --git a/gr-pdu/lib/time_delta_impl.cc b/gr-pdu/lib/time_delta_impl.cc new file mode 100644 index 0000000000..e7213dc0ff --- /dev/null +++ b/gr-pdu/lib/time_delta_impl.cc @@ -0,0 +1,108 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "time_delta_impl.h" +#include <gnuradio/io_signature.h> +#include <gnuradio/pdu.h> + +namespace gr { +namespace pdu { + +time_delta::sptr time_delta::make(const pmt::pmt_t delta_key, const pmt::pmt_t time_key) +{ + return gnuradio::make_block_sptr<time_delta_impl>(delta_key, time_key); +} + +time_delta_impl::time_delta_impl(const pmt::pmt_t delta_key, const pmt::pmt_t time_key) + : gr::block("time_delta", io_signature::make(0, 0, 0), io_signature::make(0, 0, 0)), + d_name(pmt::symbol_to_string(delta_key)), + d_delta_key(delta_key), + d_time_key(time_key), + d_epoch(boost::gregorian::date(1970, 1, 1)), + d_mean(0.0), + d_var_acc(0.0), + d_var(0.0), + d_n(0) +{ + // boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); + // d_epoch = epoch; + message_port_register_in(msgport_names::pdu()); + set_msg_handler(msgport_names::pdu(), + [this](const pmt::pmt_t& msg) { this->handle_pdu(msg); }); + message_port_register_out(msgport_names::pdu()); +} + +time_delta_impl::~time_delta_impl() {} + +bool time_delta_impl::stop() +{ + GR_LOG_NOTICE(d_logger, + boost::format("Statistics for %s: Mean = %0.6f ms, Var = %0.6f ms") % + d_name % d_mean % get_variance()); + return true; +} + +void time_delta_impl::reset_stats() +{ + // do not allow statistics to be reset while they are being accessed + gr::thread::scoped_lock l(d_mutex); + + d_n = 0; + d_mean = 0; + d_var_acc = 0; + d_var = 0; +} + +void time_delta_impl::update_stats(double x) +{ + // uses Welford's Method to calculate the running variance + double prev_mean = d_mean; + d_n++; + d_mean = d_mean + (x - d_mean) / d_n; + d_var_acc += (x - d_mean) * (x - prev_mean); + d_var = d_var_acc / (d_n - 1); +} + +void time_delta_impl::handle_pdu(const pmt::pmt_t& pdu) +{ + // make sure the message is a PDU + if (!(pmt::is_pdu(pdu))) { + GR_LOG_WARN(d_logger, "Message received is not a PDU, dropping"); + return; + } + + gr::thread::scoped_lock l(d_mutex); + + double t_now((boost::get_system_time() - d_epoch).total_microseconds() / 1000000.0); + + pmt::pmt_t meta = pmt::car(pdu); + pmt::pmt_t sys_time_pmt = pmt::dict_ref(meta, d_time_key, pmt::PMT_NIL); + if (!pmt::is_real(sys_time_pmt)) { + GR_LOG_INFO(d_logger, + boost::format("PDU received with no system time at %f") % t_now); + return; + } + + double pdu_time = pmt::to_double(sys_time_pmt); + double time_delta_ms = (t_now - pdu_time) * 1000.0; + GR_LOG_DEBUG(d_logger, + boost::format("%s PDU received at %f with time delta %f milliseconds") % + d_name % t_now % time_delta_ms); + update_stats(time_delta_ms); + + meta = pmt::dict_add(meta, d_delta_key, pmt::from_double(time_delta_ms)); + message_port_pub(msgport_names::pdu(), pmt::cons(meta, pmt::cdr(pdu))); +} + +} /* namespace pdu */ +} /* namespace gr */ diff --git a/gr-pdu/lib/time_delta_impl.h b/gr-pdu/lib/time_delta_impl.h new file mode 100644 index 0000000000..621964b380 --- /dev/null +++ b/gr-pdu/lib/time_delta_impl.h @@ -0,0 +1,49 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_PDU_TIME_DELTA_IMPL_H +#define INCLUDED_PDU_TIME_DELTA_IMPL_H + +#include <gnuradio/pdu/time_delta.h> + +namespace gr { +namespace pdu { + +class time_delta_impl : public time_delta +{ +private: + std::string d_name; + const pmt::pmt_t d_delta_key; + const pmt::pmt_t d_time_key; + const boost::posix_time::ptime d_epoch; + + gr::thread::mutex d_mutex; + + // statistic tracking + double d_mean, d_var_acc, d_var; + size_t d_n; + + void update_stats(double x); + void handle_pdu(const pmt::pmt_t& pdu); + +public: + time_delta_impl(const pmt::pmt_t delta_key, const pmt::pmt_t time_key); + ~time_delta_impl() override; + + bool stop() override; + + double get_variance(void) override { return d_var; }; + double get_mean(void) override { return d_mean; }; + void reset_stats(void) override; +}; + +} // namespace pdu +} // namespace gr + +#endif /* INCLUDED_PDU_TIME_DELTA_IMPL_H */ diff --git a/gr-pdu/python/pdu/bindings/CMakeLists.txt b/gr-pdu/python/pdu/bindings/CMakeLists.txt index 79e33229e0..79334e2440 100644 --- a/gr-pdu/python/pdu/bindings/CMakeLists.txt +++ b/gr-pdu/python/pdu/bindings/CMakeLists.txt @@ -5,6 +5,7 @@ include(GrPybind) ######################################################################## list(APPEND pdu_python_files + add_system_time_python.cc pdu_filter_python.cc pdu_remove_python.cc pdu_set_python.cc @@ -12,6 +13,7 @@ list(APPEND pdu_python_files pdu_to_tagged_stream_python.cc random_pdu_python.cc tagged_stream_to_pdu_python.cc + time_delta_python.cc python_bindings.cc) GR_PYBIND_MAKE_CHECK_HASH(pdu diff --git a/gr-pdu/python/pdu/bindings/add_system_time_python.cc b/gr-pdu/python/pdu/bindings/add_system_time_python.cc new file mode 100644 index 0000000000..97b3d1d660 --- /dev/null +++ b/gr-pdu/python/pdu/bindings/add_system_time_python.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2021 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(add_system_time.h) */ +/* BINDTOOL_HEADER_FILE_HASH(3abd057660e6652a1bb162bc47c0db73) */ +/***********************************************************************************/ + +#include <pybind11/complex.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + +namespace py = pybind11; + +#include <gnuradio/pdu/add_system_time.h> +// pydoc.h is automatically generated in the build directory +#include <add_system_time_pydoc.h> + +void bind_add_system_time(py::module& m) +{ + + using add_system_time = gr::pdu::add_system_time; + + + py::class_<add_system_time, + gr::block, + gr::basic_block, + std::shared_ptr<add_system_time>>(m, "add_system_time", D(add_system_time)) + + .def(py::init(&add_system_time::make), py::arg("key"), D(add_system_time, make)) + + + ; +} diff --git a/gr-pdu/python/pdu/bindings/docstrings/add_system_time_pydoc_template.h b/gr-pdu/python/pdu/bindings/docstrings/add_system_time_pydoc_template.h new file mode 100644 index 0000000000..3de2f612dc --- /dev/null +++ b/gr-pdu/python/pdu/bindings/docstrings/add_system_time_pydoc_template.h @@ -0,0 +1,21 @@ +/* + * Copyright 2021 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, pdu, __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_pdu_add_system_time = R"doc()doc"; + + +static const char* __doc_gr_pdu_add_system_time_make = R"doc()doc"; diff --git a/gr-pdu/python/pdu/bindings/docstrings/time_delta_pydoc_template.h b/gr-pdu/python/pdu/bindings/docstrings/time_delta_pydoc_template.h new file mode 100644 index 0000000000..7c3b25060f --- /dev/null +++ b/gr-pdu/python/pdu/bindings/docstrings/time_delta_pydoc_template.h @@ -0,0 +1,21 @@ +/* + * Copyright 2021 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, pdu, __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_pdu_time_delta = R"doc()doc"; + + +static const char* __doc_gr_pdu_time_delta_make = R"doc()doc"; diff --git a/gr-pdu/python/pdu/bindings/python_bindings.cc b/gr-pdu/python/pdu/bindings/python_bindings.cc index 3c311e5848..0739908789 100644 --- a/gr-pdu/python/pdu/bindings/python_bindings.cc +++ b/gr-pdu/python/pdu/bindings/python_bindings.cc @@ -15,6 +15,7 @@ namespace py = pybind11; +void bind_add_system_time(py::module&); void bind_pdu_filter(py::module&); void bind_pdu_remove(py::module&); void bind_pdu_set(py::module&); @@ -22,6 +23,7 @@ void bind_pdu_split(py::module&); void bind_pdu_to_tagged_stream(py::module&); void bind_random_pdu(py::module&); void bind_tagged_stream_to_pdu(py::module&); +void bind_time_delta(py::module&); // We need this hack because import_array() returns NULL // for newer Python versions. @@ -42,6 +44,7 @@ PYBIND11_MODULE(pdu_python, m) // Allow access to base block methods py::module::import("gnuradio.gr"); + bind_add_system_time(m); bind_pdu_filter(m); bind_pdu_remove(m); bind_pdu_set(m); @@ -49,4 +52,5 @@ PYBIND11_MODULE(pdu_python, m) bind_pdu_to_tagged_stream(m); bind_random_pdu(m); bind_tagged_stream_to_pdu(m); + bind_time_delta(m); } diff --git a/gr-pdu/python/pdu/bindings/time_delta_python.cc b/gr-pdu/python/pdu/bindings/time_delta_python.cc new file mode 100644 index 0000000000..71790064ff --- /dev/null +++ b/gr-pdu/python/pdu/bindings/time_delta_python.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2021 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(time_delta.h) */ +/* BINDTOOL_HEADER_FILE_HASH(292cf88806dbd063fba41550fc47a9b9) */ +/***********************************************************************************/ + +#include <pybind11/complex.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + +namespace py = pybind11; + +#include <gnuradio/pdu/time_delta.h> +// pydoc.h is automatically generated in the build directory +#include <time_delta_pydoc.h> + +void bind_time_delta(py::module& m) +{ + + using time_delta = gr::pdu::time_delta; + + + py::class_<time_delta, gr::block, gr::basic_block, std::shared_ptr<time_delta>>( + m, "time_delta", D(time_delta)) + + .def(py::init(&time_delta::make), + py::arg("delta_key"), + py::arg("time_key"), + D(time_delta, make)) + + + ; +} diff --git a/gr-pdu/python/pdu/qa_add_system_time.py b/gr-pdu/python/pdu/qa_add_system_time.py new file mode 100755 index 0000000000..44d0ad4bef --- /dev/null +++ b/gr-pdu/python/pdu/qa_add_system_time.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# +# Copyright 2021 NTESS LLC. +# +# This file is part of GNU Radio +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# + + +from gnuradio import gr, gr_unittest, blocks, pdu +import pmt +import time + + +class qa_add_system_time(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + self.add_sys_time = pdu.add_system_time(pmt.intern('systime')) + self.debug = blocks.message_debug() + + # make connections + self.tb.msg_connect((self.add_sys_time, 'pdu'), (self.debug, 'store')) + self.tb.msg_connect((self.add_sys_time, 'pdu'), (self.debug, 'print')) + + def tearDown(self): + self.tb = None + + def test_001_basic_io(self): + self.tb.start() + # provide two non PDU inputs and one PDU input + self.add_sys_time.to_basic_block()._post(pmt.intern("pdu"), pmt.intern("BAD PDU")) + self.add_sys_time.to_basic_block()._post(pmt.intern("pdu"), + pmt.cons(pmt.from_long(4), pmt.PMT_NIL)) + self.add_sys_time.to_basic_block()._post(pmt.intern("pdu"), + pmt.cons(pmt.make_dict(), pmt.init_f32vector(1, [0.0]))) + self.waitFor(lambda: self.debug.num_messages() >= 1, timeout=1.0, poll_interval=0.01) + self.tb.stop() + self.tb.wait() + + # make sure we got one message and it has a systime key + self.assertEqual(1, self.debug.num_messages()) + self.assertTrue(pmt.dict_has_key(pmt.car(self.debug.get_message(0)), pmt.intern('systime'))) + + def test_002_timing(self): + self.tb.start() + + self.add_sys_time.to_basic_block()._post(pmt.intern("pdu"), pmt.intern("BAD PDU")) + self.add_sys_time.to_basic_block()._post(pmt.intern("pdu"), + pmt.cons(pmt.make_dict(), pmt.init_u8vector(1, [0]))) + time.sleep(1.0) # wait for one second to provide a time difference between messages + self.add_sys_time.to_basic_block()._post(pmt.intern("pdu"), + pmt.cons(pmt.make_dict(), pmt.init_u8vector(1, [0]))) + self.waitFor(lambda: self.debug.num_messages() == 2, timeout=1.0, poll_interval=0.01) + self.tb.stop() + self.tb.wait() + + t0 = pmt.to_double(pmt.dict_ref(pmt.car(self.debug.get_message(0)), + pmt.intern("systime"), + pmt.from_double(0.0))) + t1 = pmt.to_double(pmt.dict_ref(pmt.car(self.debug.get_message(1)), + pmt.intern("systime"), + pmt.from_double(0.0))) + self.assertTrue(((t1 - t0) - 1) < 0.05) # should be sufficient tolerance + + +if __name__ == '__main__': + gr_unittest.run(qa_add_system_time) diff --git a/gr-pdu/python/pdu/qa_pdu_split.py b/gr-pdu/python/pdu/qa_pdu_split.py index 76b60499a7..988fb6b164 100644 --- a/gr-pdu/python/pdu/qa_pdu_split.py +++ b/gr-pdu/python/pdu/qa_pdu_split.py @@ -35,10 +35,10 @@ class qa_pdu_split (gr_unittest.TestCase): in_pdu = pmt.cons(in_meta1, pmt.init_u8vector(6, range(6))) self.tb.start() - split.to_basic_block()._post(pmt.intern("pdus"), pmt.intern("MALFORMED PDU")) - split.to_basic_block()._post(pmt.intern("pdus"), pmt.cons(pmt.PMT_NIL, pmt.init_u8vector(2, range(2)))) - split.to_basic_block()._post(pmt.intern("pdus"), pmt.cons(in_meta2, pmt.init_u8vector(0, []))) - split.to_basic_block()._post(pmt.intern("pdus"), in_pdu) + split.to_basic_block()._post(pmt.intern("pdu"), pmt.intern("MALFORMED PDU")) + split.to_basic_block()._post(pmt.intern("pdu"), pmt.cons(pmt.PMT_NIL, pmt.init_u8vector(2, range(2)))) + split.to_basic_block()._post(pmt.intern("pdu"), pmt.cons(in_meta2, pmt.init_u8vector(0, []))) + split.to_basic_block()._post(pmt.intern("pdu"), in_pdu) split.to_basic_block()._post(pmt.intern("system"), pmt.cons(pmt.intern("done"), pmt.from_long(1))) self.waitFor(lambda: d1.num_messages() == 2, timeout=1.0, poll_interval=0.01) self.waitFor(lambda: d2.num_messages() == 2, timeout=1.0, poll_interval=0.01) @@ -64,10 +64,10 @@ class qa_pdu_split (gr_unittest.TestCase): in_pdu = pmt.cons(in_meta1, pmt.init_u8vector(6, range(6))) self.tb.start() - split.to_basic_block()._post(pmt.intern("pdus"), pmt.intern("MALFORMED PDU")) - split.to_basic_block()._post(pmt.intern("pdus"), pmt.cons(pmt.PMT_NIL, pmt.init_u8vector(2, range(2)))) - split.to_basic_block()._post(pmt.intern("pdus"), pmt.cons(in_meta2, pmt.init_u8vector(0, []))) - split.to_basic_block()._post(pmt.intern("pdus"), in_pdu) + split.to_basic_block()._post(pmt.intern("pdu"), pmt.intern("MALFORMED PDU")) + split.to_basic_block()._post(pmt.intern("pdu"), pmt.cons(pmt.PMT_NIL, pmt.init_u8vector(2, range(2)))) + split.to_basic_block()._post(pmt.intern("pdu"), pmt.cons(in_meta2, pmt.init_u8vector(0, []))) + split.to_basic_block()._post(pmt.intern("pdu"), in_pdu) split.to_basic_block()._post(pmt.intern("system"), pmt.cons(pmt.intern("done"), pmt.from_long(1))) self.waitFor(lambda: d1.num_messages() == 3, timeout=1.0, poll_interval=0.01) self.waitFor(lambda: d2.num_messages() == 3, timeout=1.0, poll_interval=0.01) diff --git a/gr-pdu/python/pdu/qa_time_delta.py b/gr-pdu/python/pdu/qa_time_delta.py new file mode 100755 index 0000000000..0cb0f99020 --- /dev/null +++ b/gr-pdu/python/pdu/qa_time_delta.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# +# Copyright 2021 NTESS LLC. +# +# This file is part of GNU Radio +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# + + +from gnuradio import gr, gr_unittest, blocks, pdu +import pmt +import numpy as np +import time + + +class qa_time_delta(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + self.time_delta = pdu.time_delta(pmt.intern("sys time delta (ms)"), pmt.intern("system_time")) + self.debug = blocks.message_debug() + + self.tb.msg_connect((self.time_delta, 'pdu'), (self.debug, 'store')) + + def tearDown(self): + self.tb = None + + def test_001_invalid_a(self): + self.tb.start() + self.time_delta.to_basic_block()._post(pmt.intern("pdu"), pmt.intern("NOT A PDU")) + time.sleep(0.01) # short delay to ensure the message is processed + self.tb.stop() + self.tb.wait() + + # nothing should be produced in this case + self.assertEqual(0, self.debug.num_messages()) + + def test_001_invalid_b(self): + in_data = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] + meta = pmt.dict_add(pmt.make_dict(), pmt.intern('sam'), pmt.from_double(25.1)) + in_pdu = pmt.cons(meta, pmt.init_c32vector(len(in_data), in_data)) + + # set up fg + self.tb.start() + self.time_delta.to_basic_block()._post(pmt.intern("pdu"), in_pdu) + time.sleep(0.01) # short delay to ensure the message is processed + self.tb.stop() + self.tb.wait() + + # nothing should be produced in this case + self.assertEqual(0, self.debug.num_messages()) + + def test_002_normal(self): + tnow = time.time() + + in_data = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] + meta = pmt.dict_add(pmt.make_dict(), pmt.intern('system_time'), pmt.from_double(tnow - 10.0)) + in_pdu = pmt.cons(meta, pmt.init_c32vector(len(in_data), in_data)) + + e_data = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] + e_meta = pmt.dict_add(pmt.make_dict(), pmt.intern('system_time'), pmt.from_double(tnow)) + e_meta = pmt.dict_add(e_meta, pmt.intern('sys time delta (ms)'), pmt.from_double(10000.0)) + e_pdu = pmt.cons(e_meta, pmt.init_c32vector(len(e_data), e_data)) + + # set up fg + self.tb.start() + self.time_delta.to_basic_block()._post(pmt.intern("pdu"), in_pdu) + self.waitFor(lambda: self.debug.num_messages() == 1, timeout=1.0, poll_interval=0.01) + self.tb.stop() + self.tb.wait() + + # check data + self.assertEqual(1, self.debug.num_messages()) + a_meta = pmt.car(self.debug.get_message(0)) + time_tag = pmt.dict_ref(a_meta, pmt.intern("system_time"), pmt.PMT_NIL) + delta_tag = pmt.dict_ref(a_meta, pmt.intern("sys time delta (ms)"), pmt.PMT_NIL) + self.assertAlmostEqual(tnow, pmt.to_double(time_tag), delta=60) + self.assertAlmostEqual(10000, pmt.to_double(delta_tag), delta=10) + + +if __name__ == '__main__': + gr_unittest.run(qa_time_delta) |