Add Auto publishing mode to UDPStreamer; optimise WebUI hub

UDPStreamer:
- Add PublishingMode = "Strict" | "Auto" config parameter
- Add MinRefreshRate (Hz) for Auto mode; uses HRT phase-locked tick
  counting to rate-limit sends without accumulation buffers
- Fix high-frequency integration test configs to use newline-separated
  key-value pairs (MARTe2 StandardParser does not treat ';' as delimiter)
- Add 3 new unit tests (AutoMode_Valid, AutoMode_MissingRefreshRate,
  UnknownPublishingMode); all 33 tests passing

WebUI hub:
- Skip ring-buffer LTTB writes when zoom has not been accessed in 10 s,
  reducing idle CPU usage
- Use unsafe float64→bytes reinterpretation to eliminate per-element
  encoding overhead in the hot broadcast path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-05-26 22:29:40 +02:00
parent b465dd680c
commit f85ab8652c
8 changed files with 549 additions and 127 deletions
@@ -61,6 +61,21 @@ TEST(UDPStreamerGTest, TestInitialise_ZeroStackSize) {
ASSERT_TRUE(test.TestInitialise_ZeroStackSize());
}
TEST(UDPStreamerGTest, TestInitialise_AutoMode_Valid) {
UDPStreamerTest test;
ASSERT_TRUE(test.TestInitialise_AutoMode_Valid());
}
TEST(UDPStreamerGTest, TestInitialise_AutoMode_MissingRefreshRate) {
UDPStreamerTest test;
ASSERT_TRUE(test.TestInitialise_AutoMode_MissingRefreshRate());
}
TEST(UDPStreamerGTest, TestInitialise_UnknownPublishingMode) {
UDPStreamerTest test;
ASSERT_TRUE(test.TestInitialise_UnknownPublishingMode());
}
TEST(UDPStreamerGTest, TestGetBrokerName_Output) {
UDPStreamerTest test;
ASSERT_TRUE(test.TestGetBrokerName_Output());
@@ -230,6 +230,41 @@ bool UDPStreamerTest::TestInitialise_ZeroStackSize() {
return ok;
}
bool UDPStreamerTest::TestInitialise_AutoMode_Valid() {
using namespace MARTe;
UDPStreamer ds;
ConfigurationDatabase cdb;
cdb.Write("Port", 44502u);
cdb.Write("PublishingMode", "Auto");
cdb.Write("MinRefreshRate", 120.0);
cdb.CreateRelative("Signals");
cdb.MoveToRoot();
return ds.Initialise(cdb);
}
bool UDPStreamerTest::TestInitialise_AutoMode_MissingRefreshRate() {
using namespace MARTe;
UDPStreamer ds;
ConfigurationDatabase cdb;
cdb.Write("Port", 44503u);
cdb.Write("PublishingMode", "Auto");
/* MinRefreshRate intentionally omitted */
cdb.CreateRelative("Signals");
cdb.MoveToRoot();
return !ds.Initialise(cdb); /* Must fail */
}
bool UDPStreamerTest::TestInitialise_UnknownPublishingMode() {
using namespace MARTe;
UDPStreamer ds;
ConfigurationDatabase cdb;
cdb.Write("Port", 44504u);
cdb.Write("PublishingMode", "Invalid");
cdb.CreateRelative("Signals");
cdb.MoveToRoot();
return !ds.Initialise(cdb); /* Must fail */
}
bool UDPStreamerTest::TestGetBrokerName_Output() {
using namespace MARTe;
UDPStreamer ds;
@@ -1157,36 +1192,88 @@ bool UDPStreamerTest::TestIsClientConnected_InitiallyFalse() {
* Fragment count at MaxPayloadSize=1400:
* chunk = 1400 - 17 = 1383 B → ceil(8016 / 1383) = 6 fragments
*/
/* MARTe2 StandardParser uses whitespace/newlines as delimiters.
* Semicolons are NOT statement separators — they are consumed as part of
* token values. All inline { key = val; key = val } blocks below have been
* rewritten to use one key-value pair per line. */
#define HF_FUNCTIONS_BLOCK \
" +Functions = {\n" \
" Class = ReferenceContainer\n" \
" +Writer = {\n" \
" Class = UDPStreamerTestOutputGAM\n" \
" OutputSignals = {\n" \
" T0 = { DataSource = Streamer; Type = uint64 }\n" \
" Ch1 = { DataSource = Streamer; Type = int16; NumberOfElements = 1000 }\n" \
" Ch2 = { DataSource = Streamer; Type = int16; NumberOfElements = 1000 }\n" \
" Ch3 = { DataSource = Streamer; Type = int16; NumberOfElements = 1000 }\n" \
" Ch4 = { DataSource = Streamer; Type = int16; NumberOfElements = 1000 }\n" \
" T0 = {\n" \
" DataSource = Streamer\n" \
" Type = uint64\n" \
" }\n" \
" Ch1 = {\n" \
" DataSource = Streamer\n" \
" Type = int16\n" \
" NumberOfElements = 1000\n" \
" }\n" \
" Ch2 = {\n" \
" DataSource = Streamer\n" \
" Type = int16\n" \
" NumberOfElements = 1000\n" \
" }\n" \
" Ch3 = {\n" \
" DataSource = Streamer\n" \
" Type = int16\n" \
" NumberOfElements = 1000\n" \
" }\n" \
" Ch4 = {\n" \
" DataSource = Streamer\n" \
" Type = int16\n" \
" NumberOfElements = 1000\n" \
" }\n" \
" }\n" \
" }\n" \
" }\n"
#define HF_SIGNALS_BLOCK \
" Signals = {\n" \
" T0 = { Type = uint64 }\n" \
" Ch1 = { Type = int16; NumberOfDimensions = 1; NumberOfElements = 1000;\n" \
" TimeMode = FirstSample; TimeSignal = T0; SamplingRate = 1000000.0 }\n" \
" Ch2 = { Type = int16; NumberOfDimensions = 1; NumberOfElements = 1000;\n" \
" TimeMode = FirstSample; TimeSignal = T0; SamplingRate = 1000000.0 }\n" \
" Ch3 = { Type = int16; NumberOfDimensions = 1; NumberOfElements = 1000;\n" \
" TimeMode = FirstSample; TimeSignal = T0; SamplingRate = 1000000.0 }\n" \
" Ch4 = { Type = int16; NumberOfDimensions = 1; NumberOfElements = 1000;\n" \
" TimeMode = FirstSample; TimeSignal = T0; SamplingRate = 1000000.0 }\n" \
" T0 = {\n" \
" Type = uint64\n" \
" }\n" \
" Ch1 = {\n" \
" Type = int16\n" \
" NumberOfDimensions = 1\n" \
" NumberOfElements = 1000\n" \
" TimeMode = FirstSample\n" \
" TimeSignal = T0\n" \
" SamplingRate = 1000000.0\n" \
" }\n" \
" Ch2 = {\n" \
" Type = int16\n" \
" NumberOfDimensions = 1\n" \
" NumberOfElements = 1000\n" \
" TimeMode = FirstSample\n" \
" TimeSignal = T0\n" \
" SamplingRate = 1000000.0\n" \
" }\n" \
" Ch3 = {\n" \
" Type = int16\n" \
" NumberOfDimensions = 1\n" \
" NumberOfElements = 1000\n" \
" TimeMode = FirstSample\n" \
" TimeSignal = T0\n" \
" SamplingRate = 1000000.0\n" \
" }\n" \
" Ch4 = {\n" \
" Type = int16\n" \
" NumberOfDimensions = 1\n" \
" NumberOfElements = 1000\n" \
" TimeMode = FirstSample\n" \
" TimeSignal = T0\n" \
" SamplingRate = 1000000.0\n" \
" }\n" \
" }\n"
#define HF_TAIL_BLOCK \
" +Timings = { Class = TimingDataSource }\n" \
" +Timings = {\n" \
" Class = TimingDataSource\n" \
" }\n" \
" }\n" \
" +States = {\n" \
" Class = ReferenceContainer\n" \
@@ -1194,36 +1281,57 @@ bool UDPStreamerTest::TestIsClientConnected_InitiallyFalse() {
" Class = RealTimeState\n" \
" +Threads = {\n" \
" Class = ReferenceContainer\n" \
" +Thread1 = { Class = RealTimeThread; Functions = { Writer } }\n" \
" +Thread1 = {\n" \
" Class = RealTimeThread\n" \
" Functions = { Writer }\n" \
" }\n" \
" }\n" \
" }\n" \
" }\n" \
" +Scheduler = { Class = GAMScheduler; TimingDataSource = Timings }\n" \
" +Scheduler = {\n" \
" Class = GAMScheduler\n" \
" TimingDataSource = Timings\n" \
" }\n" \
"}\n"
static const MARTe::char8 *const HF_CFG_ALLOC =
"+Test = { Class = RealTimeApplication\n"
"+Test = {\n"
" Class = RealTimeApplication\n"
HF_FUNCTIONS_BLOCK
" +Data = { Class = ReferenceContainer\n"
" +Streamer = { Class = UDPStreamer; Port = 44650; MaxPayloadSize = 1400\n"
" +Data = {\n"
" Class = ReferenceContainer\n"
" +Streamer = {\n"
" Class = UDPStreamer\n"
" Port = 44650\n"
" MaxPayloadSize = 1400\n"
HF_SIGNALS_BLOCK
" }\n"
HF_TAIL_BLOCK;
static const MARTe::char8 *const HF_CFG_FRAG =
"+Test = { Class = RealTimeApplication\n"
"+Test = {\n"
" Class = RealTimeApplication\n"
HF_FUNCTIONS_BLOCK
" +Data = { Class = ReferenceContainer\n"
" +Streamer = { Class = UDPStreamer; Port = 44651; MaxPayloadSize = 1400\n"
" +Data = {\n"
" Class = ReferenceContainer\n"
" +Streamer = {\n"
" Class = UDPStreamer\n"
" Port = 44651\n"
" MaxPayloadSize = 1400\n"
HF_SIGNALS_BLOCK
" }\n"
HF_TAIL_BLOCK;
static const MARTe::char8 *const HF_CFG_INTEGRITY =
"+Test = { Class = RealTimeApplication\n"
"+Test = {\n"
" Class = RealTimeApplication\n"
HF_FUNCTIONS_BLOCK
" +Data = { Class = ReferenceContainer\n"
" +Streamer = { Class = UDPStreamer; Port = 44653; MaxPayloadSize = 1400\n"
" +Data = {\n"
" Class = ReferenceContainer\n"
" +Streamer = {\n"
" Class = UDPStreamer\n"
" Port = 44653\n"
" MaxPayloadSize = 1400\n"
HF_SIGNALS_BLOCK
" }\n"
HF_TAIL_BLOCK;
@@ -162,6 +162,21 @@ public:
*/
bool TestQuantization_Uint8Clamping();
/**
* @brief Tests PublishingMode = "Auto" with a valid MinRefreshRate.
*/
bool TestInitialise_AutoMode_Valid();
/**
* @brief Tests that PublishingMode = "Auto" without MinRefreshRate is rejected.
*/
bool TestInitialise_AutoMode_MissingRefreshRate();
/**
* @brief Tests that an unknown PublishingMode string is rejected.
*/
bool TestInitialise_UnknownPublishingMode();
/**
* @brief Tests the GetPort() accessor.
*/