Files
udpscope/src/source/e1608/e1608_source.hpp
T

164 lines
5.4 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#pragma once
#include "source/data_source.hpp"
#include "source/e1608/e1608_device.hpp"
#include "source/e1608/e1608_link.hpp"
#include "source/source_config.hpp"
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
namespace udpscope::e1608 {
constexpr double kMaxAggregateHz = 250000.0;
constexpr double kMinAggregateHz = 0.02;
/// Everything a configured E-1608 source needs, parsed once from the flat `SourceConfig`.
struct E1608Settings {
std::string address; ///< host or IP; empty means "find it by MAC"
std::string mac_text; ///< as typed; empty means "any board at `address`"
std::string subnet; ///< `a.b.c` to sweep when looking up a MAC; empty means broadcast
int port = kDefaultPort;
std::uint32_t code = 0;
int timeout_ms = 2000;
double scan_rate = 1000.0; ///< per channel; with an external clock, that clock's rate
bool ext_clock = false;
Trigger trigger = Trigger::None;
bool auto_restart = true;
bool calibrate = true;
double poll_rate = 10.0; ///< Hz, for the digital inputs and the counter
bool diff = false;
bool ai_on[8] = {};
Range ai_range[8] = {};
bool dir_out[8] = {};
bool plot[8] = {};
bool plot_counter = false;
int do_init = 0;
double ao_init[2] = {0.0, 0.0};
static E1608Settings from(const SourceConfig& cfg);
/// The first reason this cannot connect, in words for the user; empty when it can.
std::string problem() const;
int active_channels() const;
std::vector<QueueEntry> queue() const;
double aggregate() const { return scan_rate * active_channels(); }
/// The address, else the MAC; names the session file.
std::string identity() const;
};
/// What the panels read. Written by the producer thread, read by the GTK thread's timer.
struct E1608Readouts {
std::atomic<unsigned> di{0}; ///< the DIN byte as last read
std::atomic<unsigned> do_latch{0}; ///< the DOUT byte as last written
std::atomic<std::uint32_t> counter{0};
std::atomic<double> ao[2]; ///< the last DAC voltage written; NaN before any
std::atomic<bool> gen_on[2];
std::atomic<unsigned> epoch{0}; ///< bumped at each (re)connect: panels re-sync from it
E1608Readouts() {
for (int i = 0; i < 2; ++i) {
ao[i].store(std::nan(""));
gen_on[i].store(false);
}
}
};
enum class AoMode { Level, Sine, Square, Triangle, Ramp };
struct AoSettings {
AoMode mode = AoMode::Level;
double level = 0.0; ///< Level mode, volts
double amplitude = 1.0; ///< waveform modes, volts
double offset = 0.0;
double freq_hz = 1.0; ///< clamped to 0.1 .. 10
bool run = false; ///< waveform modes only
};
class E1608Source final : public DataSource {
public:
explicit E1608Source(const SourceConfig& cfg);
void open(SourceSink& sink) override;
void poll(int timeout_ms) override;
void close() override;
SourceStats stats() const override;
bool connected() const override { return connected_.load(std::memory_order_relaxed); }
// Producer thread only: reach these through `post()`. Each is ignored unless scanning.
void set_output_bit(int bit, bool on);
void reset_counter();
void set_analog_out(int ch, const AoSettings& s);
std::shared_ptr<E1608Readouts> readouts() const { return readouts_; }
const E1608Settings& settings() const { return cfg_; }
unsigned connects() const { return connects_.load(std::memory_order_relaxed); }
std::string last_message() const;
private:
using Clock = std::chrono::steady_clock;
enum class State { Down, Scanning, Halted };
void connect_and_start();
void scan_step(int timeout_ms);
void emit_frame(std::size_t count);
void run_timers();
void slow_read();
void check_status();
void restart_scan();
void start_scan();
void generator_tick(Clock::time_point now);
void write_ao(int ch, double volts);
void update_gen_flags();
void fail(const std::string& msg, bool fatal);
void note(const std::string& msg);
int wait_ms(int timeout_ms, Clock::time_point now) const;
E1608Settings cfg_;
SourceSink* sink_ = nullptr;
State state_ = State::Down;
Link link_;
Device dev_{link_};
Calibration cal_;
std::vector<QueueEntry> queue_;
std::vector<SignalInfo> signals_;
std::vector<int> plotted_bits_; ///< the DI bits that are plot channels, ascending
std::size_t block_ = 10;
std::shared_ptr<E1608Readouts> readouts_ = std::make_shared<E1608Readouts>();
std::atomic<bool> connected_{false};
std::atomic<unsigned> connects_{0};
mutable std::mutex msg_mu_;
std::string last_message_;
std::string last_logged_;
std::vector<std::uint8_t> rx_; ///< the read buffer
std::vector<std::uint8_t> pending_; ///< bytes of an incomplete scan
std::vector<std::vector<double>> ready_; ///< decoded, not yet framed: [queue index][scan]
std::vector<std::vector<double>> dec_;
std::vector<std::vector<double>> vals_; ///< the frame being built: [signal][sample]
std::vector<SignalValues> views_;
std::vector<double> held_di_;
double held_counter_ = 0.0;
std::uint8_t do_latch_ = 0;
AoSettings ao_[2];
Clock::time_point gen_t0_[2];
bool any_gen_ = false;
Clock::time_point retry_at_;
Clock::time_point next_slow_, next_status_, next_gen_;
std::uint32_t frame_counter_ = 0;
std::uint32_t pending_lost_ = 0;
std::uint64_t packets_ = 0, frames_ = 0, reconnects_ = 0;
};
} // namespace udpscope::e1608