Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
3.8 KiB
C++
104 lines
3.8 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
|
|
|
#include "trigger.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace udpscope {
|
|
|
|
double TriggerConfig::post_span_s(double buffer_span_s) const {
|
|
const double pre = std::clamp(pretrigger_pct, 0.0, 100.0) / 100.0;
|
|
return buffer_span_s * (1.0 - pre);
|
|
}
|
|
|
|
void TriggerEngine::configure(const TriggerConfig& cfg, const std::string& channel_name,
|
|
double post_seconds, double pre_seconds, double oldest_s) {
|
|
const bool channel_changed = channel_name != channel_name_;
|
|
if (channel_changed) channel_name_ = channel_name;
|
|
cfg_ = cfg;
|
|
post_seconds_ = post_seconds > 0.0 ? post_seconds : 0.0;
|
|
pre_seconds_ = pre_seconds > 0.0 ? pre_seconds : 0.0;
|
|
oldest_s_ = oldest_s;
|
|
// A no-op unless the condition actually changed: this runs on every packet.
|
|
detector_.configure(cfg_.condition);
|
|
if (channel_changed) {
|
|
detector_.reset();
|
|
pending_ = false;
|
|
post_accum_count_ = 0;
|
|
}
|
|
}
|
|
|
|
void TriggerEngine::arm(double now_s) noexcept {
|
|
armed_ = true;
|
|
pending_ = false;
|
|
post_accum_count_ = 0.0;
|
|
holdoff_until_s_ = now_s;
|
|
detector_.reset();
|
|
}
|
|
|
|
bool TriggerEngine::process(double value, double now_s) noexcept {
|
|
if (!cfg_.enabled || !armed_ || cfg_.channel < 0) return false;
|
|
|
|
// The detector is fed **first and unconditionally**, on every sample the engine sees --
|
|
// including during the hold-off and during an open capture, where its answer is thrown
|
|
// away. It is a sequential machine: a pulse whose opening edge fell in a skipped stretch
|
|
// can never be closed, and the symptom would be a width condition that silently stops
|
|
// firing after the first hold-off.
|
|
const double event_t = detector_.feed(value, now_s);
|
|
|
|
// Hold-off: settled, waiting for the next event.
|
|
if (!pending_ && now_s < holdoff_until_s_) {
|
|
last_t_s_ = now_s;
|
|
return false;
|
|
}
|
|
|
|
last_t_s_ = now_s;
|
|
|
|
if (pending_) {
|
|
// Accumulate the post-trigger window; latch once the stream clock has advanced
|
|
// past it. Time, not a sample count: `now_s` is the same clock the x axis reads,
|
|
// so the capture covers exactly the requested span whatever the cadence is.
|
|
++post_accum_count_;
|
|
if (now_s - pending_start_s_ >= post_seconds_) {
|
|
pending_ = false;
|
|
post_accum_count_ = 0;
|
|
if (cfg_.mode == TriggerMode::Single) {
|
|
armed_ = false;
|
|
} else {
|
|
holdoff_until_s_ = now_s + std::max(0.0, cfg_.holdoff_s);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!std::isfinite(event_t)) return false;
|
|
|
|
// The pre-trigger window has to already be behind the event, or the capture opens on a
|
|
// half-filled pre window and the event is short on its left side -- the plot then shows a
|
|
// window that is correctly sized but only partly drawn. The buffer fills whether or not the
|
|
// trigger is armed, so this normally passes on the first event; it only bites just after a
|
|
// rebuild (a CONFIG, or enabling the signal), when the history genuinely is not there yet.
|
|
//
|
|
// Measured from the *event's own* time, not from `now_s`: for a qualified condition the two
|
|
// differ (a pulse is reported at its closing edge), and the pre window belongs to the event.
|
|
if (event_t - oldest_s_ < pre_seconds_) return false;
|
|
|
|
// The event's own time, which is what a trigger marker marks: the latch itself may happen
|
|
// a post-window later.
|
|
last_trigger_s_ = event_t;
|
|
|
|
// The event always opens a capture; there is no "latch on the event itself" case.
|
|
// A zero-length post window latches on the very next sample, which is the least a
|
|
// capture can hold, and every longer window is just more samples -- no conversion
|
|
// from seconds to a sample count, so no conversion to get wrong.
|
|
pending_ = true;
|
|
pending_start_s_ = event_t;
|
|
post_accum_count_ = 0;
|
|
return false;
|
|
}
|
|
|
|
} // namespace udpscope
|