flatudp: the source, kind, mock sender and stream probes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e8e71b32f
commit
78a29fbc39
13 files changed
+962
-7
No files matched your search
+133
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user