# 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`/`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 `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 `""` 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
```
