X-Git-Url: https://sigrok.org/gitweb/?p=pulseview.git;a=blobdiff_plain;f=pv%2Fdata%2Fmathsignal.cpp;h=adb27131e6d43bc00a89e788cf398c07ed57229a;hp=2ad90212fe01eb5c8cd5cef764aaed6f1b3150b5;hb=HEAD;hpb=144e72c9ec677e7df35d37d7de6e8a18bb3f2ba1 diff --git a/pv/data/mathsignal.cpp b/pv/data/mathsignal.cpp index 2ad90212..adb27131 100644 --- a/pv/data/mathsignal.cpp +++ b/pv/data/mathsignal.cpp @@ -37,18 +37,24 @@ 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; template -struct sig_sample : public exprtk::igeneric_function +struct fnc_sample : public exprtk::igeneric_function { typedef typename exprtk::igeneric_function::parameter_list_t parameter_list_t; typedef typename exprtk::igeneric_function::generic_type generic_type; typedef typename generic_type::scalar_view scalar_t; typedef typename generic_type::string_view string_t; - sig_sample(MathSignal& owner) : + fnc_sample(MathSignal& owner) : exprtk::igeneric_function("ST"), // Require channel name and sample number owner_(owner), sig_data(nullptr) @@ -63,10 +69,16 @@ struct sig_sample : public exprtk::igeneric_function const std::string str_sig_name = to_str(exprtk_sig_name); const double sample_num = exprtk_sample_num(); + if (sample_num < 0) + return 0; + if (!sig_data) sig_data = owner_.signal_from_name(str_sig_name); - assert(sig_data); + if (!sig_data) + // There doesn't actually exist a signal with that name + return 0; + owner_.update_signal_sample(sig_data, current_segment, sample_num); return T(sig_data->sample_value); @@ -84,12 +96,12 @@ 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), exprtk_parser_(nullptr), - fnc_sig_sample_(nullptr) + fnc_sample_(nullptr) { uint32_t sig_idx = session_.get_next_signal_index(MathChannel); set_name(QString(tr("Math%1")).arg(sig_idx)); @@ -104,13 +116,12 @@ MathSignal::MathSignal(pv::Session &session) : MathSignal::~MathSignal() { reset_generation(); - - if (fnc_sig_sample_) - delete fnc_sig_sample_; } void MathSignal::save_settings(QSettings &settings) const { + SignalBase::save_settings(settings); + settings.setValue("expression", expression_); settings.setValue("custom_sample_rate", (qulonglong)custom_sample_rate_); @@ -121,6 +132,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(); @@ -137,11 +150,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_; @@ -154,11 +162,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 @@ -272,16 +283,19 @@ void MathSignal::reset_generation() exprtk_unknown_symbol_table_ = nullptr; } - if (fnc_sig_sample_) { - delete fnc_sig_sample_; - fnc_sig_sample_ = nullptr; + if (fnc_sample_) { + delete fnc_sample_; + fnc_sample_ = nullptr; } if (!error_message_.isEmpty()) { - error_message_ = QString(); + error_message_.clear(); + error_type_ = MATH_ERR_NONE; // TODO Emulate noquote() qDebug().nospace() << name() << ": Error cleared"; } + + generation_chunk_size_ = ChunkLength; } void MathSignal::begin_generation() @@ -289,18 +303,25 @@ 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(&session_, SIGNAL(data_received()), this, SLOT(on_data_received())); - fnc_sig_sample_ = new sig_sample(*this); + for (const shared_ptr& sb : session_.signalbases()) { + if (sb->analog_data()) + disconnect(sb->analog_data().get(), nullptr, this, SLOT(on_data_received())); + disconnect(sb.get(), nullptr, this, SLOT(on_enabled_changed())); + } + + fnc_sample_ = new fnc_sample(*this); exprtk_unknown_symbol_table_ = new exprtk::symbol_table(); exprtk_symbol_table_ = new exprtk::symbol_table(); - exprtk_symbol_table_->add_function("sig_sample", *fnc_sig_sample_); + exprtk_symbol_table_->add_constant("T", 1 / session_.get_samplerate()); + exprtk_symbol_table_->add_function("sample", *fnc_sample_); exprtk_symbol_table_->add_variable("t", exprtk_current_time_); exprtk_symbol_table_->add_variable("s", exprtk_current_sample_); exprtk_symbol_table_->add_constants(); @@ -313,7 +334,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; @@ -322,32 +361,38 @@ 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 analog 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())); + connect(&session_, SIGNAL(data_received()), this, SLOT(on_data_received())); gen_interrupt_ = false; gen_thread_ = std::thread(&MathSignal::generation_proc, this); } } -void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample, +uint64_t MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample, const int64_t sample_count) { + uint64_t count = 0; + shared_ptr analog = dynamic_pointer_cast(data_); shared_ptr segment = analog->analog_segments().at(segment_id); // Keep the math functions segment IDs in sync - fnc_sig_sample_->current_segment = segment_id; + fnc_sample_->current_segment = segment_id; const double sample_rate = data_->get_samplerate(); @@ -366,11 +411,20 @@ void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_samp double value = exprtk_expression_->value(); sample_data[i] = value; exprtk_current_sample_ += 1; + count++; + + // If during the evaluation of the expression it was found that this + // math signal itself is being accessed, the chunk size was reduced + // to 1, which means we must stop after this sample we just generated + if (generation_chunk_size_ == 1) + break; } - segment->append_interleaved_samples(sample_data, sample_count, 1); + segment->append_interleaved_samples(sample_data, count, 1); delete[] sample_data; + + return count; } void MathSignal::generation_proc() @@ -410,15 +464,13 @@ void MathSignal::generation_proc() // Process the samples if necessary... if (samples_to_process > 0) { - const uint64_t chunk_sample_count = ChunkLength; - uint64_t processed_samples = 0; do { const uint64_t start_sample = output_sample_count + processed_samples; - const uint64_t sample_count = - min(samples_to_process - processed_samples, chunk_sample_count); + uint64_t sample_count = + min(samples_to_process - processed_samples, generation_chunk_size_); - generate_samples(segment_id, start_sample, sample_count); + sample_count = generate_samples(segment_id, start_sample, sample_count); processed_samples += sample_count; // Notify consumers of this signal's data @@ -447,6 +499,12 @@ void MathSignal::generation_proc() signal_data* MathSignal::signal_from_name(const std::string& name) { + // If the expression contains the math signal itself, we must add every sample to + // the output segment immediately so that it can be accessed + const QString sig_name = QString::fromStdString(name); + if (sig_name == this->name()) + generation_chunk_size_ = 1; + // Look up signal in the map and if it doesn't exist yet, add it for future use auto element = input_signals_.find(name); @@ -455,7 +513,6 @@ signal_data* MathSignal::signal_from_name(const std::string& name) return &(element->second); } else { const vector< shared_ptr > signalbases = session_.signalbases(); - const QString sig_name = QString::fromStdString(name); for (const shared_ptr& sb : signalbases) if (sb->name() == sig_name) { @@ -467,10 +524,18 @@ signal_data* MathSignal::signal_from_name(const std::string& name) 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); } } + // If we reach this point, no valid signal was found with the supplied name + if (error_type_ == MATH_ERR_NONE) + set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("\"%1\" isn't a valid analog signal")) \ + .arg(QString::fromStdString(name))); + return nullptr; } @@ -491,7 +556,11 @@ void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id const shared_ptr segment = analog->analog_segments().at(segment_id); sig_data->sample_num = sample_num; - sig_data->sample_value = segment->get_sample(sample_num); + + if (sample_num < segment->get_sample_count()) + sig_data->sample_value = segment->get_sample(sample_num); + else + sig_data->sample_value = 0; // We only have a reference if this signal is used as a scalar; // if it's used by a function, it's null @@ -499,6 +568,25 @@ void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id *(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) @@ -514,5 +602,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