e1608: the settings, the DataSource state machine, and the connect form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
5ac1b36a1f
commit
bacc960776
6 files changed
+959
-1
No files matched your search
@@ -0,0 +1,163 @@
|
||||
// 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
|
||||
Reference in new issue
Block a user