Add single-channel capture mode

This commit is contained in:
kamil
2026-04-09 00:18:09 +03:00
parent 05d42efb98
commit a1e7afc8a7
5 changed files with 170 additions and 70 deletions

View File

@ -11,13 +11,23 @@
namespace {
constexpr std::size_t kLivePlotMaxColumns = 800;
struct PlotPoint {
std::size_t sample_index = 0;
double value = 0.0;
};
std::size_t packet_channel_count(const CapturePacket& packet) {
return (packet.channel_count <= 1U) ? 1U : 2U;
}
bool packet_has_ch2(const CapturePacket& packet) {
return packet_channel_count(packet) >= 2U;
}
std::size_t packet_frame_count(const CapturePacket& packet) {
return std::min(packet.ch1.size(), packet.ch2.size());
return packet_has_ch2(packet) ? std::min(packet.ch1.size(), packet.ch2.size()) : packet.ch1.size();
}
std::vector<PlotPoint> build_min_max_trace(const std::vector<double>& data, std::size_t max_columns) {
@ -156,8 +166,8 @@ void write_live_html_document(const std::string& path, const std::string& data_j
<< " <div class=\"status\" id=\"statusText\">Open this page once and leave it open. It reloads itself to pick up new packets.</div>\n"
<< " <canvas id=\"plot\" width=\"1200\" height=\"620\"></canvas>\n"
<< " <div class=\"legend\">\n"
<< " <span><span class=\"sw\" style=\"background:#005bbb\"></span>CH1</span>\n"
<< " <span><span class=\"sw\" style=\"background:#d62828\"></span>CH2</span>\n"
<< " <span id=\"legendCh1\"><span class=\"sw\" style=\"background:#005bbb\"></span>CH1</span>\n"
<< " <span id=\"legendCh2\"><span class=\"sw\" style=\"background:#d62828\"></span>CH2</span>\n"
<< " </div>\n"
<< " </div>\n"
<< " </div>\n"
@ -171,6 +181,7 @@ void write_live_html_document(const std::string& path, const std::string& data_j
<< " const zeroInfo = document.getElementById('zeroInfo');\n"
<< " const packetRateInfo = document.getElementById('packetRateInfo');\n"
<< " const updateInfo = document.getElementById('updateInfo');\n"
<< " const legendCh2 = document.getElementById('legendCh2');\n"
<< " function drawAxes(minY, maxY, maxT) {\n"
<< " const left = 78, top = 24, width = canvas.width - 112, height = canvas.height - 92;\n"
<< " ctx.clearRect(0, 0, canvas.width, canvas.height);\n"
@ -212,6 +223,7 @@ void write_live_html_document(const std::string& path, const std::string& data_j
<< " timingInfo.textContent = '';\n"
<< " zeroInfo.textContent = '';\n"
<< " packetRateInfo.textContent = '';\n"
<< " legendCh2.style.display = '';\n"
<< " updateInfo.textContent = 'Auto-refresh every 500 ms';\n"
<< " statusText.textContent = message;\n"
<< " ctx.clearRect(0, 0, canvas.width, canvas.height);\n"
@ -221,9 +233,10 @@ void write_live_html_document(const std::string& path, const std::string& data_j
<< " function renderPacket(data) {\n"
<< " const ch1 = data.ch1 || [];\n"
<< " const ch2 = data.ch2 || [];\n"
<< " const hasCh2 = (data.channelCount || 2) > 1 && ch2.length > 0;\n"
<< " const values = [];\n"
<< " ch1.forEach(p => values.push(p[1]));\n"
<< " ch2.forEach(p => values.push(p[1]));\n"
<< " if (hasCh2) ch2.forEach(p => values.push(p[1]));\n"
<< " let minY = Math.min(...values);\n"
<< " let maxY = Math.max(...values);\n"
<< " if (!Number.isFinite(minY) || !Number.isFinite(maxY) || minY === maxY) {\n"
@ -235,8 +248,9 @@ void write_live_html_document(const std::string& path, const std::string& data_j
<< " const maxT = Math.max(data.durationMs / 1000.0, 1e-9);\n"
<< " const box = drawAxes(minY, maxY, maxT);\n"
<< " drawTrace(ch1, '#005bbb', box, minY, maxY, maxT);\n"
<< " drawTrace(ch2, '#d62828', box, minY, maxY, maxT);\n"
<< " packetInfo.textContent = 'Packet #' + data.packetIndex + ' of ' + data.packetsSeen;\n"
<< " if (hasCh2) drawTrace(ch2, '#d62828', box, minY, maxY, maxT);\n"
<< " legendCh2.style.display = hasCh2 ? '' : 'none';\n"
<< " packetInfo.textContent = 'Packet #' + data.packetIndex + ' of ' + data.packetsSeen + ', channels: ' + (data.channelCount || 2);\n"
<< " timingInfo.textContent = 'Frames/ch: ' + data.framesPerChannel + ', duration: ' + data.durationMs.toFixed(3) + ' ms';\n"
<< " packetRateInfo.textContent = 'Packets/s: ' + data.packetsPerSecond.toFixed(3);\n"
<< " zeroInfo.textContent = 'Zeroed on DI1 change: ' + data.zeroedPercent.toFixed(3) + '% (' + data.zeroedSamples + '/' + data.storedSamples + ')';\n"
@ -306,14 +320,15 @@ void CaptureFileWriter::update_live_plot(const CapturePacket& packet,
const double zeroed_percent = (stored_samples == 0U)
? 0.0
: (100.0 * static_cast<double>(zeroed_samples) / static_cast<double>(stored_samples));
const auto trace1 = build_min_max_trace(packet.ch1, 1800);
const auto trace2 = build_min_max_trace(packet.ch2, 1800);
const auto trace1 = build_min_max_trace(packet.ch1, kLivePlotMaxColumns);
const auto trace2 = build_min_max_trace(packet.ch2, kLivePlotMaxColumns);
std::ostringstream json;
json << std::fixed << std::setprecision(9);
json << "{\n"
<< " \"status\": \"packet\",\n"
<< " \"packetIndex\": " << packet.packet_index << ",\n"
<< " \"channelCount\": " << packet_channel_count(packet) << ",\n"
<< " \"packetsSeen\": " << packets_seen << ",\n"
<< " \"packetsPerSecond\": " << packets_per_second << ",\n"
<< " \"framesPerChannel\": " << frames << ",\n"
@ -339,7 +354,12 @@ void CaptureFileWriter::write_csv(const std::vector<CapturePacket>& packets,
fail_write("Cannot open CSV for writing: " + csv_path_);
}
file << "packet_index,frame_index,global_frame_index,time_s,packet_time_s,ch1_v,ch2_v\n";
const std::size_t channel_count = packets.empty() ? 2U : packet_channel_count(packets.front());
file << "packet_index,frame_index,global_frame_index,time_s,packet_time_s,ch1_v";
if (channel_count >= 2U) {
file << ",ch2_v";
}
file << "\n";
file << std::fixed << std::setprecision(9);
std::size_t global_frame_index = 0;
@ -349,7 +369,11 @@ void CaptureFileWriter::write_csv(const std::vector<CapturePacket>& packets,
const double time_s = static_cast<double>(global_frame_index) / frame_freq_hz;
const double packet_time_s = static_cast<double>(i) / frame_freq_hz;
file << packet.packet_index << "," << i << "," << global_frame_index << ","
<< time_s << "," << packet_time_s << "," << packet.ch1[i] << "," << packet.ch2[i] << "\n";
<< time_s << "," << packet_time_s << "," << packet.ch1[i];
if (channel_count >= 2U) {
file << "," << packet.ch2[i];
}
file << "\n";
++global_frame_index;
}
}
@ -366,14 +390,17 @@ void CaptureFileWriter::write_svg(const std::vector<CapturePacket>& packets,
std::size_t total_frames = 0;
double min_y = std::numeric_limits<double>::infinity();
double max_y = -std::numeric_limits<double>::infinity();
const std::size_t channel_count = packets.empty() ? 2U : packet_channel_count(packets.front());
for (const auto& packet : packets) {
const std::size_t frames = packet_frame_count(packet);
total_frames += frames;
for (std::size_t i = 0; i < frames; ++i) {
min_y = std::min(min_y, packet.ch1[i]);
max_y = std::max(max_y, packet.ch1[i]);
min_y = std::min(min_y, packet.ch2[i]);
max_y = std::max(max_y, packet.ch2[i]);
if (packet_has_ch2(packet)) {
min_y = std::min(min_y, packet.ch2[i]);
max_y = std::max(max_y, packet.ch2[i]);
}
}
}
@ -444,9 +471,11 @@ void CaptureFileWriter::write_svg(const std::vector<CapturePacket>& packets,
file << " <polyline fill=\"none\" stroke=\"#005bbb\" stroke-width=\"1.2\" points=\""
<< polyline_points(trace1, frame_offset, total_frames, min_y, max_y, left, top, plot_w, plot_h)
<< "\"/>\n";
file << " <polyline fill=\"none\" stroke=\"#d62828\" stroke-width=\"1.2\" points=\""
<< polyline_points(trace2, frame_offset, total_frames, min_y, max_y, left, top, plot_w, plot_h)
<< "\"/>\n";
if (packet_has_ch2(packet)) {
file << " <polyline fill=\"none\" stroke=\"#d62828\" stroke-width=\"1.2\" points=\""
<< polyline_points(trace2, frame_offset, total_frames, min_y, max_y, left, top, plot_w, plot_h)
<< "\"/>\n";
}
if (packets.size() <= 24U) {
file << " <text x=\"" << (x0 + 6.0) << "\" y=\"" << (top + 18.0)
@ -458,7 +487,8 @@ void CaptureFileWriter::write_svg(const std::vector<CapturePacket>& packets,
}
file << " <text x=\"" << left << "\" y=\"24\" font-size=\"22\" font-family=\"Segoe UI, Arial, sans-serif\""
<< " fill=\"#203040\">E-502 capture: " << packets.size() << " packet(s), CH1 and CH2</text>\n";
<< " fill=\"#203040\">E-502 capture: " << packets.size() << " packet(s), "
<< ((channel_count >= 2U) ? "CH1 and CH2" : "CH1 only") << "</text>\n";
file << " <text x=\"" << left << "\" y=\"" << (height - 22)
<< "\" font-size=\"16\" font-family=\"Segoe UI, Arial, sans-serif\" fill=\"#425466\">time, s</text>\n";
file << " <text x=\"18\" y=\"" << (top + 16)
@ -485,10 +515,12 @@ void CaptureFileWriter::write_svg(const std::vector<CapturePacket>& packets,
<< "\" stroke=\"#005bbb\" stroke-width=\"3\"/>\n";
file << " <text x=\"" << (width - 220) << "\" y=\"" << (legend_y + 4)
<< "\" font-size=\"14\" font-family=\"Segoe UI, Arial, sans-serif\" fill=\"#203040\">CH1</text>\n";
file << " <line x1=\"" << (width - 160) << "\" y1=\"" << legend_y
<< "\" x2=\"" << (width - 120) << "\" y2=\"" << legend_y
<< "\" stroke=\"#d62828\" stroke-width=\"3\"/>\n";
file << " <text x=\"" << (width - 110) << "\" y=\"" << (legend_y + 4)
<< "\" font-size=\"14\" font-family=\"Segoe UI, Arial, sans-serif\" fill=\"#203040\">CH2</text>\n";
if (channel_count >= 2U) {
file << " <line x1=\"" << (width - 160) << "\" y1=\"" << legend_y
<< "\" x2=\"" << (width - 120) << "\" y2=\"" << legend_y
<< "\" stroke=\"#d62828\" stroke-width=\"3\"/>\n";
file << " <text x=\"" << (width - 110) << "\" y=\"" << (legend_y + 4)
<< "\" font-size=\"14\" font-family=\"Segoe UI, Arial, sans-serif\" fill=\"#203040\">CH2</text>\n";
}
file << "</svg>\n";
}