7.7 KiB
7.7 KiB
UDPStreamer DataSource
UDPStreamer is a MARTe2 output DataSource that streams signals from a real-time application
to a single connected UDP client. It is fully asynchronous from the RT thread: the RT cycle
only performs a fast spinlock + memcpy, while all network I/O runs on a dedicated background
thread.
Key Features
- Zero-copy RT path —
Synchronise()only locks, copies signal memory, and posts a semaphore. - Single-client model — one client at a time; a new CONNECT replaces the previous session.
- Packet fragmentation — large payloads are split into ≤
MaxPayloadSize-byte datagrams, each with a header carrying fragment index and total count so the client can reassemble them. - Signal quantization —
float32/float64signals can be linearly quantized touint8,int8,uint16, orint16on the wire, reducing bandwidth significantly. - Temporal arrays — signals with
NumberOfElements > 1can carry per-sample time metadata viaTimeModeandTimeSignal, enabling high-frequency burst transmission (e.g. 1 000 samples per RT cycle at 1 MSps).
Configuration
+Streamer = {
Class = UDPStreamer
// Network
Port = 44500 // UDP port the server listens on (default: 44500)
MaxPayloadSize = 1400 // Maximum bytes per UDP datagram (default: 1400)
// Must be > 17 (header size). Tune for MTU.
// Background thread (optional)
CPUMask = 0x2 // CPU affinity mask for the network thread
StackSize = 1048576 // Stack size in bytes (default: 1 MiB)
Signals = {
// ── Scalar signal ────────────────────────────────────────────────────
Time = {
Type = uint32
Unit = "us" // Optional: physical unit string (informational)
}
// ── Float signal with quantization ───────────────────────────────────
Pressure = {
Type = float32
Unit = "Pa"
RangeMin = 0.0 // Required when QuantizedType is set
RangeMax = 1000000.0 // Required when QuantizedType is set
QuantizedType = uint16 // none | uint8 | int8 | uint16 | int16
}
// ── Temporal array (packed burst) ────────────────────────────────────
Channel1 = {
Type = float32
NumberOfDimensions = 1
NumberOfElements = 1000 // N samples per RT cycle
Unit = "V"
TimeMode = FirstSample // see Time Modes below
TimeSignal = Time // name of a scalar signal in this DataSource
SamplingRate = 1000000.0 // Hz — used by client to reconstruct timestamps
}
}
}
Top-level Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
Port |
uint16 | 44500 | UDP server port |
MaxPayloadSize |
uint32 | 1400 | Max payload bytes per UDP datagram (min 18) |
CPUMask |
uint32 | 0 (any) | Background thread CPU affinity |
StackSize |
uint32 | 1 048 576 | Background thread stack size in bytes |
Per-signal Parameters
| Parameter | Type | Default | Applies to |
|---|---|---|---|
Unit |
string | "" |
Any type — informational, forwarded to client in CONFIG |
RangeMin |
float64 | 0.0 | float32/float64 with QuantizedType |
RangeMax |
float64 | 1.0 | float32/float64 with QuantizedType |
QuantizedType |
string | none |
float32/float64 only |
TimeMode |
string | PacketTime |
Signals with NumberOfElements > 1 |
TimeSignal |
string | — | Required when TimeMode ≠ PacketTime |
SamplingRate |
float64 | 0.0 | Required when TimeMode = FirstSample or LastSample |
Quantization Types
| Value | Wire type | Bit depth | Notes |
|---|---|---|---|
none |
same as source | — | Raw copy, no quantization |
uint8 |
uint8 | 8-bit | Maps [RangeMin, RangeMax] → [0, 255] |
int8 |
int8 | 8-bit | Maps [RangeMin, RangeMax] → [-127, 127] |
uint16 |
uint16 | 16-bit | Maps [RangeMin, RangeMax] → [0, 65 535] |
int16 |
int16 | 16-bit | Maps [RangeMin, RangeMax] → [-32 767, 32 767] |
Quantization formula (unsigned, e.g. uint16):
normalized = clamp((value - RangeMin) / (RangeMax - RangeMin), 0.0, 1.0)
wire_value = (uint16)(normalized × 65535)
Time Modes
| Value | Meaning | Requirements |
|---|---|---|
PacketTime |
The HRT counter captured at Synchronise() time is used as the packet timestamp. No per-signal time metadata. |
— |
FullArray |
TimeSignal carries one timestamp per element (same NumberOfElements). |
TimeSignal must have the same NumberOfElements. |
FirstSample |
TimeSignal is a scalar giving the timestamp of element [0]. Elements [1..N-1] are inferred at 1/SamplingRate intervals. |
Scalar TimeSignal; SamplingRate > 0. |
LastSample |
Same as FirstSample but TimeSignal is the timestamp of element [N-1]. |
Scalar TimeSignal; SamplingRate > 0. |
Broker
UDPStreamer uses MemoryMapSynchronisedOutputBroker for output signals. This broker is
called automatically by the MARTe2 scheduler after all GAMs in the thread have executed.
Note: Input signals are not supported —
GetBrokerName()returns""forInputSignals.
Lifecycle
PrepareNextState() ← opens UDP server socket, starts background thread
↓
[RT thread, each cycle]
GAMs execute ← write into Streamer signal memory via broker
Synchronise() ← spinlock + memcpy to readyBuffer + post dataSem
↓
[Background thread]
Poll serverSocket ← receive CONNECT / DISCONNECT / ACK
Wait dataSem ← woken by Synchronise()
QuantizeAndSerialize() ← build wire payload
SendFragmented() ← send DATA fragments to client
Performance Notes
- The RT path (
Synchronise()) performs only:FastLock()+memcpy+FastUnLock()+EventSem.Post(). No socket calls, no heap allocation. readyBufferandwireBufferare allocated once inAllocateMemory().- If no client is connected the background thread skips serialisation entirely.
- Packet loss is tolerated silently. ACK tracking is reserved for future use.
Example: minimal scalar streaming
+Data = {
Class = ReferenceContainer
+Streamer = {
Class = UDPStreamer
Port = 44500
Signals = {
Counter = { Type = uint32 }
Voltage = { Type = float32; Unit = "V"; RangeMin = -10.0; RangeMax = 10.0; QuantizedType = uint16 }
}
}
}
Example: high-frequency burst
+Streamer = {
Class = UDPStreamer
Port = 44500
MaxPayloadSize = 1400
Signals = {
T0 = { Type = uint32; Unit = "us" }
Ch1 = {
Type = float32
NumberOfDimensions = 1
NumberOfElements = 1000 // 1000 samples per RT cycle
Unit = "V"
TimeMode = FirstSample
TimeSignal = T0
SamplingRate = 1000000.0 // 1 MSps
}
}
}
With MaxPayloadSize = 1400, a single 1000-element float32 signal produces:
payload = 8 B (HRT timestamp) + 4 B (T0/uint32) + 4000 B (float32×1000) = 4012 B
fragments = ceil(4012 / 1383) = 3