From ab18c539900e6c8776ca78b3ef0ac0fa5a8298f4 Mon Sep 17 00:00:00 2001 From: awe Date: Thu, 9 Apr 2026 13:36:44 +0300 Subject: [PATCH] test new format --- .codex | 0 main.cpp | 125 ++++++++++++++++++++++++++++++++++++++- tty_protocol_writer.cpp | 128 ++++++++++++++++++++++++++++++++++++++++ tty_protocol_writer.h | 29 +++++++++ 4 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 .codex create mode 100644 tty_protocol_writer.cpp create mode 100644 tty_protocol_writer.h diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 diff --git a/main.cpp b/main.cpp index 0cf51b5..2d3ddb6 100644 --- a/main.cpp +++ b/main.cpp @@ -15,6 +15,7 @@ #endif #include "capture_file_writer.h" +#include "tty_protocol_writer.h" #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +93,7 @@ struct Config { std::string svg_path = "capture.svg"; std::string live_html_path = "live_plot.html"; std::string live_json_path = "live_plot.json"; + std::optional tty_path; }; [[noreturn]] void fail(const std::string& message) { @@ -371,7 +374,7 @@ void print_help(const char* exe_name) { << " [internal_ref_hz:2000000]\n" << " [di1:zero|trace|ignore]\n" << " [duration_ms:100] [packet_limit:0] [csv:capture.csv] [svg:capture.svg]\n" - << " [live_html:live_plot.html] [live_json:live_plot.json]\n" + << " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/dev/ttyADC_data]\n" << " [recv_block:32768] [stats_period_ms:1000] [live_update_period_ms:1000] [svg_history_packets:50] [start_wait_ms:10000]\n" << " [buffer_words:8388608] [step_words:32768]\n" << " [pullup_syn1] [pullup_syn2] [pulldown_conv_in] [pulldown_start_in]\n" @@ -402,6 +405,7 @@ void print_help(const char* exe_name) { << " buffer_words:8388608 -> input stream buffer size in 32-bit words\n" << " step_words:32768 -> input stream transfer step in 32-bit words\n" << " live_html/live_json -> live graph files updated as packets arrive\n" + << " tty:/dev/ttyADC_data -> write binary step frames to a Linux/POSIX tty path\n" << " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n" << "\n" << "Differential physical channel mapping:\n" @@ -577,6 +581,10 @@ Config parse_args(int argc, char** argv) { cfg.live_json_path = arg.substr(10); continue; } + if (starts_with(arg, "tty:")) { + cfg.tty_path = arg.substr(4); + continue; + } fail("Unknown argument: " + arg); } @@ -616,6 +624,9 @@ Config parse_args(int argc, char** argv) { if (cfg.input_buffer_words < cfg.recv_block_words) { cfg.input_buffer_words = cfg.recv_block_words; } + if (cfg.tty_path && (cfg.channel_count != 2U)) { + fail("tty output requires channels:2"); + } if (sync_uses_di_syn1(cfg.sync_mode) && sync_uses_di_syn1(cfg.sync_start_mode)) { fail("clock and start cannot both use DI_SYN1; use start_in or immediate start"); } @@ -857,6 +868,48 @@ struct PacketAccumulator { } }; +struct TtyStepAccumulator { + double sum_ch1 = 0.0; + double sum_ch2 = 0.0; + uint32_t count_ch1 = 0; + uint32_t count_ch2 = 0; + uint32_t next_step_index = 1; + + void clear_step() { + sum_ch1 = 0.0; + sum_ch2 = 0.0; + count_ch1 = 0; + count_ch2 = 0; + } + + void reset_packet() { + clear_step(); + next_step_index = 1; + } + + void add_sample(uint32_t lch, double raw_code) { + if (lch == 0U) { + sum_ch1 += raw_code; + ++count_ch1; + } else if (lch == 1U) { + sum_ch2 += raw_code; + ++count_ch2; + } + } + + bool has_complete_step() const { + return (count_ch1 != 0U) && (count_ch2 != 0U); + } +}; + +int16_t pack_raw_code_to_int16(double avg_raw_code) { + const double scaled = + avg_raw_code * 32767.0 / static_cast(X502_ADC_SCALE_CODE_MAX); + const long long rounded = std::llround(scaled); + const long long clamped = std::clamp(rounded, -32768LL, 32767LL); + return static_cast(clamped); +} + struct ConsoleCtrlGuard { bool installed = false; @@ -1039,11 +1092,16 @@ int run(const Config& cfg) { << " ADC range: +/-" << range_to_volts(cfg.range) << " V\n" << " max frames per packet per channel: " << target_frames << "\n"; + std::unique_ptr tty_writer; + if (cfg.tty_path) { + tty_writer = std::make_unique(*cfg.tty_path); + } CaptureFileWriter writer(cfg.csv_path, cfg.svg_path, cfg.live_html_path, cfg.live_json_path); writer.initialize_live_plot(); writer.initialize_csv(cfg.channel_count, cfg.di1_mode == Di1Mode::Trace); std::cout << " live plot html: " << writer.live_html_path() << "\n" << " live plot data: " << writer.live_json_path() << "\n" + << " tty step output: " << (cfg.tty_path ? *cfg.tty_path : std::string("disabled")) << "\n" << " recv block words: " << cfg.recv_block_words << "\n" << " input step words: " << cfg.input_step_words << "\n" << " input buffer words: " << cfg.input_buffer_words << "\n" @@ -1062,11 +1120,14 @@ int run(const Config& cfg) { const uint32_t read_capacity_words = std::max(cfg.recv_block_words, cfg.input_step_words); std::vector raw(read_capacity_words); std::vector adc_buffer(read_capacity_words); + std::vector adc_raw_buffer(read_capacity_words); std::vector din_buffer(read_capacity_words); std::deque pending_adc; + std::deque pending_adc_raw; std::deque pending_din; std::deque packets; PacketAccumulator current_packet; + TtyStepAccumulator tty_step; current_packet.reset(target_frames, cfg.channel_count); std::size_t csv_global_frame_index = 0; @@ -1145,9 +1206,29 @@ int run(const Config& cfg) { return; } current_packet.reset(target_frames, cfg.channel_count); + if (tty_writer) { + tty_step.reset_packet(); + tty_writer->emit_packet_start(); + } packet_active = true; }; + auto emit_tty_step = [&]() { + if (!tty_writer || !tty_step.has_complete_step()) { + return; + } + if (tty_step.next_step_index >= 0xFFFFU) { + fail("TTY protocol step index overflow within packet"); + } + + const double ch1_avg = tty_step.sum_ch1 / static_cast(tty_step.count_ch1); + const double ch2_avg = tty_step.sum_ch2 / static_cast(tty_step.count_ch2); + tty_writer->emit_step(static_cast(tty_step.next_step_index), + pack_raw_code_to_int16(ch1_avg), + pack_raw_code_to_int16(ch2_avg)); + ++tty_step.next_step_index; + }; + auto finish_packet = [&](PacketCloseReason reason) { const std::size_t frames = current_packet.frame_count(cfg.channel_count); if (frames != 0U) { @@ -1230,6 +1311,7 @@ int run(const Config& cfg) { packet_active = false; current_packet.reset(target_frames, cfg.channel_count); + tty_step.clear_step(); }; while (!stop_loop_requested) { @@ -1309,7 +1391,24 @@ int run(const Config& cfg) { } expect_ok(api, process_err, "Process ADC+DIN data"); - if ((adc_count == 0U) && (din_count == 0U)) { + uint32_t raw_adc_count = 0; + if (tty_writer) { + raw_adc_count = static_cast(adc_raw_buffer.size()); + const int32_t raw_process_err = api.ProcessData(device.hnd, + raw.data(), + static_cast(recvd), + 0, + adc_raw_buffer.data(), + &raw_adc_count, + nullptr, + nullptr); + expect_ok(api, raw_process_err, "Process raw ADC data"); + if (raw_adc_count != adc_count) { + fail("Raw ADC processing returned a different sample count than voltage processing"); + } + } + + if ((adc_count == 0U) && (din_count == 0U) && (!tty_writer || (raw_adc_count == 0U))) { continue; } @@ -1321,19 +1420,31 @@ int run(const Config& cfg) { for (uint32_t i = 0; i < adc_count; ++i) { pending_adc.push_back(adc_buffer[i]); } + if (tty_writer) { + for (uint32_t i = 0; i < raw_adc_count; ++i) { + pending_adc_raw.push_back(adc_raw_buffer[i]); + } + } for (uint32_t i = 0; i < din_count; ++i) { pending_din.push_back(din_buffer[i]); } - if ((pending_adc.size() > 1000000U) || (pending_din.size() > 1000000U)) { + if ((pending_adc.size() > 1000000U) || + (pending_din.size() > 1000000U) || + (tty_writer && (pending_adc_raw.size() > 1000000U))) { fail("Internal backlog grew too large while aligning ADC and DIN samples"); } while (!pending_adc.empty() && !pending_din.empty() && + (!tty_writer || !pending_adc_raw.empty()) && !stop_loop_requested) { const double adc_value = pending_adc.front(); pending_adc.pop_front(); + const double adc_raw_value = tty_writer ? pending_adc_raw.front() : 0.0; + if (tty_writer) { + pending_adc_raw.pop_front(); + } const uint32_t din_value = pending_din.front(); pending_din.pop_front(); @@ -1391,6 +1502,11 @@ int run(const Config& cfg) { continue; } + if (tty_writer && di1_changed) { + emit_tty_step(); + tty_step.clear_step(); + } + const uint32_t lch = next_lch; next_lch = (next_lch + 1U) % cfg.channel_count; @@ -1409,6 +1525,9 @@ int run(const Config& cfg) { if (current_packet.channels[lch].size() < target_frames) { current_packet.channels[lch].push_back(stored_value); + if (tty_writer) { + tty_step.add_sample(lch, adc_raw_value); + } ++current_packet.stored_samples; ++total_stored_adc_samples; ++stats_stored_adc_samples; diff --git a/tty_protocol_writer.cpp b/tty_protocol_writer.cpp new file mode 100644 index 0000000..f7857c1 --- /dev/null +++ b/tty_protocol_writer.cpp @@ -0,0 +1,128 @@ +#include "tty_protocol_writer.h" + +#include +#include + +#ifdef _WIN32 + +TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) { + throw std::runtime_error("tty output is supported only on Linux/POSIX"); +} + +TtyProtocolWriter::~TtyProtocolWriter() = default; + +TtyProtocolWriter::TtyProtocolWriter(TtyProtocolWriter&& other) noexcept = default; + +TtyProtocolWriter& TtyProtocolWriter::operator=(TtyProtocolWriter&& other) noexcept = default; + +void TtyProtocolWriter::emit_packet_start() const {} + +void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) const { + (void) index; + (void) ch1_avg; + (void) ch2_avg; +} + +const std::string& TtyProtocolWriter::path() const { + return path_; +} + +void TtyProtocolWriter::write_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) const { + (void) word0; + (void) word1; + (void) word2; + (void) word3; +} + +void TtyProtocolWriter::close_fd() noexcept {} + +#else + +#include +#include +#include +#include +#include +#include + +namespace { + +std::string io_error(const std::string& action, const std::string& path) { + std::ostringstream out; + out << action << " '" << path << "': " << std::strerror(errno); + return out.str(); +} + +} // namespace + +TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) { + fd_ = ::open(path_.c_str(), O_WRONLY | O_NOCTTY); + if (fd_ < 0) { + throw std::runtime_error(io_error("Cannot open tty output", path_)); + } +} + +TtyProtocolWriter::~TtyProtocolWriter() { + close_fd(); +} + +TtyProtocolWriter::TtyProtocolWriter(TtyProtocolWriter&& other) noexcept + : path_(std::move(other.path_)), + fd_(other.fd_) { + other.fd_ = -1; +} + +TtyProtocolWriter& TtyProtocolWriter::operator=(TtyProtocolWriter&& other) noexcept { + if (this != &other) { + close_fd(); + path_ = std::move(other.path_); + fd_ = other.fd_; + other.fd_ = -1; + } + return *this; +} + +void TtyProtocolWriter::emit_packet_start() const { + write_frame(0x000A, 0xFFFF, 0xFFFF, 0xFFFF); +} + +void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) const { + write_frame(0x000A, + index, + static_cast(ch1_avg), + static_cast(ch2_avg)); +} + +const std::string& TtyProtocolWriter::path() const { + return path_; +} + +void TtyProtocolWriter::write_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) const { + const uint16_t frame[4] = {word0, word1, word2, word3}; + const std::uint8_t* bytes = reinterpret_cast(frame); + std::size_t remaining = sizeof(frame); + + while (remaining != 0U) { + const ssize_t written = ::write(fd_, bytes, remaining); + if (written < 0) { + if (errno == EINTR) { + continue; + } + throw std::runtime_error(io_error("Cannot write tty frame to", path_)); + } + if (written == 0) { + throw std::runtime_error("tty write returned 0 bytes for '" + path_ + "'"); + } + bytes += static_cast(written); + remaining -= static_cast(written); + } +} + +void TtyProtocolWriter::close_fd() noexcept { + if (fd_ >= 0) { + ::close(fd_); + fd_ = -1; + } +} + +#endif diff --git a/tty_protocol_writer.h b/tty_protocol_writer.h new file mode 100644 index 0000000..295e0c5 --- /dev/null +++ b/tty_protocol_writer.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +class TtyProtocolWriter { +public: + explicit TtyProtocolWriter(std::string path); + ~TtyProtocolWriter(); + + TtyProtocolWriter(const TtyProtocolWriter&) = delete; + TtyProtocolWriter& operator=(const TtyProtocolWriter&) = delete; + TtyProtocolWriter(TtyProtocolWriter&& other) noexcept; + TtyProtocolWriter& operator=(TtyProtocolWriter&& other) noexcept; + + void emit_packet_start() const; + void emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) const; + + const std::string& path() const; + +private: + void write_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) const; + void close_fd() noexcept; + + std::string path_; +#ifndef _WIN32 + int fd_ = -1; +#endif +};