// 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 #include #include #include #include #include #include #include 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 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 di{0}; ///< the DIN byte as last read std::atomic do_latch{0}; ///< the DOUT byte as last written std::atomic counter{0}; std::atomic ao[2]; ///< the last DAC voltage written; NaN before any std::atomic gen_on[2]; std::atomic 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 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 queue_; std::vector signals_; std::vector plotted_bits_; ///< the DI bits that are plot channels, ascending std::size_t block_ = 10; std::shared_ptr readouts_ = std::make_shared(); std::atomic connected_{false}; std::atomic connects_{0}; mutable std::mutex msg_mu_; std::string last_message_; std::string last_logged_; std::vector rx_; ///< the read buffer std::vector pending_; ///< bytes of an incomplete scan std::vector> ready_; ///< decoded, not yet framed: [queue index][scan] std::vector> dec_; std::vector> vals_; ///< the frame being built: [signal][sample] std::vector views_; std::vector 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