x buffer overflow

This commit is contained in:
awe
2026-04-10 15:45:16 +03:00
parent de62e39d24
commit 900fdc1262
2 changed files with 223 additions and 148 deletions

367
main.cpp
View File

@ -406,15 +406,18 @@ void print_help(const char* exe_name) {
<< " di1:ignore -> ignore DI1 for both zeroing and plotting\n" << " di1:ignore -> ignore DI1 for both zeroing and plotting\n"
<< " stats_period_ms:1000 -> print online transfer/capture statistics every 1000 ms (0 disables)\n" << " stats_period_ms:1000 -> print online transfer/capture statistics every 1000 ms (0 disables)\n"
<< " recv_block:32768 -> request up to 32768 raw 32-bit words per X502_Recv() call\n" << " recv_block:32768 -> request up to 32768 raw 32-bit words per X502_Recv() call\n"
<< " live_update_period_ms:1000 -> refresh live HTML/JSON no more than once per second\n" << " live_update_period_ms:1000 -> refresh live HTML/JSON no more than once per second"
<< " svg_history_packets:50 -> keep last 50 packets in RAM for final SVG (0 keeps all, risky)\n" << " (ignored in tty+di1_group_avg fast mode)\n"
<< " svg_history_packets:50 -> keep last 50 packets in RAM for final SVG (0 keeps all, risky)"
<< " (ignored in tty+di1_group_avg fast mode)\n"
<< " duration_ms:100 -> max packet length if stop edge does not arrive\n" << " duration_ms:100 -> max packet length if stop edge does not arrive\n"
<< " packet_limit:0 -> 0 means continuous until Ctrl+C, N means stop after N packets\n" << " packet_limit:0 -> 0 means continuous until Ctrl+C, N means stop after N packets\n"
<< " buffer_words:8388608 -> input stream buffer size in 32-bit words\n" << " buffer_words:8388608 -> input stream buffer size in 32-bit words\n"
<< " step_words:32768 -> input stream transfer step 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" << " live_html/live_json -> live graph files updated as packets arrive outside tty+di1_group_avg fast mode\n"
<< " tty:/tmp/ttyADC_data -> write a continuous legacy 4-word CH1/CH2 stream to a Linux/POSIX tty or PTY link path\n" << " tty:/tmp/ttyADC_data -> write a continuous legacy 4-word CH1/CH2 stream to a Linux/POSIX tty or PTY link path\n"
<< " di1_group_avg -> with tty + di1:trace, emit one averaged CH1/CH2 step per constant DI1 run\n" << " di1_group_avg -> with tty + di1:trace, emit one averaged CH1/CH2 step per constant DI1 run\n"
<< " tty+di1_group_avg -> fast stream-only mode: skip CSV/SVG/live outputs and send only averaged tty data\n"
<< " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n" << " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n"
<< "\n" << "\n"
<< "Differential physical channel mapping:\n" << "Differential physical channel mapping:\n"
@ -432,7 +435,7 @@ void print_help(const char* exe_name) {
<< "This build enables synchronous DIN together with ADC. DI_SYN2 stop edges are detected\n" << "This build enables synchronous DIN together with ADC. DI_SYN2 stop edges are detected\n"
<< "inside the same input stream, packets are split continuously by DI_SYN2 edges, and DI1\n" << "inside the same input stream, packets are split continuously by DI_SYN2 edges, and DI1\n"
<< "can either zero ADC samples on change, be exported as a separate synchronous trace, or be ignored.\n" << "can either zero ADC samples on change, be exported as a separate synchronous trace, or be ignored.\n"
<< "Open live_plot.html in a browser to see the live graph update over time.\n" << "Outside tty+di1_group_avg fast mode, open live_plot.html in a browser to see the live graph update over time.\n"
<< "The live HTML supports X/Y zoom buttons, mouse-wheel zoom, and reset.\n" << "The live HTML supports X/Y zoom buttons, mouse-wheel zoom, and reset.\n"
<< "For tty output, use di1_group_avg together with di1:trace to emit one averaged 4-word step\n" << "For tty output, use di1_group_avg together with di1:trace to emit one averaged 4-word step\n"
<< "per constant DI1 run while keeping the current ring-buffered binary frame format.\n" << "per constant DI1 run while keeping the current ring-buffered binary frame format.\n"
@ -1319,6 +1322,7 @@ int run(const Config& cfg) {
std::size_t tty_ring_capacity_bytes = 0; std::size_t tty_ring_capacity_bytes = 0;
std::unique_ptr<TtyProtocolWriter> tty_writer; std::unique_ptr<TtyProtocolWriter> tty_writer;
const bool tty_di1_group_average = cfg.tty_path.has_value() && cfg.di1_group_average; const bool tty_di1_group_average = cfg.tty_path.has_value() && cfg.di1_group_average;
const bool fast_tty_avg_stream_mode = tty_di1_group_average;
if (cfg.tty_path) { if (cfg.tty_path) {
const uint64_t typical_tty_batch_frames = (static_cast<uint64_t>(read_capacity_words) + 1U) / 2U; const uint64_t typical_tty_batch_frames = (static_cast<uint64_t>(read_capacity_words) + 1U) / 2U;
const uint64_t typical_tty_batch_bytes = typical_tty_batch_frames * sizeof(uint16_t) * 4U; const uint64_t typical_tty_batch_bytes = typical_tty_batch_frames * sizeof(uint16_t) * 4U;
@ -1333,25 +1337,39 @@ int run(const Config& cfg) {
tty_writer->emit_packet_start(); tty_writer->emit_packet_start();
} }
} }
CaptureFileWriter writer(cfg.csv_path, cfg.svg_path, cfg.live_html_path, cfg.live_json_path); std::unique_ptr<CaptureFileWriter> writer;
writer.initialize_live_plot(); if (!fast_tty_avg_stream_mode) {
writer.initialize_csv(cfg.channel_count, cfg.di1_mode == Di1Mode::Trace); writer = std::make_unique<CaptureFileWriter>(cfg.csv_path, cfg.svg_path, cfg.live_html_path, cfg.live_json_path);
std::cout << " live plot html: " << writer.live_html_path() << "\n" writer->initialize_live_plot();
<< " live plot data: " << writer.live_json_path() << "\n" writer->initialize_csv(cfg.channel_count, cfg.di1_mode == Di1Mode::Trace);
<< " tty stream output: " << (cfg.tty_path ? *cfg.tty_path : std::string("disabled")) << "\n" }
<< " tty ring buffer bytes: " if (fast_tty_avg_stream_mode) {
<< (cfg.tty_path ? std::to_string(tty_ring_capacity_bytes) : std::string("disabled")) << "\n" std::cout << " tty avg stream-only mode: enabled\n"
<< " recv block words: " << cfg.recv_block_words << "\n" << " tty stream output: " << *cfg.tty_path << "\n"
<< " input step words: " << cfg.input_step_words << "\n" << " tty ring buffer bytes: " << tty_ring_capacity_bytes << "\n"
<< " input buffer words: " << cfg.input_buffer_words << "\n" << " recv block words: " << cfg.recv_block_words << "\n"
<< " tty di1_group_avg: " << " input step words: " << cfg.input_step_words << "\n"
<< (tty_di1_group_average ? std::string("enabled") << " input buffer words: " << cfg.input_buffer_words << "\n"
: (cfg.di1_group_average ? std::string("requested, tty disabled") << " tty di1_group_avg: enabled\n"
: std::string("disabled"))) << " stream-only note: ignoring csv/svg/live_html/live_json/live_update_period_ms/svg_history_packets\n";
<< "\n" } else {
<< " SVG history packets kept in RAM: " std::cout << " live plot html: " << writer->live_html_path() << "\n"
<< ((cfg.svg_history_packets == 0U) ? std::string("all (unbounded)") : std::to_string(cfg.svg_history_packets)) << " live plot data: " << writer->live_json_path() << "\n"
<< "\n"; << " tty stream output: " << (cfg.tty_path ? *cfg.tty_path : std::string("disabled")) << "\n"
<< " tty ring buffer bytes: "
<< (cfg.tty_path ? std::to_string(tty_ring_capacity_bytes) : 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"
<< " tty di1_group_avg: "
<< (tty_di1_group_average ? std::string("enabled")
: (cfg.di1_group_average ? std::string("requested, tty disabled")
: std::string("disabled")))
<< "\n"
<< " SVG history packets kept in RAM: "
<< ((cfg.svg_history_packets == 0U) ? std::string("all (unbounded)") : std::to_string(cfg.svg_history_packets))
<< "\n";
}
ConsoleCtrlGuard console_guard; ConsoleCtrlGuard console_guard;
if (!console_guard.installed) { if (!console_guard.installed) {
@ -1362,7 +1380,7 @@ int run(const Config& cfg) {
device.streams_started = true; device.streams_started = true;
std::vector<uint32_t> raw(read_capacity_words); std::vector<uint32_t> raw(read_capacity_words);
std::vector<double> adc_buffer(read_capacity_words); std::vector<double> adc_buffer(fast_tty_avg_stream_mode ? 0U : read_capacity_words);
std::vector<double> adc_raw_buffer(read_capacity_words); std::vector<double> adc_raw_buffer(read_capacity_words);
std::vector<uint32_t> din_buffer(read_capacity_words); std::vector<uint32_t> din_buffer(read_capacity_words);
std::deque<double> pending_adc; std::deque<double> pending_adc;
@ -1374,8 +1392,12 @@ int run(const Config& cfg) {
TtyGroupAverageState tty_group_state; TtyGroupAverageState tty_group_state;
std::vector<uint16_t> tty_frame_words; std::vector<uint16_t> tty_frame_words;
tty_frame_words.reserve(static_cast<std::size_t>(read_capacity_words) * 2U + 16U); tty_frame_words.reserve(static_cast<std::size_t>(read_capacity_words) * 2U + 16U);
current_packet.reset(target_frames, cfg.channel_count); if (!fast_tty_avg_stream_mode) {
current_packet.reset(target_frames, cfg.channel_count);
}
std::size_t csv_global_frame_index = 0; std::size_t csv_global_frame_index = 0;
std::size_t packet_avg_steps = 0;
std::size_t fast_packet_frames = 0;
bool capture_started = false; bool capture_started = false;
bool stop_loop_requested = false; bool stop_loop_requested = false;
@ -1486,6 +1508,7 @@ int run(const Config& cfg) {
static_cast<uint16_t>(pack_raw_code_to_int16(ch1_avg)), static_cast<uint16_t>(pack_raw_code_to_int16(ch1_avg)),
static_cast<uint16_t>(pack_raw_code_to_int16(ch2_avg))); static_cast<uint16_t>(pack_raw_code_to_int16(ch2_avg)));
++tty_group_state.next_index; ++tty_group_state.next_index;
++packet_avg_steps;
}; };
auto flush_tty_frames = [&]() { auto flush_tty_frames = [&]() {
@ -1507,7 +1530,11 @@ int run(const Config& cfg) {
if (packet_active) { if (packet_active) {
return; return;
} }
current_packet.reset(target_frames, cfg.channel_count); packet_avg_steps = 0;
fast_packet_frames = 0;
if (!fast_tty_avg_stream_mode) {
current_packet.reset(target_frames, cfg.channel_count);
}
if (tty_di1_group_average) { if (tty_di1_group_average) {
tty_group_state.reset_packet(); tty_group_state.reset_packet();
append_tty_packet_start(); append_tty_packet_start();
@ -1521,77 +1548,97 @@ int run(const Config& cfg) {
tty_group_state.clear_step(); tty_group_state.clear_step();
} }
const std::size_t frames = current_packet.frame_count(cfg.channel_count); const std::size_t frames = fast_tty_avg_stream_mode
? fast_packet_frames
: current_packet.frame_count(cfg.channel_count);
if (frames != 0U) { if (frames != 0U) {
current_packet.channels[0].resize(frames);
if (cfg.channel_count >= 2U) {
current_packet.channels[1].resize(frames);
} else {
current_packet.channels[1].clear();
}
if (cfg.di1_mode == Di1Mode::Trace) {
current_packet.di1.resize(frames);
} else {
current_packet.di1.clear();
}
CapturePacket packet;
packet.packet_index = static_cast<std::size_t>(total_completed_packets + 1U);
packet.channel_count = cfg.channel_count;
packet.has_di1_trace = (cfg.di1_mode == Di1Mode::Trace);
packet.ch1 = std::move(current_packet.channels[0]);
packet.ch2 = std::move(current_packet.channels[1]);
packet.di1 = std::move(current_packet.di1);
const double packet_duration_ms = (1000.0 * static_cast<double>(frames)) / actual_frame_freq_hz; const double packet_duration_ms = (1000.0 * static_cast<double>(frames)) / actual_frame_freq_hz;
const double zeroed_fraction = (current_packet.stored_samples == 0U) if (fast_tty_avg_stream_mode) {
? 0.0 const std::size_t packet_index = static_cast<std::size_t>(total_completed_packets + 1U);
: (100.0 * static_cast<double>(current_packet.zeroed_samples) / std::cout << std::fixed << std::setprecision(3)
static_cast<double>(current_packet.stored_samples)); << "Packet " << packet_index
<< ": frames/ch=" << frames
<< ", duration_ms=" << packet_duration_ms
<< ", close_reason=" << packet_close_reason_to_string(reason)
<< ", avg_steps=" << packet_avg_steps
<< "\n";
} else {
current_packet.channels[0].resize(frames);
if (cfg.channel_count >= 2U) {
current_packet.channels[1].resize(frames);
} else {
current_packet.channels[1].clear();
}
if (cfg.di1_mode == Di1Mode::Trace) {
current_packet.di1.resize(frames);
} else {
current_packet.di1.clear();
}
std::cout << std::fixed << std::setprecision(3) CapturePacket packet;
<< "Packet " << packet.packet_index packet.packet_index = static_cast<std::size_t>(total_completed_packets + 1U);
<< ": frames/ch=" << frames packet.channel_count = cfg.channel_count;
<< ", duration_ms=" << packet_duration_ms packet.has_di1_trace = (cfg.di1_mode == Di1Mode::Trace);
<< ", close_reason=" << packet_close_reason_to_string(reason); packet.ch1 = std::move(current_packet.channels[0]);
if (cfg.di1_mode == Di1Mode::ZeroOnChange) { packet.ch2 = std::move(current_packet.channels[1]);
std::cout << ", zeroed_on_DI1_change=" << zeroed_fraction << "% (" packet.di1 = std::move(current_packet.di1);
<< current_packet.zeroed_samples << "/" << current_packet.stored_samples << ")";
} else if (cfg.di1_mode == Di1Mode::Trace) { const double zeroed_fraction = (current_packet.stored_samples == 0U)
std::cout << ", di1_trace_frames=" << packet.di1.size(); ? 0.0
: (100.0 * static_cast<double>(current_packet.zeroed_samples) /
static_cast<double>(current_packet.stored_samples));
std::cout << std::fixed << std::setprecision(3)
<< "Packet " << packet.packet_index
<< ": frames/ch=" << frames
<< ", duration_ms=" << packet_duration_ms
<< ", close_reason=" << packet_close_reason_to_string(reason);
if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
std::cout << ", zeroed_on_DI1_change=" << zeroed_fraction << "% ("
<< current_packet.zeroed_samples << "/" << current_packet.stored_samples << ")";
} else if (cfg.di1_mode == Di1Mode::Trace) {
std::cout << ", di1_trace_frames=" << packet.di1.size();
}
std::cout << "\n";
writer->append_csv_packet(packet, actual_frame_freq_hz, csv_global_frame_index);
packets.push_back(std::move(packet));
if ((cfg.svg_history_packets != 0U) && (packets.size() > cfg.svg_history_packets)) {
packets.pop_front();
}
const double elapsed_capture_s =
std::max(1e-9, static_cast<double>(tick_count_ms() - capture_loop_start) / 1000.0);
const double packets_per_second =
(elapsed_capture_s >= 0.1)
? (static_cast<double>(total_completed_packets + 1U) / elapsed_capture_s)
: 0.0;
const TickMs now = tick_count_ms();
const bool should_update_live =
(cfg.live_update_period_ms == 0U) ||
(last_live_update == 0U) ||
((now - last_live_update) >= cfg.live_update_period_ms);
if (should_update_live) {
writer->update_live_plot(packets.back(),
static_cast<std::size_t>(total_completed_packets + 1U),
packets_per_second,
actual_frame_freq_hz,
packet_close_reason_to_string(reason),
current_packet.zeroed_samples,
current_packet.stored_samples);
last_live_update = now;
}
} }
std::cout << "\n";
writer.append_csv_packet(packet, actual_frame_freq_hz, csv_global_frame_index);
++total_completed_packets; ++total_completed_packets;
++stats_completed_packets; ++stats_completed_packets;
packets.push_back(std::move(packet));
if ((cfg.svg_history_packets != 0U) && (packets.size() > cfg.svg_history_packets)) {
packets.pop_front();
}
const double elapsed_capture_s = const double elapsed_capture_s =
std::max(1e-9, static_cast<double>(tick_count_ms() - capture_loop_start) / 1000.0); std::max(1e-9, static_cast<double>(tick_count_ms() - capture_loop_start) / 1000.0);
const double packets_per_second = const double packets_per_second =
(elapsed_capture_s >= 0.1) (elapsed_capture_s >= 0.1)
? (static_cast<double>(total_completed_packets) / elapsed_capture_s) ? (static_cast<double>(total_completed_packets) / elapsed_capture_s)
: 0.0; : 0.0;
const TickMs now = tick_count_ms();
const bool should_update_live =
(cfg.live_update_period_ms == 0U) ||
(last_live_update == 0U) ||
((now - last_live_update) >= cfg.live_update_period_ms);
if (should_update_live) {
writer.update_live_plot(packets.back(),
static_cast<std::size_t>(total_completed_packets),
packets_per_second,
actual_frame_freq_hz,
packet_close_reason_to_string(reason),
current_packet.zeroed_samples,
current_packet.stored_samples);
last_live_update = now;
}
std::cout << std::fixed << std::setprecision(3) std::cout << std::fixed << std::setprecision(3)
<< " packets/s(avg)=" << packets_per_second; << " packets/s(avg)=" << packets_per_second;
if (elapsed_capture_s < 0.1) { if (elapsed_capture_s < 0.1) {
@ -1601,7 +1648,11 @@ int run(const Config& cfg) {
} }
packet_active = false; packet_active = false;
current_packet.reset(target_frames, cfg.channel_count); packet_avg_steps = 0;
fast_packet_frames = 0;
if (!fast_tty_avg_stream_mode) {
current_packet.reset(target_frames, cfg.channel_count);
}
if (tty_di1_group_average) { if (tty_di1_group_average) {
tty_group_state.clear_step(); tty_group_state.clear_step();
} }
@ -1666,13 +1717,16 @@ int run(const Config& cfg) {
continue; continue;
} }
uint32_t adc_count = static_cast<uint32_t>(adc_buffer.size()); uint32_t adc_count = fast_tty_avg_stream_mode ? static_cast<uint32_t>(adc_raw_buffer.size())
: static_cast<uint32_t>(adc_buffer.size());
uint32_t din_count = static_cast<uint32_t>(din_buffer.size()); uint32_t din_count = static_cast<uint32_t>(din_buffer.size());
const uint32_t process_flags =
fast_tty_avg_stream_mode ? 0U : static_cast<uint32_t>(X502_PROC_FLAGS_VOLT);
const int32_t process_err = api.ProcessData(device.hnd, const int32_t process_err = api.ProcessData(device.hnd,
raw.data(), raw.data(),
static_cast<uint32_t>(recvd), static_cast<uint32_t>(recvd),
X502_PROC_FLAGS_VOLT, process_flags,
adc_buffer.data(), fast_tty_avg_stream_mode ? adc_raw_buffer.data() : adc_buffer.data(),
&adc_count, &adc_count,
din_buffer.data(), din_buffer.data(),
&din_count); &din_count);
@ -1688,8 +1742,8 @@ int run(const Config& cfg) {
} }
expect_ok(api, process_err, "Process ADC+DIN data"); expect_ok(api, process_err, "Process ADC+DIN data");
uint32_t raw_adc_count = 0; uint32_t raw_adc_count = fast_tty_avg_stream_mode ? adc_count : 0U;
if (tty_writer) { if (tty_writer && !fast_tty_avg_stream_mode) {
for (std::size_t i = 0; i < static_cast<std::size_t>(recvd); ++i) { for (std::size_t i = 0; i < static_cast<std::size_t>(recvd); ++i) {
double raw_code = 0.0; double raw_code = 0.0;
if (!try_extract_raw_adc_code(raw[i], raw_code)) { if (!try_extract_raw_adc_code(raw[i], raw_code)) {
@ -1740,32 +1794,34 @@ int run(const Config& cfg) {
total_din_samples += din_count; total_din_samples += din_count;
stats_adc_samples += adc_count; stats_adc_samples += adc_count;
stats_din_samples += din_count; stats_din_samples += din_count;
for (uint32_t i = 0; i < adc_count; ++i) { if (fast_tty_avg_stream_mode) {
pending_adc.push_back(adc_buffer[i]);
}
if (tty_di1_group_average) {
for (uint32_t i = 0; i < raw_adc_count; ++i) { for (uint32_t i = 0; i < raw_adc_count; ++i) {
pending_adc_raw.push_back(adc_raw_buffer[i]); pending_adc_raw.push_back(adc_raw_buffer[i]);
} }
} else {
for (uint32_t i = 0; i < adc_count; ++i) {
pending_adc.push_back(adc_buffer[i]);
}
} }
for (uint32_t i = 0; i < din_count; ++i) { for (uint32_t i = 0; i < din_count; ++i) {
pending_din.push_back(din_buffer[i]); pending_din.push_back(din_buffer[i]);
} }
if ((pending_adc.size() > 1000000U) || if (((!fast_tty_avg_stream_mode) && (pending_adc.size() > 1000000U)) ||
(pending_din.size() > 1000000U) || (pending_din.size() > 1000000U) ||
(tty_di1_group_average && (pending_adc_raw.size() > 1000000U))) { (fast_tty_avg_stream_mode && (pending_adc_raw.size() > 1000000U))) {
fail("Internal backlog grew too large while aligning ADC and DIN samples"); fail("Internal backlog grew too large while aligning ADC and DIN samples");
} }
while (!pending_adc.empty() && while ((fast_tty_avg_stream_mode ? !pending_adc_raw.empty() : !pending_adc.empty()) &&
!pending_din.empty() && !pending_din.empty() &&
(!tty_di1_group_average || !pending_adc_raw.empty()) &&
!stop_loop_requested) { !stop_loop_requested) {
const double adc_value = pending_adc.front(); const double adc_value = fast_tty_avg_stream_mode ? 0.0 : pending_adc.front();
pending_adc.pop_front(); if (!fast_tty_avg_stream_mode) {
const double adc_raw_value = tty_di1_group_average ? pending_adc_raw.front() : 0.0; pending_adc.pop_front();
if (tty_di1_group_average) { }
const double adc_raw_value = fast_tty_avg_stream_mode ? pending_adc_raw.front() : 0.0;
if (fast_tty_avg_stream_mode) {
pending_adc_raw.pop_front(); pending_adc_raw.pop_front();
} }
@ -1840,40 +1896,53 @@ int run(const Config& cfg) {
const uint32_t lch = next_lch; const uint32_t lch = next_lch;
next_lch = (next_lch + 1U) % cfg.channel_count; next_lch = (next_lch + 1U) % cfg.channel_count;
if ((cfg.di1_mode == Di1Mode::Trace) && ((cfg.channel_count <= 1U) || (lch == 0U))) { if (!fast_tty_avg_stream_mode &&
(cfg.di1_mode == Di1Mode::Trace) &&
((cfg.channel_count <= 1U) || (lch == 0U))) {
current_packet.pending_frame_di1 = static_cast<uint8_t>(di1_level ? 1U : 0U); current_packet.pending_frame_di1 = static_cast<uint8_t>(di1_level ? 1U : 0U);
current_packet.pending_frame_di1_valid = true; current_packet.pending_frame_di1_valid = true;
} }
double stored_value = adc_value; if (fast_tty_avg_stream_mode) {
if ((cfg.di1_mode == Di1Mode::ZeroOnChange) && di1_changed) { if (fast_packet_frames < target_frames) {
stored_value = 0.0;
++total_zeroed_samples;
++stats_zeroed_samples;
++current_packet.zeroed_samples;
}
if (current_packet.channels[lch].size() < target_frames) {
current_packet.channels[lch].push_back(stored_value);
if (tty_di1_group_average) {
tty_group_state.add_sample(lch, adc_raw_value); tty_group_state.add_sample(lch, adc_raw_value);
} if (lch == (cfg.channel_count - 1U)) {
++current_packet.stored_samples; ++fast_packet_frames;
++total_stored_adc_samples; ++total_completed_frames;
++stats_stored_adc_samples; ++stats_completed_frames;
if (lch == (cfg.channel_count - 1U)) { }
if ((cfg.di1_mode == Di1Mode::Trace) && }
current_packet.pending_frame_di1_valid && } else {
(current_packet.di1.size() < target_frames)) { double stored_value = adc_value;
current_packet.di1.push_back(current_packet.pending_frame_di1); if ((cfg.di1_mode == Di1Mode::ZeroOnChange) && di1_changed) {
current_packet.pending_frame_di1_valid = false; stored_value = 0.0;
++total_zeroed_samples;
++stats_zeroed_samples;
++current_packet.zeroed_samples;
}
if (current_packet.channels[lch].size() < target_frames) {
current_packet.channels[lch].push_back(stored_value);
++current_packet.stored_samples;
++total_stored_adc_samples;
++stats_stored_adc_samples;
if (lch == (cfg.channel_count - 1U)) {
if ((cfg.di1_mode == Di1Mode::Trace) &&
current_packet.pending_frame_di1_valid &&
(current_packet.di1.size() < target_frames)) {
current_packet.di1.push_back(current_packet.pending_frame_di1);
current_packet.pending_frame_di1_valid = false;
}
++total_completed_frames;
++stats_completed_frames;
} }
++total_completed_frames;
++stats_completed_frames;
} }
} }
if (current_packet.frame_count(cfg.channel_count) >= target_frames) { const std::size_t completed_frames = fast_tty_avg_stream_mode
? fast_packet_frames
: current_packet.frame_count(cfg.channel_count);
if (completed_frames >= target_frames) {
finish_packet(PacketCloseReason::DurationLimit); finish_packet(PacketCloseReason::DurationLimit);
if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) { if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) {
stop_loop_requested = true; stop_loop_requested = true;
@ -1912,22 +1981,27 @@ int run(const Config& cfg) {
tty_final_stats = tty_writer->stats(); tty_final_stats = tty_writer->stats();
} }
const std::vector<CapturePacket> svg_packets(packets.begin(), packets.end()); if (!fast_tty_avg_stream_mode) {
writer.write(svg_packets, actual_frame_freq_hz, range_to_volts(cfg.range)); const std::vector<CapturePacket> svg_packets(packets.begin(), packets.end());
writer->write(svg_packets, actual_frame_freq_hz, range_to_volts(cfg.range));
std::size_t total_packet_frames = 0; std::size_t total_packet_frames = 0;
for (const auto& packet : svg_packets) { for (const auto& packet : svg_packets) {
total_packet_frames += (packet.channel_count <= 1U) total_packet_frames += (packet.channel_count <= 1U)
? packet.ch1.size() ? packet.ch1.size()
: std::min(packet.ch1.size(), packet.ch2.size()); : std::min(packet.ch1.size(), packet.ch2.size());
} }
std::cout << "Captured " << total_completed_packets << " packet(s), " std::cout << "Captured " << total_completed_packets << " packet(s), "
<< total_packet_frames << " total frames per channel kept for final SVG\n"; << total_packet_frames << " total frames per channel kept for final SVG\n";
if (cfg.di1_mode == Di1Mode::ZeroOnChange) { if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
std::cout << "ADC samples forced to 0 on DI1 change: " << total_zeroed_samples << "\n"; std::cout << "ADC samples forced to 0 on DI1 change: " << total_zeroed_samples << "\n";
} else if (cfg.di1_mode == Di1Mode::Trace) { } else if (cfg.di1_mode == Di1Mode::Trace) {
std::cout << "DI1 synchronous trace exported to CSV/live plot/SVG\n"; std::cout << "DI1 synchronous trace exported to CSV/live plot/SVG\n";
}
} else {
std::cout << "Captured " << total_completed_packets
<< " packet(s) in tty avg stream-only mode\n";
} }
std::cout << "Average stats: " std::cout << "Average stats: "
<< "MB/s=" << std::fixed << std::setprecision(3) << "MB/s=" << std::fixed << std::setprecision(3)
@ -1961,10 +2035,15 @@ int run(const Config& cfg) {
<< ", tty_frames_dropped=" << tty_final_stats.frames_dropped << ", tty_frames_dropped=" << tty_final_stats.frames_dropped
<< ", tty_ring_overflows=" << tty_final_stats.ring_overflows; << ", tty_ring_overflows=" << tty_final_stats.ring_overflows;
} }
std::cout << "\n" if (fast_tty_avg_stream_mode) {
<< "Final SVG packets retained in memory: " << svg_packets.size() << "\n" std::cout << ", tty avg stream-only mode=enabled";
<< "CSV: " << cfg.csv_path << "\n" }
<< "SVG: " << cfg.svg_path << "\n"; std::cout << "\n";
if (!fast_tty_avg_stream_mode) {
std::cout << "Final SVG packets retained in memory: " << packets.size() << "\n"
<< "CSV: " << cfg.csv_path << "\n"
<< "SVG: " << cfg.svg_path << "\n";
}
return 0; return 0;
} }

View File

@ -6,8 +6,6 @@ cd "$SCRIPT_DIR"
BIN="${BIN:-./main.exe}" BIN="${BIN:-./main.exe}"
TTY_PATH="${TTY_PATH:-/tmp/ttyADC_data}" TTY_PATH="${TTY_PATH:-/tmp/ttyADC_data}"
CSV_PATH="${CSV_PATH:-capture.csv}"
SVG_PATH="${SVG_PATH:-capture.svg}"
LIB_DIR="${LIB_DIR:-$HOME/.local/lib}" LIB_DIR="${LIB_DIR:-$HOME/.local/lib}"
if [[ ! -x "$BIN" ]]; then if [[ ! -x "$BIN" ]]; then
@ -31,6 +29,4 @@ exec "$BIN" \
duration_ms:100 \ duration_ms:100 \
packet_limit:0 \ packet_limit:0 \
"tty:${TTY_PATH}" \ "tty:${TTY_PATH}" \
"csv:${CSV_PATH}" \
"svg:${SVG_PATH}" \
"$@" "$@"