Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
164 lines
5.4 KiB
C++
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
|