flatudp: the source, kind, mock sender and stream probes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-02 01:05:38 +01:00
1 parent 6e8e71b32f
commit 78a29fbc39
13 files changed
+962 -7

No files matched your search

+133 -1
View File
@@ -9,6 +9,7 @@
#include "source/probe_source.hpp"
#include "source/e1608/e1608_source.hpp"
#include "source/flatudp/flatudp_signals.hpp"
#include "source/flatudp/flatudp_source.hpp"
#include "source/registry.hpp"
#include "ui_app.hpp"
@@ -1938,6 +1939,14 @@ gboolean source_kinds_probe(gpointer user) {
c.set("trigger", "rising");
c.set("dir1", "o");
c.set("plot0", "1");
} else if (id == "flatudp") {
c.set("port", "44600");
c.set("bind", "10.0.0.8");
c.set("multicast", "239.1.2.3");
c.set("iface", "10.0.0.9");
c.set("rate", "2500");
c.set("endian", "big");
c.set("signals", "Trigger:uint8,Ia:float32,Wave:int16[4]");
}
return c;
};
@@ -2102,6 +2111,31 @@ bool flatudp_codec_check() {
return true;
}
flatudp::FlatUdpSource* flatudp_source(App* a) {
return a->acq != nullptr ? dynamic_cast<flatudp::FlatUdpSource*>(a->acq->source()) : nullptr;
}
std::uint64_t g_flatudp_frames_before = 0;
/// The mock's signal k carries (i * (k + 1) + element) % 100 in packet i, so two consecutive
/// samples of a scalar signal differ by k + 1 modulo 100. Returns the number of steps that do not,
/// over a window clear of the head the producer is writing; -1 when it cannot be read.
long flatudp_breaks(App* a, std::size_t channel, long step) {
if (channel >= a->model->channels().size()) return -1;
const ChannelBuffer& ch = a->model->live().channel(channel);
const u64 n = ch.size();
const u64 want = 400;
if (n <= want + 50) return -1;
std::vector<f32> v(static_cast<std::size_t>(want));
ch.raw(n - want - 50, n - 50, v.data());
long breaks = 0;
for (std::size_t i = 1; i < v.size(); ++i) {
const long d = std::lround(double(v[i])) - std::lround(double(v[i - 1]));
if (((d % 100) + 100) % 100 != step) ++breaks;
}
return breaks;
}
} // namespace
/// Phase 2, 1 s after the controls were driven: the stream, the calibration, the capture button,
@@ -2247,12 +2281,110 @@ gboolean e1608_probe_reconnect(gpointer user) {
return G_SOURCE_REMOVE;
}
/// The flat UDP source's probe. For now only the pure codec check; it needs no flatudp source.
/// After the mock's gap: the stream's frame count moved on and the source is connected again.
gboolean flatudp_probe_resumed(gpointer user) {
App* a = static_cast<App*>(user);
if (a->window == nullptr) return G_SOURCE_REMOVE;
flatudp::FlatUdpSource* src = flatudp_source(a);
const std::uint64_t after = a->model->stats().frames.load(std::memory_order_relaxed);
const bool up = src != nullptr && src->connected() &&
a->model->stats().connected.load(std::memory_order_relaxed);
std::fprintf(stderr, "[flatudp] resumed: connected=%d frames_before=%" G_GUINT64_FORMAT
" frames_after=%" G_GUINT64_FORMAT " ok=%d\n",
up ? 1 : 0, g_flatudp_frames_before, after,
up && after > g_flatudp_frames_before ? 1 : 0);
std::fflush(stderr);
return G_SOURCE_REMOVE;
}
/// Inside the mock's gap: 2 s of silence must have raised `Disconnected`.
gboolean flatudp_probe_gap(gpointer user) {
App* a = static_cast<App*>(user);
if (a->window == nullptr) return G_SOURCE_REMOVE;
flatudp::FlatUdpSource* src = flatudp_source(a);
if (src == nullptr) return G_SOURCE_REMOVE;
const bool src_up = src->connected();
const bool model_up = a->model->stats().connected.load(std::memory_order_relaxed);
g_flatudp_frames_before = a->model->stats().frames.load(std::memory_order_relaxed);
std::fprintf(stderr, "[flatudp] silence: connected=%d model_connected=%d ok=%d\n", src_up ? 1 : 0,
model_up ? 1 : 0, !src_up && !model_up ? 1 : 0);
std::fflush(stderr);
g_timeout_add(4000, flatudp_probe_resumed, a);
return G_SOURCE_REMOVE;
}
/// Phase 2, 1 s after the signals were enabled: the stream, its channels, the value continuity, the
/// wrong-size accounting and the capture button.
gboolean flatudp_probe_report(gpointer user) {
App* a = static_cast<App*>(user);
if (a->window == nullptr) return G_SOURCE_REMOVE;
flatudp::FlatUdpSource* src = flatudp_source(a);
if (src == nullptr) return G_SOURCE_REMOVE;
const flatudp::FlatUdpSettings& s = src->settings();
const bool up = a->model->stats().connected.load(std::memory_order_relaxed);
const std::vector<SignalInfo>& sigs = a->model->signals();
std::string names, expect_names;
std::size_t expect_channels = 0; // one channel per signal: a vector's elements are consecutive samples
for (const SignalInfo& si : sigs) names += (names.empty() ? "" : ",") + si.name;
for (const flatudp::Row& r : s.rows) {
expect_names += (expect_names.empty() ? "" : ",") + r.name;
++expect_channels;
}
const double rate = sigs.empty() ? 0.0 : sigs.front().sampling_rate;
const std::uint64_t frames = a->model->stats().frames.load(std::memory_order_relaxed);
const bool stream_ok = up && names == expect_names && std::fabs(rate - s.rate) < 1e-6 && frames > 0;
std::fprintf(stderr,
"[flatudp] stream: connected=%d signals=%zu names=%s expect=%s frames=%" G_GUINT64_FORMAT
" rate=%.3f ok=%d\n",
up ? 1 : 0, sigs.size(), names.c_str(), expect_names.c_str(), frames, rate,
stream_ok ? 1 : 0);
const std::size_t channels = a->model->channels().size();
std::fprintf(stderr, "[flatudp] channels: count=%zu expect=%zu ok=%d\n", channels, expect_channels,
channels == expect_channels ? 1 : 0);
// Only the first two signals are checked, and only in an all-scalar list: a vector row makes the
// model span each frame by its elements, and the scalar channels are then padded with zeros.
if (up && s.rows.size() >= 2 && flatudp::all_scalar(s.rows)) {
const long b0 = flatudp_breaks(a, 0, 1);
const long b1 = flatudp_breaks(a, 1, 2);
std::fprintf(stderr, "[flatudp] continuity: ch0_breaks=%ld ch1_breaks=%ld ok=%d\n", b0, b1,
b0 == 0 && b1 == 0 ? 1 : 0);
} else {
std::fprintf(stderr, "[flatudp] continuity: skipped (needs a connected all-scalar list of two or more)\n");
}
const std::uint64_t wrong = src->wrong_size_datagrams();
const std::string msg = src->last_message();
std::fprintf(stderr, "[flatudp] wrong size: count=%" G_GUINT64_FORMAT " message='%s' ok=%d\n", wrong,
msg.c_str(), wrong >= 1 && msg.find("expected") != std::string::npos ? 1 : 0);
std::fprintf(stderr, "[flatudp] capture button: present=%d sensitive=%d\n",
a->capture_button != nullptr ? 1 : 0,
a->capture_button != nullptr && gtk_widget_get_sensitive(a->capture_button) ? 1 : 0);
std::fprintf(stderr, "[flatudp] state: valid=%" G_GUINT64_FORMAT " wrong=%" G_GUINT64_FORMAT "\n",
src->valid_datagrams(), wrong);
std::fflush(stderr);
return G_SOURCE_REMOVE;
}
/// Phase 1: the codec check (it needs no flatudp source), then every signal enabled in one structural
/// change, and the report a second later.
gboolean flatudp_probe(gpointer user) {
App* a = static_cast<App*>(user);
if (a->window == nullptr) return G_SOURCE_REMOVE;
std::fprintf(stderr, "[flatudp] codec: ok=%d\n", flatudp_codec_check() ? 1 : 0);
if (flatudp_source(a) == nullptr) {
std::fprintf(stderr, "[flatudp] no flatudp source (run with --source flatudp)\n");
std::fflush(stderr);
return G_SOURCE_REMOVE;
}
std::vector<std::size_t> all(a->model->signals().size());
for (std::size_t i = 0; i < all.size(); ++i) all[i] = i;
if (!all.empty()) a->model->set_signals_enabled(all, true);
std::fflush(stderr);
g_timeout_add(1000, flatudp_probe_report, a);
return G_SOURCE_REMOVE;
}