X-Git-Url: https://sigrok.org/gitweb/?p=pulseview.git;a=blobdiff_plain;f=pv%2Fdata%2Fmathsignal.cpp;h=860439135ff2f939373a63645ea8b380f1935129;hp=d9ba5bc369440e3ae051cc4ebecf517338d37484;hb=66b6f41becf52ed12e50d1fa7f56cf0c76379d61;hpb=516b0c4163547a969da7686702c043e7e0335419 diff --git a/pv/data/mathsignal.cpp b/pv/data/mathsignal.cpp index d9ba5bc3..86043913 100644 --- a/pv/data/mathsignal.cpp +++ b/pv/data/mathsignal.cpp @@ -37,6 +37,12 @@ using std::unique_lock; namespace pv { namespace data { +#define MATH_ERR_NONE 0 +#define MATH_ERR_EMPTY_EXPR 1 +#define MATH_ERR_EXPRESSION 2 +#define MATH_ERR_INVALID_SIGNAL 3 +#define MATH_ERR_ENABLE 4 + const int64_t MathSignal::ChunkLength = 256 * 1024; @@ -84,7 +90,7 @@ MathSignal::MathSignal(pv::Session &session) : use_custom_sample_rate_(false), use_custom_sample_count_(false), expression_(""), - error_message_(""), + error_type_(MATH_ERR_NONE), exprtk_unknown_symbol_table_(nullptr), exprtk_symbol_table_(nullptr), exprtk_expression_(nullptr), @@ -99,10 +105,6 @@ MathSignal::MathSignal(pv::Session &session) : connect(&session_, SIGNAL(capture_state_changed(int)), this, SLOT(on_capture_state_changed(int))); - connect(&session_, SIGNAL(data_received()), - this, SLOT(on_data_received())); - - expression_ = "sin(2 * pi * t) + cos(t / 2 * pi)"; } MathSignal::~MathSignal() @@ -115,6 +117,8 @@ MathSignal::~MathSignal() void MathSignal::save_settings(QSettings &settings) const { + SignalBase::save_settings(settings); + settings.setValue("expression", expression_); settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_); @@ -125,6 +129,8 @@ void MathSignal::save_settings(QSettings &settings) const void MathSignal::restore_settings(QSettings &settings) { + SignalBase::restore_settings(settings); + if (settings.contains("expression")) expression_ = settings.value("expression").toString(); @@ -141,11 +147,6 @@ void MathSignal::restore_settings(QSettings &settings) use_custom_sample_count_ = settings.value("use_custom_sample_count").toBool(); } -QString MathSignal::error_message() const -{ - return error_message_; -} - QString MathSignal::get_expression() const { return expression_; @@ -158,11 +159,14 @@ void MathSignal::set_expression(QString expression) begin_generation(); } -void MathSignal::set_error_message(QString msg) +void MathSignal::set_error(uint8_t type, QString msg) { + error_type_ = type; error_message_ = msg; // TODO Emulate noquote() - qDebug().nospace() << name() << ": " << msg << "(Expression: '" << expression_ << "')"; + qDebug().nospace() << name() << ": " << msg << "(Expression: '" + expression_ + "')"; + + error_message_changed(msg); } uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const @@ -182,10 +186,18 @@ uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const const shared_ptr& sb = input_signal.second.sb; shared_ptr a = sb->analog_data(); - const uint32_t last_segment = (a->analog_segments().size() - 1); - if (segment_id > last_segment) + auto analog_segments = a->analog_segments(); + + if (analog_segments.size() == 0) { + result = 0; + continue; + } + + const uint32_t highest_segment_id = (analog_segments.size() - 1); + if (segment_id > highest_segment_id) continue; - const shared_ptr segment = a->analog_segments()[segment_id]; + + const shared_ptr segment = analog_segments.at(segment_id); result = min(result, (int64_t)segment->get_sample_count()); } } else @@ -195,6 +207,48 @@ uint64_t MathSignal::get_working_sample_count(uint32_t segment_id) const return result; } +void MathSignal::update_completeness(uint32_t segment_id, uint64_t output_sample_count) +{ + bool output_complete = true; + + if (input_signals_.size() > 0) { + for (auto input_signal : input_signals_) { + const shared_ptr& sb = input_signal.second.sb; + + shared_ptr a = sb->analog_data(); + auto analog_segments = a->analog_segments(); + + if (analog_segments.size() == 0) { + output_complete = false; + continue; + } + + const uint32_t highest_segment_id = (analog_segments.size() - 1); + if (segment_id > highest_segment_id) { + output_complete = false; + continue; + } + + const shared_ptr segment = analog_segments.at(segment_id); + if (!segment->is_complete()) { + output_complete = false; + continue; + } + + if (output_sample_count < segment->get_sample_count()) + output_complete = false; + } + } else { + // We're done when we generated as many samples as the stopped session is long + if ((session_.get_capture_state() != Session::Stopped) || + (output_sample_count < session_.get_segment_sample_count(segment_id))) + output_complete = false; + } + + if (output_complete) + analog_data()->analog_segments().at(segment_id)->set_complete(); +} + void MathSignal::reset_generation() { if (gen_thread_.joinable()) { @@ -232,7 +286,8 @@ void MathSignal::reset_generation() } if (!error_message_.isEmpty()) { - error_message_ = QString(); + error_message_.clear(); + error_type_ = MATH_ERR_NONE; // TODO Emulate noquote() qDebug().nospace() << name() << ": Error cleared"; } @@ -243,10 +298,13 @@ void MathSignal::begin_generation() reset_generation(); if (expression_.isEmpty()) { - set_error_message(tr("No expression defined, nothing to do")); + set_error(MATH_ERR_EMPTY_EXPR, tr("No expression defined, nothing to do")); return; } + disconnect(this, SLOT(on_data_received())); + disconnect(this, SLOT(on_enabled_changed())); + fnc_sig_sample_ = new sig_sample(*this); exprtk_unknown_symbol_table_ = new exprtk::symbol_table(); @@ -265,7 +323,25 @@ void MathSignal::begin_generation() exprtk_parser_->enable_unknown_symbol_resolver(); if (!exprtk_parser_->compile(expression_.toStdString(), *exprtk_expression_)) { - set_error_message(tr("Error in expression")); + QString error_details; + size_t error_count = exprtk_parser_->error_count(); + + for (size_t i = 0; i < error_count; i++) { + typedef exprtk::parser_error::type error_t; + error_t error = exprtk_parser_->get_error(i); + exprtk::parser_error::update_error(error, expression_.toStdString()); + + QString error_detail = tr("%1 at line %2, column %3: %4"); + if ((error_count > 1) && (i < (error_count - 1))) + error_detail += "\n"; + + error_details += error_detail \ + .arg(exprtk::parser_error::to_str(error.mode).c_str()) \ + .arg(error.line_no) \ + .arg(error.column_no) \ + .arg(error.diagnostic.c_str()); + } + set_error(MATH_ERR_EXPRESSION, error_details); } else { // Resolve unknown scalars to signals and add them to the input signal list vector unknowns; @@ -274,14 +350,24 @@ void MathSignal::begin_generation() signal_data* sig_data = signal_from_name(unknown); const shared_ptr signal = (sig_data) ? (sig_data->sb) : nullptr; if (!signal || (!signal->analog_data())) { - set_error_message(QString(tr("%1 isn't a valid signal")).arg( - QString::fromStdString(unknown))); + set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("%1 isn't a valid analog signal")) \ + .arg(QString::fromStdString(unknown))); } else sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown)); } } + QString disabled_signals; + if (!all_input_signals_enabled(disabled_signals) && error_message_.isEmpty()) + set_error(MATH_ERR_ENABLE, + tr("No data will be generated as %1 must be enabled").arg(disabled_signals)); + if (error_message_.isEmpty()) { + // Connect to the session data notification if we have no input signals + if (input_signals_.empty()) + connect(&session_, SIGNAL(data_received()), + this, SLOT(on_data_received())); + gen_interrupt_ = false; gen_thread_ = std::thread(&MathSignal::generation_proc, this); } @@ -322,8 +408,6 @@ void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_samp void MathSignal::generation_proc() { - uint32_t segment_id = 0; - // Don't do anything until we have a valid sample rate do { if (use_custom_sample_rate_) @@ -340,6 +424,7 @@ void MathSignal::generation_proc() if (gen_interrupt_) return; + uint32_t segment_id = 0; shared_ptr analog = analog_data(); // Create initial analog segment @@ -370,28 +455,26 @@ void MathSignal::generation_proc() processed_samples += sample_count; // Notify consumers of this signal's data - // TODO Does this work when a conversion is active? samples_added(segment_id, start_sample, start_sample + processed_samples); } while (!gen_interrupt_ && (processed_samples < samples_to_process)); } - if (samples_to_process == 0) { - if (segment_id < session_.get_highest_segment_id()) { - analog->analog_segments().back()->set_complete(); + update_completeness(segment_id, output_sample_count); + if (output_segment->is_complete() && (segment_id < session_.get_highest_segment_id())) { // Process next segment segment_id++; output_segment = make_shared(*analog.get(), segment_id, analog->get_samplerate()); analog->push_segment(output_segment); - } else { - // All segments have been processed, wait for more input - unique_lock gen_input_lock(input_mutex_); - gen_input_cond_.wait(gen_input_lock); - } } + if (!gen_interrupt_ && (samples_to_process == 0)) { + // Wait for more input + unique_lock gen_input_lock(input_mutex_); + gen_input_cond_.wait(gen_input_lock); + } } while (!gen_interrupt_); } @@ -408,8 +491,20 @@ signal_data* MathSignal::signal_from_name(const std::string& name) const QString sig_name = QString::fromStdString(name); for (const shared_ptr& sb : signalbases) - if (sb->name() == sig_name) + if (sb->name() == sig_name) { + if (!sb->analog_data()) + continue; + + connect(sb->analog_data().get(), SIGNAL(samples_added(SharedPtrToSegment, uint64_t, uint64_t)), + this, SLOT(on_data_received())); + connect(sb->analog_data().get(), SIGNAL(segment_completed()), + this, SLOT(on_data_received())); + + connect(sb.get(), SIGNAL(enabled_changed(bool)), + this, SLOT(on_enabled_changed())); + return &(input_signals_.insert({name, signal_data(sb)}).first->second); + } } return nullptr; @@ -434,21 +529,39 @@ void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id sig_data->sample_num = sample_num; sig_data->sample_value = segment->get_sample(sample_num); - // We only have a reference if this signal is used as a scalar, + // We only have a reference if this signal is used as a scalar; // if it's used by a function, it's null if (sig_data->ref) *(sig_data->ref) = sig_data->sample_value; } +bool MathSignal::all_input_signals_enabled(QString &disabled_signals) const +{ + bool all_enabled = true; + + disabled_signals.clear(); + + for (auto input_signal : input_signals_) { + const shared_ptr& sb = input_signal.second.sb; + + if (!sb->enabled()) { + all_enabled = false; + disabled_signals += disabled_signals.isEmpty() ? + sb->name() : ", " + sb->name(); + } + } + + return all_enabled; +} + void MathSignal::on_capture_state_changed(int state) { if (state == Session::Running) begin_generation(); - if (state == Session::Stopped) { - shared_ptr analog = analog_data(); - analog->analog_segments().back()->set_complete(); - } + // Make sure we don't miss any input samples, just in case + if (state == Session::Stopped) + gen_input_cond_.notify_one(); } void MathSignal::on_data_received() @@ -456,5 +569,18 @@ void MathSignal::on_data_received() gen_input_cond_.notify_one(); } +void MathSignal::on_enabled_changed() +{ + QString disabled_signals; + if (!all_input_signals_enabled(disabled_signals) && + ((error_type_ == MATH_ERR_NONE) || (error_type_ == MATH_ERR_ENABLE))) + set_error(MATH_ERR_ENABLE, + tr("No data will be generated as %1 must be enabled").arg(disabled_signals)); + else if (disabled_signals.isEmpty() && (error_type_ == MATH_ERR_ENABLE)) { + error_type_ = MATH_ERR_NONE; + error_message_.clear(); + } +} + } // namespace data } // namespace pv