98 lines
3.8 KiB
C++
98 lines
3.8 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
|
|
|
#pragma once
|
|
|
|
#include "config.hpp"
|
|
#include "model.hpp"
|
|
#include "trigger.hpp"
|
|
|
|
#include <udps_client.h>
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace udpscope {
|
|
|
|
/// Owns the UDPStreamer client and the producer thread that feeds the model's
|
|
/// acquisition buffer. Everything it touches from the thread is serialised on
|
|
/// `ScopeModel::mutex()`, so a UI-side structural change cannot race a frame.
|
|
class Acquisition {
|
|
public:
|
|
Acquisition(ScopeModel& model, const Config& cfg);
|
|
~Acquisition();
|
|
|
|
Acquisition(const Acquisition&) = delete;
|
|
Acquisition& operator=(const Acquisition&) = delete;
|
|
|
|
/// Opens the client (no socket until the first poll) and starts the thread.
|
|
/// Throws std::runtime_error when the client cannot be created.
|
|
void start();
|
|
/// Stops the thread and destroys the client. Idempotent.
|
|
void stop();
|
|
|
|
void set_paused(bool paused) noexcept { paused_.store(paused, std::memory_order_relaxed); }
|
|
bool paused() const noexcept { return paused_.load(std::memory_order_relaxed); }
|
|
|
|
private:
|
|
void run();
|
|
void on_config(const udps_signal_t* signals, std::uint32_t count, std::uint8_t publish_mode);
|
|
void on_data(const udps_frame_t* frame);
|
|
void on_event(int event, const char* detail);
|
|
|
|
static void config_trampoline(const udps_signal_t* signals, std::uint32_t count,
|
|
std::uint8_t publish_mode, void* user);
|
|
static void data_trampoline(const udps_frame_t* frame, void* user);
|
|
static void event_trampoline(udps_event_t event, const char* detail, void* user);
|
|
|
|
/// Fills `stamps_` with this frame's base-tick timestamps, in stream seconds,
|
|
/// and returns how many were written (`frame->num_samples` unless the frame is
|
|
/// malformed).
|
|
std::uint32_t build_stamps(const udps_frame_t* frame, int time_signal);
|
|
|
|
ScopeModel& model_;
|
|
Config cfg_;
|
|
udps_client* client_ = nullptr;
|
|
std::thread thread_;
|
|
std::atomic<bool> running_{false};
|
|
std::atomic<bool> paused_{false};
|
|
TriggerEngine engine_;
|
|
|
|
std::uint64_t tick_ = 0; ///< base ticks consumed since the epoch
|
|
std::uint64_t last_tick_ = ~std::uint64_t{0};
|
|
double last_stamp_ = 0.0; ///< last relative time written (monotonic guard)
|
|
double last_recv_ = 0.0;
|
|
bool have_recv_ = false;
|
|
/// Last accepted stream-time stamp: a "time signal" whose values do not advance
|
|
/// across packets (a producer that restarts them per packet, or a mislabelled
|
|
/// signal) would freeze the whole time base -- the trigger's hold-off clock
|
|
/// included -- so the stream source is dropped in favour of arrival time.
|
|
double last_stream_anchor_ = 0.0;
|
|
bool have_stream_anchor_ = false;
|
|
/// Arrival-clock origin: the first packet's receive time, so arrival stamps are
|
|
/// small seconds (the time ring stores f32, which cannot hold Unix epochs).
|
|
double arrival_epoch_ = 0.0;
|
|
bool have_arrival_epoch_ = false;
|
|
std::vector<double> stamps_;
|
|
/// The decimation filter's state: a running sum per channel (and one for the shared
|
|
/// clock) over the group of base samples that becomes one stored sample. A boxcar
|
|
/// average, which is what turns decimation into a *resampling* rather than a stride:
|
|
/// its first null sits at the stored rate, so what would fold back is attenuated
|
|
/// instead of reappearing as a false low frequency. Cleared whenever the partition
|
|
/// changes, since the group length is `time_stride`.
|
|
std::vector<double> value_acc_;
|
|
std::vector<std::uint64_t> value_acc_n_;
|
|
double time_acc_ = 0.0;
|
|
std::uint64_t time_acc_n_ = 0;
|
|
/// The partition the accumulators were sized for, so a re-plan resets them rather than
|
|
/// averaging across the boundary.
|
|
std::uint64_t acc_stride_ = 0;
|
|
std::size_t acc_channels_ = 0;
|
|
std::chrono::steady_clock::time_point started_{};
|
|
};
|
|
|
|
} // namespace udpscope
|