diff --git a/.codespellrc b/.codespellrc index 3a6a8940..f8b4cc8b 100644 --- a/.codespellrc +++ b/.codespellrc @@ -2,6 +2,6 @@ builtin = clear,rare,en-GB_to_en-US,names,informal,code check-filenames = check-hidden = -ignore-words-list = weill,sinc,mut,numer,uint,stdio +ignore-words-list = weill,sinc,mut,numer,uint,stdio,ws skip = */.git,*/build,*/prefix,./scripts/all_plugins,./libs quiet-level = 2 diff --git a/CMakeLists.txt b/CMakeLists.txt index fa3ad632..fe3ef6a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ include(cmake/prelude.cmake) project( "rtxi" - VERSION 3.1.0 + VERSION 3.2.0 DESCRIPTION "Real-Time eXperiment Interface" HOMEPAGE_URL "http://rtxi.org/" LANGUAGES C CXX diff --git a/plugins/data_recorder/data_recorder.cpp b/plugins/data_recorder/data_recorder.cpp index 6f19e896..27e90b66 100644 --- a/plugins/data_recorder/data_recorder.cpp +++ b/plugins/data_recorder/data_recorder.cpp @@ -34,11 +34,15 @@ #include "data_recorder.hpp" -#include +#include +#include +#include #include "debug.hpp" #include "fifo.hpp" #include "rtos.hpp" +#include "userprefs/userprefs.hpp" +#include "widgets.hpp" DataRecorder::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) : Widgets::Panel( @@ -47,6 +51,7 @@ DataRecorder::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) , blockList(new QComboBox) , channelList(new QComboBox) , typeList(new QComboBox) + , timeTagType(new QComboBox()) , selectionBox(new QListWidget) , recordStatus(new QLabel) , downsampleSpin(new QSpinBox(this)) @@ -130,10 +135,20 @@ DataRecorder::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) auto* stampLayout = new QHBoxLayout; // Add timestamp elements - stampLayout->addWidget(timeStampEdit); addTag = new QPushButton(tr("Tag")); stampLayout->addWidget(addTag); + + auto* timeTagLabel = new QLabel(tr("Time Tag Type:")); + stampLayout->addWidget(timeTagLabel); + timeTagType->addItem("Index", TIME_TAG_TYPE::INDEX); + timeTagType->addItem("Time Stamp", TIME_TAG_TYPE::TIME); + timeTagType->addItem("No Tag", TIME_TAG_TYPE::NONE); + stampLayout->addWidget(timeTagType); + QObject::connect(timeTagType, + QOverload::of(&QComboBox::currentIndexChanged), + this, + &DataRecorder::Panel::setTimeTagType); QObject::connect( addTag, &QPushButton::released, this, &DataRecorder::Panel::addNewTag); @@ -277,6 +292,10 @@ DataRecorder::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) &DataRecorder::Panel::record_signal, this, &DataRecorder::Panel::record_slot); + QObject::connect(this, + &DataRecorder::Panel::record_signal, + timeTagType, + &QComboBox::setDisabled); recording_timer->start(); } @@ -333,12 +352,13 @@ void DataRecorder::Panel::changeDataFile() fileDialog.setWindowTitle("Select Data File"); QSettings userprefs; - QSettings::setPath(QSettings::NativeFormat, - QSettings::SystemScope, - "/usr/local/share/rtxi/"); - fileDialog.setDirectory( - userprefs.value("/dirs/data", getenv("HOME")).toString()); // NOLINT + userprefs.beginGroup("settings"); + fileDialog.setDirectory(userprefs + .value(QString::fromStdString(std::string( + UserPrefs::HDF5_SAVE_LOCATION_KEY))) + .toString()); // NOLINT + userprefs.endGroup(); QStringList filterList; filterList.push_back("HDF5 files (*.h5)"); filterList.push_back("All files (*.*)"); @@ -362,9 +382,6 @@ void DataRecorder::Panel::changeDataFile() filename += ".h5"; } - // Write this directory to the user prefs as most recently used - userprefs.setValue("/dirs/data", fileDialog.directory().path()); - auto* hplugin = dynamic_cast(this->getHostPlugin()); hplugin->change_file(filename.toStdString()); this->fileNameEdit->setText(QString(hplugin->getOpenFilename().c_str())); @@ -470,6 +487,7 @@ void DataRecorder::Panel::startRecordClicked() this->starting_record_time = QTime::currentTime(); this->trialNum->setNum(hplugin->getTrialCount()); this->trialLength->setText("Recording..."); + this->timeTagType->setDisabled(true); } } @@ -487,6 +505,7 @@ void DataRecorder::Panel::stopRecordClicked() this->fileSize->setNum( static_cast(QFile(fileNameEdit->text()).size()) / (1024.0 * 1024.0)); + this->timeTagType->setDisabled(false); } } @@ -527,6 +546,21 @@ void DataRecorder::Panel::syncEnableRecordingButtons(const QString& /*unused*/) this->recordStatus->setText(ready ? "Ready" : "Not ready"); } +void DataRecorder::Panel::setTimeTagType(int tag_type) +{ + // Update our local copy of tag type if successfully changed in RT thread + if (dynamic_cast(this->getHostPlugin()) + ->changeIndexingType(tag_type)) + { + this->time_type = static_cast(tag_type); + }; +} + +DataRecorder::TIME_TAG_TYPE DataRecorder::Panel::getTimeTagType() const +{ + return this->time_type; +} + DataRecorder::Plugin::Plugin(Event::Manager* ev_manager) : Widgets::Plugin(ev_manager, std::string(DataRecorder::MODULE_NAME)) , recording(false) @@ -591,8 +625,15 @@ void DataRecorder::Plugin::startRecording() const std::unique_lock lk(this->m_channels_list_mut); this->append_new_trial(); const Event::Type event_type = Event::Type::RT_THREAD_UNPAUSE_EVENT; + const Event::Type unpause_event_type = + Event::Type::RT_WIDGET_STATE_CHANGE_EVENT; std::vector start_recording_event; for (auto& rec_channel : this->m_recording_channels_list) { + start_recording_event.emplace_back(unpause_event_type); + start_recording_event.back().setParam( + "component", + static_cast(rec_channel.component.get())); + start_recording_event.back().setParam("state", RT::State::UNPAUSE); start_recording_event.emplace_back(event_type); start_recording_event.back().setParam( "thread", static_cast(rec_channel.component.get())); @@ -608,8 +649,15 @@ void DataRecorder::Plugin::stopRecording() } const std::unique_lock lk(this->m_channels_list_mut); const Event::Type event_type = Event::Type::RT_THREAD_PAUSE_EVENT; + const Event::Type pause_event_type = + Event::Type::RT_WIDGET_STATE_CHANGE_EVENT; std::vector stop_recording_event; for (auto& rec_chan : this->m_recording_channels_list) { + stop_recording_event.emplace_back(pause_event_type); + stop_recording_event.back().setParam( + "component", + static_cast(rec_chan.component.get())); + stop_recording_event.back().setParam("state", RT::State::PAUSE); stop_recording_event.emplace_back(event_type); stop_recording_event.back().setParam( "thread", static_cast(rec_chan.component.get())); @@ -618,6 +666,41 @@ void DataRecorder::Plugin::stopRecording() this->recording.store(false); } +bool DataRecorder::Plugin::changeIndexingType(int tag_type) +{ + // We only change the index type while not recording! + if (this->recording.load()) { + ERROR_MSG( + "DataRecorder::Plugin::changeIndexingType : Unable to change index " + "type while recording."); + return false; + } + if (tag_type < 0) { + ERROR_MSG( + "DataRecorderr::Plugin::changeIndexingType : Invalid index provided. " + "Unable to change index type"); + return false; + } + std::vector change_index_type_events; + change_index_type_events.reserve(this->m_recording_channels_list.size()); + for (auto& rec_chan : this->m_recording_channels_list) { + change_index_type_events.emplace_back( + Event::Type::RT_WIDGET_PARAMETER_CHANGE_EVENT); + change_index_type_events.back().setParam( + "paramID", std::any(static_cast(PARAMETER::INDEXING))); + change_index_type_events.back().setParam( + "paramType", std::any(Widgets::Variable::UINT_PARAMETER)); + change_index_type_events.back().setParam( + "paramValue", std::any(static_cast(tag_type))); + change_index_type_events.back().setParam( + "paramWidget", + std::any(static_cast(rec_chan.component.get()))); + } + this->getEventManager()->postEvent(change_index_type_events); + + return true; +} + void DataRecorder::Plugin::close_trial_group() { if (!open_file.load()) { @@ -677,15 +760,30 @@ void DataRecorder::Plugin::open_trial_group() H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); - for (auto& channel : this->m_recording_channels_list) { - compression_property = H5Pcreate(H5P_DATASET_CREATE); - H5Pset_deflate(compression_property, 7); - channel.hdf5_data_handle = - H5PTcreate(this->hdf5_handles.sync_group_handle, - channel.channel.name.c_str(), - this->hdf5_handles.channel_datatype_handle, - this->m_data_chunk_size, - compression_property); + const TIME_TAG_TYPE data_type = + dynamic_cast(this->getPanel())->getTimeTagType(); + if (data_type == NONE) { + for (auto& channel : this->m_recording_channels_list) { + compression_property = H5Pcreate(H5P_DATASET_CREATE); + H5Pset_deflate(compression_property, 7); + channel.hdf5_data_handle = + H5PTcreate(this->hdf5_handles.sync_group_handle, + channel.channel.name.c_str(), + H5T_IEEE_F64LE, + this->m_data_chunk_size, + compression_property); + } + } else { + for (auto& channel : this->m_recording_channels_list) { + compression_property = H5Pcreate(H5P_DATASET_CREATE); + H5Pset_deflate(compression_property, 7); + channel.hdf5_data_handle = + H5PTcreate(this->hdf5_handles.sync_group_handle, + channel.channel.name.c_str(), + this->hdf5_handles.channel_index_datatype_handle, + this->m_data_chunk_size, + compression_property); + } } } @@ -722,17 +820,16 @@ void DataRecorder::Plugin::openFile(const std::string& file_name) } this->hdf5_filename = file_name; this->trial_count = 0; - this->hdf5_handles.channel_datatype_handle = + this->hdf5_handles.channel_index_datatype_handle = H5Tcreate(H5T_COMPOUND, sizeof(DataRecorder::data_token_t)); - H5Tinsert(this->hdf5_handles.channel_datatype_handle, + H5Tinsert(this->hdf5_handles.channel_index_datatype_handle, "time", HOFFSET(DataRecorder::data_token_t, time), H5T_STD_I64LE); - H5Tinsert(this->hdf5_handles.channel_datatype_handle, + H5Tinsert(this->hdf5_handles.channel_index_datatype_handle, "value", HOFFSET(DataRecorder::data_token_t, value), H5T_IEEE_F64LE); - // this->open_trial_group(); this->open_file.store(true); } @@ -742,9 +839,10 @@ void DataRecorder::Plugin::closeFile() return; } const std::unique_lock lk(this->m_channels_list_mut); - // Attempt to close all group and dataset handles in hdf5 before closing file + // Attempt to close all group and dataset handles in hdf5 before closing + // file close_trial_group(); - H5Tclose(this->hdf5_handles.channel_datatype_handle); + H5Tclose(this->hdf5_handles.channel_index_datatype_handle); if (H5Fclose(this->hdf5_handles.file_handle) != 0) { ERROR_MSG("DataRecorder::Plugin::closeFile : Unable to close file {}", this->hdf5_filename); @@ -799,17 +897,17 @@ int DataRecorder::Plugin::create_component(IO::endpoint endpoint) this->getEventManager()->postEvent(&connect_event); hid_t data_handle = H5I_INVALID_HID; const std::unique_lock lk(this->m_channels_list_mut); - if (this->hdf5_handles.file_handle != H5I_INVALID_HID - && this->hdf5_handles.sync_group_handle != H5I_INVALID_HID) - { - const hid_t compression_property = H5Pcreate(H5P_DATASET_CREATE); - H5Pset_deflate(compression_property, 7); - data_handle = H5PTcreate(this->hdf5_handles.sync_group_handle, - chan.name.c_str(), - this->hdf5_handles.channel_datatype_handle, - this->m_data_chunk_size, - compression_property); - } + // if (this->hdf5_handles.file_handle != H5I_INVALID_HID + // && this->hdf5_handles.sync_group_handle != H5I_INVALID_HID) + // { + // const hid_t compression_property = H5Pcreate(H5P_DATASET_CREATE); + // H5Pset_deflate(compression_property, 7); + // data_handle = H5PTcreate(this->hdf5_handles.sync_group_handle, + // chan.name.c_str(), + // this->hdf5_handles.channel_index_datatype_handle, + // this->m_data_chunk_size, + // compression_property); + // } this->m_recording_channels_list.emplace_back( chan, std::move(component), data_handle); return 0; @@ -878,6 +976,23 @@ DataRecorder::Plugin::get_recording_channels() int DataRecorder::Plugin::apply_tag(const std::string& tag) { + // const std::string tag_key = + // std::string("TAG") + std::to_string(this->tag_count++); + // std::array string_dims = {tag.length()}; + // const hid_t tag_group_handle = + // H5Gcreate(this->hdf5_handles.sync_group_handle, + // tag_key.c_str(), + // H5P_DEFAULT, + // H5P_DEFAULT, + // H5P_DEFAULT); + // const hid_t dataspace_id = H5Screate_simple(1, string_dims.data(), + // nullptr); const hid_t tag_name_handle = H5Dcreate(tag_group_handle, + // tag.c_str(), + // H5T_C_S1, + // dataspace_id, + // H5P_DEFAULT, + // H5P_DEFAULT, + // H5P_DEFAULT); return 0; } @@ -887,19 +1002,52 @@ void DataRecorder::Plugin::process_data_worker() return; } std::vector data_buffer(this->m_data_chunk_size); + std::vector data_buffer_doubles; + data_buffer_doubles.reserve(this->m_data_chunk_size); const size_t packet_byte_size = sizeof(DataRecorder::data_token_t); int64_t read_bytes = 0; size_t packet_count = 0; + const TIME_TAG_TYPE time_type = + dynamic_cast(this->getPanel())->getTimeTagType(); const std::shared_lock lk(this->m_channels_list_mut); - for (auto& channel : this->m_recording_channels_list) { - while (read_bytes = channel.channel.data_source->read( - data_buffer.data(), packet_byte_size * data_buffer.size()), - read_bytes > 0) - { - packet_count = static_cast(read_bytes) / packet_byte_size; - DataRecorder::Plugin::save_data( - channel.hdf5_data_handle, data_buffer, packet_count); - } + switch (time_type) { + case INDEX: + case TIME: + for (auto& channel : this->m_recording_channels_list) { + while (read_bytes = channel.channel.data_source->read( + data_buffer.data(), packet_byte_size * data_buffer.size()), + read_bytes > 0) + { + packet_count = static_cast(read_bytes) / packet_byte_size; + DataRecorder::Plugin::save_data( + channel.hdf5_data_handle, data_buffer, packet_count); + } + } + break; + case NONE: + for (auto& channel : this->m_recording_channels_list) { + while (read_bytes = channel.channel.data_source->read( + data_buffer.data(), packet_byte_size * data_buffer.size()), + read_bytes > 0) + { + packet_count = static_cast(read_bytes) / packet_byte_size; + if (packet_count > data_buffer_doubles.size()) { + data_buffer_doubles.resize(packet_count); + } + for (size_t i = 0; i < packet_count; i++) { + data_buffer_doubles.at(i) = data_buffer.at(i).value; + } + DataRecorder::Plugin::save_data( + channel.hdf5_data_handle, data_buffer_doubles, packet_count); + data_buffer_doubles.clear(); + } + } + break; + default: + ERROR_MSG( + "DataRecorder::Plugin::process_data_worker : Bad time tagging type " + "detected. Unable to save data to hdf5 file"); + break; } } @@ -915,6 +1063,17 @@ void DataRecorder::Plugin::save_data( } } +void DataRecorder::Plugin::save_data(hid_t data_id, + const std::vector& data, + size_t packet_count) +{ + const herr_t err = + H5PTappend(data_id, static_cast(packet_count), data.data()); + if (err < 0) { + ERROR_MSG("Unable to write data into hdf5 file!"); + } +} + DataRecorder::Component::Component(Widgets::Plugin* hplugin, const std::string& probe_name) : Widgets::Component(hplugin, @@ -934,12 +1093,23 @@ void DataRecorder::Component::execute() const double value = readinput(0); switch (this->getState()) { case RT::State::EXEC: - data_sample.time = RT::OS::getTime(); + switch (this->getValue(PARAMETER::INDEXING)) { + case INDEXING: + data_sample.time = index++; + break; + case TIME: + data_sample.time = RT::OS::getTime(); + break; + case NONE: + default: + break; + } data_sample.value = value; this->m_fifo->writeRT(&data_sample, sizeof(DataRecorder::data_token_t)); break; case RT::State::UNPAUSE: case RT::State::INIT: + index = 0; this->setState(RT::State::EXEC); break; case RT::State::PAUSE: diff --git a/plugins/data_recorder/data_recorder.hpp b/plugins/data_recorder/data_recorder.hpp index d9e2364d..efa8d9fb 100644 --- a/plugins/data_recorder/data_recorder.hpp +++ b/plugins/data_recorder/data_recorder.hpp @@ -23,11 +23,12 @@ #include #include +#include #include +#include "fifo.hpp" #include "io.hpp" #include "widgets.hpp" -#include "fifo.hpp" class QComboBox; class QListWidget; @@ -48,12 +49,35 @@ typedef struct data_token_t double value; } data_token_t; +enum TIME_TAG_TYPE +{ + INDEX = 0, + TIME, + NONE +}; + +enum PARAMETER +{ + INDEXING = 0 +}; + constexpr size_t DEFAULT_BUFFER_SIZE = 10000 * sizeof(data_token_t); constexpr std::string_view MODULE_NAME = "Data Recorder"; inline std::vector get_default_vars() { - return {}; + return { + {PARAMETER::INDEXING, + "Indexing Scheme", + "Used to specify what type of indexing to use when recording. Options " + "are:\n" + "Index(0): add index to each value. resets after recorder is stopped\n" + "Time(1): Attaches current time in nanoseconds since some arbitrary " + "value. useful" + "for knowing the time between each data point.\n" + "None(3): Do not index the values. Just record them.", + Widgets::Variable::UINT_PARAMETER, + uint64_t {DataRecorder::TIME_TAG_TYPE::INDEX}}}; } inline std::vector get_default_channels() @@ -87,6 +111,7 @@ class Component : public Widgets::Component private: std::unique_ptr m_fifo; + int64_t index=0; }; class Panel : public Widgets::Panel @@ -101,6 +126,8 @@ class Panel : public Widgets::Panel Panel(QMainWindow* mwindow, Event::Manager* ev_manager); ~Panel() override = default; + TIME_TAG_TYPE getTimeTagType() const; + signals: void updateBlockInfo(); void record_signal(bool record); @@ -121,11 +148,13 @@ private slots: void addNewTag(); void processData(); void syncEnableRecordingButtons(const QString& /*unused*/); + void setTimeTagType(int tag_type); private: size_t m_buffer_size = DEFAULT_BUFFER_SIZE; size_t downsample_rate {1}; std::vector dataTags; + TIME_TAG_TYPE time_type = TIME_TAG_TYPE::INDEX; QGroupBox* channelGroup = nullptr; QGroupBox* stampGroup = nullptr; @@ -137,6 +166,7 @@ private slots: QComboBox* blockList = nullptr; QComboBox* channelList = nullptr; QComboBox* typeList = nullptr; + QComboBox* timeTagType = nullptr; QListWidget* selectionBox = nullptr; QLabel* recordStatus = nullptr; QPushButton* addRecorderButton = nullptr; @@ -176,6 +206,7 @@ class Plugin : public Widgets::Plugin void receiveEvent(Event::Object* event) override; void startRecording(); void stopRecording(); + bool changeIndexingType(int tag_type); void openFile(const std::string& file_name); void closeFile(); void change_file(const std::string& file_name); @@ -200,8 +231,12 @@ class Plugin : public Widgets::Plugin static void save_data(hid_t data_id, const std::vector& data, size_t packet_count); + static void save_data(hid_t data_id, + const std::vector& data, + size_t packet_count); hsize_t m_data_chunk_size = static_cast(1000); int m_compression_factor = 5; + int tag_count = 0; struct hdf5_handles { hid_t file_handle = H5I_INVALID_HID; @@ -210,7 +245,7 @@ class Plugin : public Widgets::Plugin hid_t sync_group_handle = H5I_INVALID_HID; hid_t async_group_handle = H5I_INVALID_HID; hid_t sys_data_group_handle = H5I_INVALID_HID; - hid_t channel_datatype_handle = H5I_INVALID_HID; + hid_t channel_index_datatype_handle = H5I_INVALID_HID; } hdf5_handles; struct recorder_t diff --git a/plugins/oscilloscope/oscilloscope.cpp b/plugins/oscilloscope/oscilloscope.cpp index fb9005a4..fc04f8db 100644 --- a/plugins/oscilloscope/oscilloscope.cpp +++ b/plugins/oscilloscope/oscilloscope.cpp @@ -884,6 +884,11 @@ void Oscilloscope::Panel::removeBlockChannels(IO::Block* block) { this->scopeWindow->removeBlockChannels(block); auto* hplugin = dynamic_cast(this->getHostPlugin()); + // Sometimes fired events may trigger this function after the plugin has been + // unloaded. in that case just return and don't crash please. + if (hplugin == nullptr) { + return; + } hplugin->deleteAllProbes(block); } diff --git a/plugins/performance_measurement/performance_measurement.cpp b/plugins/performance_measurement/performance_measurement.cpp index f81efe01..82baaaf8 100644 --- a/plugins/performance_measurement/performance_measurement.cpp +++ b/plugins/performance_measurement/performance_measurement.cpp @@ -105,7 +105,7 @@ PerformanceMeasurement::Panel::Panel(const std::string& mod_name, PerformanceMeasurement::Component::Component(Widgets::Plugin* hplugin) : Widgets::Component(hplugin, std::string(MODULE_NAME), - std::vector(), + PerformanceMeasurement::get_default_channels(), PerformanceMeasurement::get_default_vars()) { if (RT::OS::getFifo(this->fifo, @@ -137,6 +137,13 @@ void PerformanceMeasurement::Component::execute() switch (this->getState()) { case RT::State::EXEC: + writeoutput(0, stats.duration); + writeoutput(1, stats.timestep); + writeoutput(2, stats.latency); + writeoutput(3, stats.max_timestep); + writeoutput(4, stats.max_duration); + writeoutput(5, stats.max_latency); + writeoutput(6, stats.jitter); this->fifo->writeRT(&this->stats, sizeof(PerformanceMeasurement::performance_stats_t)); break; diff --git a/plugins/performance_measurement/performance_measurement.hpp b/plugins/performance_measurement/performance_measurement.hpp index 5d74626b..a511f573 100644 --- a/plugins/performance_measurement/performance_measurement.hpp +++ b/plugins/performance_measurement/performance_measurement.hpp @@ -24,9 +24,10 @@ #include "math/runningstat.h" #include "widgets.hpp" -namespace RT::OS{ +namespace RT::OS +{ class Fifo; -} // namespace RT::OS +} // namespace RT::OS class QLineEdit; @@ -36,22 +37,39 @@ namespace PerformanceMeasurement constexpr std::string_view MODULE_NAME = "RT Benchmarks"; inline std::vector get_default_vars() -{ - return - { - { - } - }; +{ + return {{}}; +} + +inline std::vector get_default_channels() +{ + return { + {"Duration", + "Real-Time measurement of time elapsed time (ns) for calculations each period", + IO::OUTPUT}, + {"Time Step", "Real-Time measurement of system period", IO::OUTPUT}, + {"Latency", "Time between intended wake-up and real wake-up", IO::OUTPUT}, + {"Max Duration", + "Maximum computation time measured since last reset", + IO::OUTPUT}, + {"Max Time Step", + "Maximum realtime period measured since last reset", + IO::OUTPUT}, + {"Max Latency", "Maximum latency measured since last reset", IO::OUTPUT}, + {"Jitter", + "Standard Deviation of the real-time period measured since last reset", + IO::OUTPUT}}; } -struct performance_stats_t{ - double duration=0.0; - double timestep=0.0; - double latency=0.0; - double max_duration=0.0; - double max_timestep=0.0; - double max_latency=0.0; - double jitter=0.0; +struct performance_stats_t +{ + double duration = 0.0; + double timestep = 0.0; + double latency = 0.0; + double max_duration = 0.0; + double max_timestep = 0.0; + double max_latency = 0.0; + double jitter = 0.0; }; class Plugin : public Widgets::Plugin @@ -59,28 +77,29 @@ class Plugin : public Widgets::Plugin public: explicit Plugin(Event::Manager* ev_manager); performance_stats_t getSampleStat(); + private: RT::OS::Fifo* component_fifo; }; // class Plugin class Component : public Widgets::Component { -public: - explicit Component(Widgets::Plugin* hplugin); +public: + explicit Component(Widgets::Plugin* hplugin); void setTickPointers(int64_t* s_ticks, int64_t* e_ticks); void execute() override; - RT::OS::Fifo* getFIfoPtr(){ return this->fifo.get(); } + RT::OS::Fifo* getFIfoPtr() { return this->fifo.get(); } private: performance_stats_t stats; - //RunningStat timestepStat; + // RunningStat timestepStat; RunningStat latencyStat; - int64_t *start_ticks=nullptr; // only accessed in rt - int64_t *end_ticks=nullptr; // only accessed in rt - int64_t last_start_ticks=0; + int64_t* start_ticks = nullptr; // only accessed in rt + int64_t* end_ticks = nullptr; // only accessed in rt + int64_t last_start_ticks = 0; std::unique_ptr fifo; }; @@ -89,20 +108,21 @@ class Panel : public Widgets::Panel Q_OBJECT public: - Panel(const std::string& mod_name, QMainWindow* mwindow, Event::Manager* ev_manager); + Panel(const std::string& mod_name, + QMainWindow* mwindow, + Event::Manager* ev_manager); public slots: /*! * Starts the statistics over */ void reset(); - //void resetMaxTimeStep(); + // void resetMaxTimeStep(); /*! * Updates the GUI with the latest values */ void refresh() override; - private: QLineEdit* durationEdit; QLineEdit* timestepEdit; @@ -114,10 +134,12 @@ public slots: std::unique_ptr createRTXIPlugin(Event::Manager* ev_manager); -Widgets::Panel* createRTXIPanel(QMainWindow* main_window, Event::Manager* ev_manager); +Widgets::Panel* createRTXIPanel(QMainWindow* main_window, + Event::Manager* ev_manager); -std::unique_ptr createRTXIComponent(Widgets::Plugin* host_plugin); +std::unique_ptr createRTXIComponent( + Widgets::Plugin* host_plugin); Widgets::FactoryMethods getFactories(); -} // namespace PerformanceMeasurement +} // namespace PerformanceMeasurement #endif /* PERFORMANCE_MEASUREMENT_H */ diff --git a/plugins/userprefs/userprefs.cpp b/plugins/userprefs/userprefs.cpp index cd4346ee..ca1ff7b1 100644 --- a/plugins/userprefs/userprefs.cpp +++ b/plugins/userprefs/userprefs.cpp @@ -1,20 +1,20 @@ /* - The Real-Time eXperiment Interface (RTXI) - Copyright (C) 2011 Georgia Institute of Technology, University of Utah, - Will Cornell Medical College +The Real-Time eXperiment Interface (RTXI) +Copyright (C) 2011 Georgia Institute of Technology, University of Utah, +Will Cornell Medical College - This program 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 of the License, or - (at your option) any later version. +This program 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 of the License, or +(at your option) any later version. - This program 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. +This program 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 program. If not, see . +You should have received a copy of the GNU General Public License +along with this program. If not, see . */ @@ -42,18 +42,12 @@ UserPrefs::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) : Widgets::Panel(std::string(UserPrefs::MODULE_NAME), mwindow, ev_manager) , status(new QLabel) , dirGroup(new QGroupBox) - , HDF(new QGroupBox) , buttons(new QGroupBox) , settingsDirEdit(new QLineEdit(dirGroup)) , dataDirEdit(new QLineEdit(dirGroup)) - , HDFBufferEdit(new QLineEdit(HDF)) { // Preferences structure - const QSettings user_preferences; - QSettings::setPath(QSettings::NativeFormat, - QSettings::SystemScope, - "/usr/local/share/rtxi/"); - + userprefs.beginGroup("settings"); // Main layout auto* box_layout = new QVBoxLayout; @@ -61,9 +55,11 @@ UserPrefs::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) auto* dirLayout = new QGridLayout; // Create elements for directory paths - const QString env_var = QString::fromLocal8Bit(qgetenv("HOME")); - settingsDirEdit->setText( - user_preferences.value("/dirs/setfiles", env_var).toString()); + const QString workspace_dir = + QString::fromStdString(std::string(WORKSPACE_SAVE_LOCATION_KEY)); + const QString hdf5_dir = + QString::fromStdString(std::string(HDF5_SAVE_LOCATION_KEY)); + settingsDirEdit->setText(userprefs.value(workspace_dir).toString()); dirLayout->addWidget( new QLabel(tr("Default settings directory:")), 0, 0, 1, 1); dirLayout->addWidget(settingsDirEdit, 0, 1, 1, 1); @@ -74,8 +70,7 @@ UserPrefs::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) this, &UserPrefs::Panel::chooseSettingsDir); - dataDirEdit->setText( - user_preferences.value("/dirs/data", env_var).toString()); + dataDirEdit->setText(userprefs.value(hdf5_dir).toString()); dirLayout->addWidget( new QLabel(tr("Default HDF5 data directory:")), 1, 0, 1, 1); dirLayout->addWidget(dataDirEdit, 1, 1, 1, 1); @@ -89,19 +84,6 @@ UserPrefs::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) // Attach layout to group dirGroup->setLayout(dirLayout); - // Create new child widget and layout - auto* hdfLayout = new QGridLayout; - - // Create elements for child widget - hdfLayout->addWidget( - new QLabel(tr("HDF Data Recorder Buffer Size (MB):")), 0, 0, 1, 1); - hdfLayout->addWidget(HDFBufferEdit, 0, 1, 1, 1); - HDFBufferEdit->setText( - QString::number(user_preferences.value("/system/HDFbuffer", 10).toInt())); - - // Attach child to parent - HDF->setLayout(hdfLayout); - // Create new child widget auto* buttonLayout = new QHBoxLayout; @@ -132,39 +114,38 @@ UserPrefs::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager) // Attach child widget to parent widget box_layout->addWidget(dirGroup); - box_layout->addWidget(HDF); box_layout->addWidget(buttons); // Attach layout to widget setLayout(box_layout); setWindowTitle(QString(this->getName().c_str())); + userprefs.endGroup(); // Set layout to Mdi this->getMdiWindow()->setFixedSize(500, this->sizeHint().height() + 50); } void UserPrefs::Panel::reset() { + userprefs.beginGroup("settings"); const QString env_var = QString::fromLocal8Bit(qgetenv("HOME")); settingsDirEdit->setText(env_var); dataDirEdit->setText(env_var); - HDFBufferEdit->setText(QString::number(10)); - userprefs.setValue("/dirs/setfiles", settingsDirEdit->text()); - userprefs.setValue("/dirs/data", dataDirEdit->text()); - bool ok = false; - const QString buffer = HDFBufferEdit->text(); - userprefs.setValue("/system/HDFbuffer", buffer.toInt(&ok)); status->setText("Preferences \nreset"); + userprefs.endGroup(); } void UserPrefs::Panel::apply() { - userprefs.setValue("/dirs/setfiles", settingsDirEdit->text()); - userprefs.setValue("/dirs/data", dataDirEdit->text()); - bool ok = false; - const QString buffer = HDFBufferEdit->text(); - userprefs.setValue("/system/HDFbuffer", buffer.toInt(&ok)); + userprefs.beginGroup("settings"); + userprefs.setValue(QString::fromStdString( + std::string(UserPrefs::WORKSPACE_SAVE_LOCATION_KEY)), + settingsDirEdit->text()); + userprefs.setValue( + QString::fromStdString(std::string(UserPrefs::HDF5_SAVE_LOCATION_KEY)), + dataDirEdit->text()); status->setText("Preferences \napplied"); + userprefs.endGroup(); } void UserPrefs::Panel::chooseSettingsDir() @@ -173,18 +154,17 @@ void UserPrefs::Panel::chooseSettingsDir() const QString dir_name = QFileDialog::getExistingDirectory( this, tr("Choose default directory for settings files"), - userprefs.value("/dirs/setfiles", env_var).toString(), + settingsDirEdit->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); settingsDirEdit->setText(dir_name); } void UserPrefs::Panel::chooseDataDir() { - const QString env_var = QString::fromLocal8Bit(qgetenv("HOME")); const QString dir_name = QFileDialog::getExistingDirectory( this, tr("Choose default directory for HDF5 data files"), - userprefs.value("/dirs/data", env_var).toString(), + dataDirEdit->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); dataDirEdit->setText(dir_name); } diff --git a/plugins/userprefs/userprefs.hpp b/plugins/userprefs/userprefs.hpp index ccaef8c0..bfd398dc 100644 --- a/plugins/userprefs/userprefs.hpp +++ b/plugins/userprefs/userprefs.hpp @@ -29,6 +29,8 @@ namespace UserPrefs { constexpr std::string_view MODULE_NAME = "User Preferences"; +constexpr std::string_view WORKSPACE_SAVE_LOCATION_KEY = "wokrspace_dir"; +constexpr std::string_view HDF5_SAVE_LOCATION_KEY = "hdf5_dir"; class Plugin : public Widgets::Plugin { @@ -56,12 +58,10 @@ public slots: QSettings userprefs; QGroupBox* dirGroup = nullptr; - QGroupBox* HDF = nullptr; QGroupBox* buttons = nullptr; QLineEdit* settingsDirEdit = nullptr; // directory for settings files QLineEdit* dataDirEdit = nullptr; // directory of most recent data file - QLineEdit* HDFBufferEdit = nullptr; // buffer size for HDF Data Recorder }; // class Panel std::unique_ptr createRTXIPlugin(Event::Manager* ev_manager); diff --git a/src/main_window.cpp b/src/main_window.cpp index 40e30332..293db33d 100644 --- a/src/main_window.cpp +++ b/src/main_window.cpp @@ -399,6 +399,22 @@ void MainWindow::loadWindow() showMaximized(); } userprefs.endGroup(); + userprefs.beginGroup("settings"); + const auto workspace_dir_key = QString::fromStdString( + std::string(UserPrefs::WORKSPACE_SAVE_LOCATION_KEY)); + const auto data_dir_key = + QString::fromStdString(std::string(UserPrefs::HDF5_SAVE_LOCATION_KEY)); + auto workspace_dir = userprefs.value(workspace_dir_key).toString(); + auto data_dir = userprefs.value(data_dir_key).toString(); + const QString env_var = QString::fromLocal8Bit(qgetenv("HOME")); + if (workspace_dir == "") { + userprefs.setValue(workspace_dir_key, env_var); + } + if (data_dir == "") { + userprefs.setValue(data_dir_key, env_var); + } + userprefs.endGroup(); + show(); } @@ -531,7 +547,7 @@ void MainWindow::loadDAQSettings( DAQ::index_t current_channel_id = 0; for (const auto& device_id : userprefs.childGroups()) { userprefs.beginGroup(device_id); - device_name = userprefs.value("name").value(); + device_name = userprefs.value("name").toString(); // NOTE: We need to make sure that the settings we are about to load are // not reloaded for other devices. Therefore we check whether the name // exists in the loaded devices registry and whether we already used @@ -547,7 +563,7 @@ void MainWindow::loadDAQSettings( block_cache.end(), [block](const auto& entry) { return entry.second == block; }) - != block_cache.end()); + == block_cache.end()); }); if (iter == devices.end()) { ERROR_MSG("Unable to find DAQ device {} from the list of loaded devices.", @@ -648,8 +664,9 @@ void MainWindow::saveWidgetSettings(QSettings& userprefs) this->event_manager->postEvent(&loaded_plugins_query); const auto plugin_list = std::any_cast>( loaded_plugins_query.getParam("plugins")); + int widget_count = 0; for (const auto& entry : plugin_list) { - userprefs.beginGroup(QString::number(entry->getID())); + userprefs.beginGroup(QString::number(widget_count++)); userprefs.setValue("library", QString::fromStdString(entry->getLibrary())); userprefs.beginGroup("standardParams"); entry->saveParameterSettings(userprefs); @@ -671,7 +688,7 @@ void MainWindow::loadWidgetSettings( std::string event_status; for (const auto& plugin_instance_id : userprefs.childGroups()) { userprefs.beginGroup(plugin_instance_id); - plugin_name = userprefs.value("library").value(); + plugin_name = userprefs.value("library").toString(); this->loadWidget(plugin_name, plugin_ptr); // Load the settings userprefs.beginGroup("standardParams"); @@ -754,7 +771,7 @@ void MainWindow::loadSettings() auto* load_settings_dialog = new QInputDialog(this); load_settings_dialog->setInputMode(QInputDialog::TextInput); load_settings_dialog->setComboBoxEditable(false); - load_settings_dialog->setComboBoxItems(userprefs.childGroups()); + load_settings_dialog->setComboBoxItems(userprefs.childKeys()); load_settings_dialog->setLabelText("Profile"); load_settings_dialog->setOkButtonText("Load"); load_settings_dialog->exec(); @@ -766,29 +783,35 @@ void MainWindow::loadSettings() const QString profile = load_settings_dialog->textValue(); mdiArea->closeAllSubWindows(); - userprefs.beginGroup(profile); - + const auto workspace_filename = userprefs.value(profile).toString(); + QSettings workspaceprefs(workspace_filename, QSettings::IniFormat); std::unordered_map blocks; - this->loadPeriodSettings(userprefs); + this->loadPeriodSettings(workspaceprefs); - this->loadDAQSettings(userprefs, blocks); + this->loadDAQSettings(workspaceprefs, blocks); - this->loadWidgetSettings(userprefs, blocks); + this->loadWidgetSettings(workspaceprefs, blocks); - this->loadConnectionSettings(userprefs, blocks); + this->loadConnectionSettings(workspaceprefs, blocks); - userprefs.endGroup(); // profile userprefs.endGroup(); // workspaces } void MainWindow::saveSettings() { QSettings userprefs; + userprefs.beginGroup("settings"); + const auto workspace_dir_loc = + userprefs + .value(QString::fromStdString( + std::string(UserPrefs::WORKSPACE_SAVE_LOCATION_KEY))) + .toString(); + userprefs.endGroup(); userprefs.beginGroup("Workspaces"); auto* save_settings_dialog = new QInputDialog(this); save_settings_dialog->setInputMode(QInputDialog::TextInput); save_settings_dialog->setComboBoxEditable(true); - save_settings_dialog->setComboBoxItems(userprefs.childGroups()); + save_settings_dialog->setComboBoxItems(userprefs.childKeys()); save_settings_dialog->setLabelText("Profile"); save_settings_dialog->setOkButtonText("Save"); save_settings_dialog->exec(); @@ -802,16 +825,18 @@ void MainWindow::saveSettings() if (userprefs.childGroups().contains(profile_name)) { userprefs.remove(profile_name); } + const QString workspace_filename = + workspace_dir_loc + "/" + profile_name + ".ws"; + userprefs.setValue(profile_name, workspace_filename); + // userprefs.beginGroup(profile_name); + QSettings workspaceprefs(workspace_filename, QSettings::IniFormat); + workspaceprefs.clear(); + this->savePeriodSettings(workspaceprefs); - userprefs.beginGroup(profile_name); - - this->savePeriodSettings(userprefs); - - this->saveDAQSettings(userprefs); + this->saveDAQSettings(workspaceprefs); - this->saveWidgetSettings(userprefs); + this->saveWidgetSettings(workspaceprefs); - userprefs.endGroup(); // profile userprefs.endGroup(); // Workspaces } diff --git a/src/widgets.cpp b/src/widgets.cpp index d3272942..e9f9fe63 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -325,6 +325,7 @@ void Widgets::Panel::exit() event.setParam("pluginPointer", std::any(static_cast(this->hostPlugin))); this->event_manager->postEvent(&event); + this->hostPlugin = nullptr; } void Widgets::Panel::refresh() diff --git a/src/workspace.cpp b/src/workspace.cpp index 0800a213..b551d65d 100644 --- a/src/workspace.cpp +++ b/src/workspace.cpp @@ -170,47 +170,48 @@ Widgets::Plugin* Workspace::Manager::loadCorePlugin(const std::string& library) this->registerFactories(library, *fact_methods); plugin = this->rtxi_factories_registry[library].createPlugin(event_manager); plugin_ptr = this->registerWidget(std::move(plugin)); + plugin_ptr->setLibrary(library); return plugin_ptr; } // TODO: extract plugin dynamic loading to another class Widgets::Plugin* Workspace::Manager::loadPlugin(const std::string& library) { - const std::string& library_loc = library; Widgets::Plugin* plugin_ptr = nullptr; // if widget factory is already registered then all we have to do is run it - if (this->rtxi_factories_registry.find(library_loc) + if (this->rtxi_factories_registry.find(library) != this->rtxi_factories_registry.end()) { std::unique_ptr plugin = - this->rtxi_factories_registry[library_loc].createPlugin( + this->rtxi_factories_registry[library].createPlugin( this->event_manager); plugin_ptr = this->registerWidget(std::move(plugin)); plugin_ptr->attachComponent( - this->rtxi_factories_registry[library_loc].createComponent(plugin_ptr)); + this->rtxi_factories_registry[library].createComponent(plugin_ptr)); + plugin_ptr->setLibrary(library); return plugin_ptr; } // If it is just a core plugin then handle that elsewhere and return - plugin_ptr = this->loadCorePlugin(library_loc); + plugin_ptr = this->loadCorePlugin(library); if (plugin_ptr != nullptr) { return plugin_ptr; } - if (this->m_plugin_loader->load(library_loc.c_str()) != 0) { - ERROR_MSG("Plugin::load : failed to load {}", library_loc.c_str()); + if (this->m_plugin_loader->load(library.c_str()) != 0) { + ERROR_MSG("Plugin::load : failed to load {}", library.c_str()); return nullptr; } auto gen_fact_methods = this->m_plugin_loader->dlsym( - library_loc.c_str(), "getFactories"); + library.c_str(), "getFactories"); if (gen_fact_methods == nullptr) { ERROR_MSG("Plugin::load : failed to retrieve getFactories symbol"); // If we got here it means we loaded the lirbary but not the symbol. // Let's just unload the library and exit before we regret it. - this->m_plugin_loader->unload(library_loc.c_str()); + this->m_plugin_loader->unload(library.c_str()); return nullptr; } @@ -218,18 +219,18 @@ Widgets::Plugin* Workspace::Manager::loadPlugin(const std::string& library) this->rtxi_factories_registry[library] = *fact_methods; std::unique_ptr plugin = fact_methods->createPlugin(this->event_manager); - plugin->setLibrary(library); if (plugin == nullptr) { ERROR_MSG("Plugin::load : failed to create plugin from library {} ", library); - this->m_plugin_loader->unload(library_loc.c_str()); + this->m_plugin_loader->unload(library.c_str()); return nullptr; } + plugin->setLibrary(library); std::unique_ptr component; try { component = fact_methods->createComponent(plugin.get()); } catch (const std::invalid_argument& e) { - this->m_plugin_loader->unload(library_loc.c_str()); + this->m_plugin_loader->unload(library.c_str()); return nullptr; } plugin->attachComponent(std::move(component));