Files
MARTe_IO_Components/Docs/UDPStreamer.md
T
Martino Ferrari e3389f932b FIrs
2026-05-15 17:42:14 +02:00

7.7 KiB
Raw Blame History

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 pathSynchronise() 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 quantizationfloat32/float64 signals can be linearly quantized to uint8, int8, uint16, or int16 on the wire, reducing bandwidth significantly.
  • Temporal arrays — signals with NumberOfElements > 1 can carry per-sample time metadata via TimeMode and TimeSignal, 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 TimeModePacketTime
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 "" for InputSignals.


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.
  • readyBuffer and wireBuffer are allocated once in AllocateMemory().
  • 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