WebUI: per-signal vscale toolbar, active-signal highlighting, zoom fix
- Per-signal, per-plot vertical scale state (sigVScale keyed by plotId:signalKey) so the same signal in two plots has fully independent vscale config - Active signal redrawn on top of all series with 2× line width for clear visual identification; badge click toggles selection and opens/closes the embedded vscale toolbar (click same badge again to deselect) - Vscale configurator moved from floating popup to a slim toolbar strip anchored inside the plot card, with an × close button - Trigger dropdown shows one entry per array signal with [0…N-1] label; opening it shows an index-picker dialog to choose the element - Zoom resampling: when server returns no data for a zoomed range, fall back to the local circular buffer instead of returning empty arrays Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,13 +50,17 @@ namespace MARTe {
|
||||
/**
|
||||
* @brief Publishing mode for the background sender thread.
|
||||
*
|
||||
* - Strict: send one UDP packet on every Synchronise() call (legacy behaviour).
|
||||
* - Auto: buffer successive ticks and only flush when the HRT-based flush
|
||||
* interval has elapsed. Controlled by MinRefreshRate (Hz).
|
||||
* - Strict: send one packet every Synchronise() call (default).
|
||||
* - Accumulate: buffer N successive snapshots and flush either when the next
|
||||
* snapshot would exceed MaxPayloadSize or when 1/MinRefreshRate
|
||||
* has elapsed. Scalars expand to arrays; the first scalar with
|
||||
* Unit="us"/"ns" is auto-promoted as the FullArray time reference.
|
||||
* - Decimate: send one packet every Ratio Synchronise() calls.
|
||||
*/
|
||||
typedef enum {
|
||||
UDPStreamerPublishStrict = 0u, /**< Send on every RT cycle (default) */
|
||||
UDPStreamerPublishAuto = 1u /**< Rate-limited: flush at MinRefreshRate Hz */
|
||||
UDPStreamerPublishStrict = 0u, /**< Send on every RT cycle (default) */
|
||||
UDPStreamerPublishAccumulate = 1u, /**< Accumulate until size/time limit; then flush */
|
||||
UDPStreamerPublishDecimate = 2u /**< Send 1 packet every Ratio cycles */
|
||||
} UDPStreamerPublishMode;
|
||||
|
||||
/**
|
||||
@@ -100,6 +104,7 @@ struct UDPStreamerSignalInfo {
|
||||
uint32 srcByteSize; /**< Bytes in MARTe2 memory */
|
||||
uint32 wireByteSize; /**< Bytes on the wire (may differ when quantized) */
|
||||
uint32 bufferOffset; /**< Byte offset in the flat MemoryDataSourceI memory buffer */
|
||||
bool accumulated; /**< True when this scalar was expanded to flushCount elements in Auto accumulation mode */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -145,59 +150,134 @@ static const uint8 UDPS_TYPECODE_FLOAT64 = 9u;
|
||||
static const uint8 UDPS_TYPECODE_UNKNOWN = 255u;
|
||||
|
||||
/**
|
||||
* @brief A DataSource that streams MARTe2 signals to a single UDP client.
|
||||
* @brief A DataSource that streams MARTe2 signals to UDP clients.
|
||||
*
|
||||
* @details This output DataSource accepts signals from GAMs and forwards them
|
||||
* asynchronously to a connected UDP client. A dedicated background thread handles
|
||||
* all network I/O so that the real-time thread is only blocked for a fast spinlock
|
||||
* asynchronously over the network. A dedicated background thread handles all
|
||||
* network I/O so that the real-time thread is only blocked by a fast spinlock
|
||||
* and a memcpy during Synchronise().
|
||||
*
|
||||
* Protocol:
|
||||
* - Client sends CONNECT → server replies with CONFIG describing all signals.
|
||||
* - Server sends DATA packets (fragmented if needed) on every RT cycle.
|
||||
* - Client sends ACK packets (optional, for monitoring loss).
|
||||
* - Client sends DISCONNECT to terminate the session.
|
||||
* Two operating modes are selected by the presence or absence of MulticastGroup:
|
||||
*
|
||||
* Signal configuration syntax:
|
||||
* @par Unicast mode (default — no MulticastGroup)
|
||||
* The server opens a single UDP socket on Port. The client initiates the session
|
||||
* by sending a CONNECT packet to that port. The server replies with a CONFIG
|
||||
* packet on the same socket and subsequently sends DATA packets directly to the
|
||||
* client's address. Any number of clients may connect sequentially (one at a
|
||||
* time); a new CONNECT evicts the previous client.
|
||||
*
|
||||
* @par Multicast mode (MulticastGroup specified)
|
||||
* The server opens a TCP listener on Port for control traffic and a UDP socket
|
||||
* aimed at MulticastGroup:DataPort for data traffic. The client:
|
||||
* 1. Connects to Port via TCP and sends a CONNECT packet.
|
||||
* 2. Receives the CONFIG packet over TCP.
|
||||
* 3. Joins the multicast group (MulticastGroup:DataPort) to receive DATA packets.
|
||||
* Multiple clients may receive data simultaneously by joining the same group.
|
||||
* A new TCP CONNECT evicts the previous client from the control channel; the
|
||||
* multicast data stream continues regardless.
|
||||
*
|
||||
* @par Packet protocol (both modes)
|
||||
* - Client → Server: CONNECT (initiates session), DISCONNECT (terminates session),
|
||||
* ACK (optional, for packet-loss monitoring).
|
||||
* - Server → Client: CONFIG (signal metadata), DATA (signal values, possibly
|
||||
* fragmented into multiple datagrams if payload exceeds MaxPayloadSize).
|
||||
*
|
||||
* @par Top-level configuration parameters
|
||||
* | Parameter | Type | Default | Description |
|
||||
* |-----------------|---------|---------|-------------|
|
||||
* | Port | uint16 | 44500 | TCP control port (multicast) or UDP server port (unicast). Values ≤ 1024 produce a warning. |
|
||||
* | MulticastGroup | string | *(absent)* | **Enables multicast mode.** IPv4 multicast address, e.g. `"239.0.0.1"`. Must be in 224.0.0.0/4. Absent or empty = unicast. |
|
||||
* | DataPort | uint16 | Port+1 | UDP port for multicast DATA datagrams. Ignored in unicast mode. Must be non-zero and differ from Port. |
|
||||
* | MaxPayloadSize | uint32 | 1400 | Maximum bytes of signal payload per UDP datagram (excluding the 17-byte header). Larger signals are fragmented. |
|
||||
* | PublishingMode | string | Strict | `Strict`: send one packet every Synchronise() call. `Auto`: rate-limited; flush only when MinRefreshRate interval has elapsed. |
|
||||
* | MinRefreshRate | float64 | — | Required when PublishingMode = Auto. Flush frequency in Hz (e.g. 120.0). |
|
||||
* | MaxBatchSize | uint32 | 1 | Optional when PublishingMode = Auto. Number of RT cycles to accumulate before flushing one packet. Scalar signals are expanded to arrays of MaxBatchSize elements; the first scalar with Unit="us" or "ns" is auto-promoted as the per-sample FullArray timestamp reference for all other scalars. When omitted or 1, the most-recent single value is sent at MinRefreshRate. |
|
||||
* | CPUMask | uint32 | 0xFFFFFFFF | CPU affinity bitmask for the background thread. |
|
||||
* | StackSize | uint32 | (MARTe2 default) | Stack size in bytes for the background thread. |
|
||||
*
|
||||
* @par Per-signal configuration parameters
|
||||
* | Parameter | Type | Default | Description |
|
||||
* |------------------|---------|-------------|-------------|
|
||||
* | Type | string | — | MARTe2 type name (uint8, int16, float32, float64, …). Mandatory. |
|
||||
* | NumberOfDimensions | uint8 | 0 | 0 = scalar, 1 = array, 2 = matrix. |
|
||||
* | NumberOfElements | uint32 | 1 | Total element count (rows × cols for matrices). |
|
||||
* | Unit | string | "" | Physical unit label, e.g. `"Pa"` or `"m/s"`. Transmitted in CONFIG for display purposes. |
|
||||
* | RangeMin | float64 | 0.0 | Minimum of the physical range. Required when QuantizedType is not `none`. |
|
||||
* | RangeMax | float64 | 1.0 | Maximum of the physical range. Required when QuantizedType is not `none`. |
|
||||
* | QuantizedType | string | none | Wire quantization for float signals: `none` \| `uint8` \| `int8` \| `uint16` \| `int16`. Reduces wire bandwidth at the cost of precision. Only valid for float32/float64 signals. |
|
||||
* | TimeMode | string | PacketTime | How the signal's time axis is encoded (see below). |
|
||||
* | TimeSignal | string | — | Name of the signal that carries timestamps. Required when TimeMode ≠ PacketTime. |
|
||||
* | SamplingRate | float64 | — | Signal sampling rate in Hz. Required when TimeMode = FirstSample or LastSample. |
|
||||
*
|
||||
* @par TimeMode values
|
||||
* | Value | Description |
|
||||
* |--------------|-------------|
|
||||
* | PacketTime | Uses the HRT counter captured at Synchronise() time as the single packet timestamp. No dedicated time signal needed. |
|
||||
* | FullArray | TimeSignal has the same NumberOfElements as this signal; one timestamp per element. |
|
||||
* | FirstSample | TimeSignal is a scalar = timestamp of the first element; subsequent elements are spaced by 1/SamplingRate. |
|
||||
* | LastSample | TimeSignal is a scalar = timestamp of the last element; elements are spaced backwards by 1/SamplingRate. |
|
||||
*
|
||||
* @par Example — unicast mode
|
||||
* <pre>
|
||||
* +UDPStreamer1 = {
|
||||
* Class = UDPStreamer
|
||||
* Port = 44500 // Optional (default 44500)
|
||||
* MaxPayloadSize = 1400 // Optional (default 1400); max payload bytes per UDP datagram
|
||||
* CPUMask = 0x2 // Optional, affinity for background thread
|
||||
* StackSize = 1048576 // Optional, stack size for background thread
|
||||
* PublishingMode = "Auto" // Optional: "Strict" (default) | "Auto"
|
||||
* MinRefreshRate = 120 // Optional (Hz); only used when PublishingMode = "Auto"
|
||||
* +Streamer = {
|
||||
* Class = UDPStreamer
|
||||
* Port = 44500
|
||||
* MaxPayloadSize = 1400
|
||||
* PublishingMode = "Strict"
|
||||
* Signals = {
|
||||
* Time = {
|
||||
* Type = uint64
|
||||
* }
|
||||
* Pressure = {
|
||||
* Type = float32
|
||||
* NumberOfDimensions = 1
|
||||
* NumberOfElements = 100
|
||||
* Unit = "Pa" // Optional
|
||||
* RangeMin = 0.0 // Optional (required for quantization)
|
||||
* RangeMax = 1000000.0 // Optional (required for quantization)
|
||||
* QuantizedType = uint16 // Optional: none|uint8|int8|uint16|int16
|
||||
* TimeMode = LastSample // Optional: PacketTime|FullArray|FirstSample|LastSample
|
||||
* TimeSignal = Time // Required when TimeMode != PacketTime
|
||||
* SamplingRate = 10000.0 // Required when TimeMode = FirstSample or LastSample
|
||||
* Type = float32
|
||||
* NumberOfDimensions = 1
|
||||
* NumberOfElements = 100
|
||||
* Unit = "Pa"
|
||||
* RangeMin = 0.0
|
||||
* RangeMax = 1000000.0
|
||||
* QuantizedType = uint16
|
||||
* TimeMode = LastSample
|
||||
* TimeSignal = Time
|
||||
* SamplingRate = 10000.0
|
||||
* }
|
||||
* Temperature = {
|
||||
* Type = float64
|
||||
* Unit = "K"
|
||||
* Type = float64
|
||||
* Unit = "degC"
|
||||
* TimeMode = PacketTime
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Notes:
|
||||
* - QuantizedType is only valid for float32/float64 signals.
|
||||
* - TimeMode = PacketTime uses the HRT counter captured in Synchronise().
|
||||
* - TimeMode = FullArray requires TimeSignal to have the same NumberOfElements.
|
||||
* - TimeMode = FirstSample/LastSample requires a scalar TimeSignal and SamplingRate.
|
||||
* @par Example — multicast mode
|
||||
* <pre>
|
||||
* +Streamer = {
|
||||
* Class = UDPStreamer
|
||||
* Port = 44500 // TCP control port
|
||||
* MulticastGroup = "239.0.0.1" // Enables multicast mode
|
||||
* DataPort = 44501 // UDP data port (default: Port+1)
|
||||
* MaxPayloadSize = 1400
|
||||
* PublishingMode = "Auto"
|
||||
* MinRefreshRate = 60
|
||||
* Signals = {
|
||||
* Time = {
|
||||
* Type = uint64
|
||||
* }
|
||||
* Voltage = {
|
||||
* Type = float32
|
||||
* NumberOfDimensions = 1
|
||||
* NumberOfElements = 1000
|
||||
* Unit = "V"
|
||||
* RangeMin = -10.0
|
||||
* RangeMax = 10.0
|
||||
* QuantizedType = int16
|
||||
* TimeMode = FirstSample
|
||||
* TimeSignal = Time
|
||||
* SamplingRate = 100000.0
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
class UDPStreamer : public MemoryDataSourceI, public EmbeddedServiceMethodBinderI {
|
||||
public:
|
||||
@@ -214,8 +294,11 @@ public:
|
||||
virtual ~UDPStreamer();
|
||||
|
||||
/**
|
||||
* @brief Parses top-level configuration parameters (Port, MaxPayloadSize, CPUMask, StackSize).
|
||||
* @return true if all mandatory parameters are valid.
|
||||
* @brief Parses top-level configuration parameters.
|
||||
* @details Reads Port, MaxPayloadSize, CPUMask, StackSize, PublishingMode,
|
||||
* MinRefreshRate, MulticastGroup, and DataPort from the configuration node.
|
||||
* Sets useMulticast and dataPort when MulticastGroup is present and valid.
|
||||
* @return true if all mandatory parameters are valid and consistent.
|
||||
*/
|
||||
virtual bool Initialise(StructuredDataI &data);
|
||||
|
||||
@@ -312,6 +395,14 @@ private:
|
||||
*/
|
||||
void HandleTCPConnect(const uint8 *buf, uint32 size);
|
||||
|
||||
/**
|
||||
* @brief Serializes an accumulated batch into wireBuffer.
|
||||
* @param src Flat buffer [numSamples × totalSrcBytes], slot 0 = oldest.
|
||||
* @param timestamps HRT counters per slot; timestamps[0] is the packet timestamp.
|
||||
* @param numSamples Actual number of filled slots to serialize.
|
||||
*/
|
||||
void SerializeAccumulated(const uint8 *src, const uint64 *timestamps, uint32 numSamples);
|
||||
|
||||
/**
|
||||
* @brief Maps a MARTe2 TypeDescriptor to the UDPS_TYPECODE_* constants.
|
||||
*/
|
||||
@@ -325,6 +416,20 @@ private:
|
||||
UDPStreamerPublishMode publishMode; /**< Strict or Auto publishing mode */
|
||||
float64 minRefreshRate; /**< Minimum flush rate (Hz) for Auto mode */
|
||||
uint64 flushPeriodTicks; /**< HRT ticks per flush interval (computed from minRefreshRate) */
|
||||
/* Accumulate mode — dynamic batch parameters */
|
||||
uint32 maxBatchCount; /**< Max snapshots that fit in MaxPayloadSize (Accumulate) */
|
||||
uint32 singleCycleWireBytes; /**< Wire bytes for all accumulated signals per snapshot */
|
||||
uint32 fixedWireBytes; /**< Wire bytes for non-accumulated signals (arrays, once per packet) */
|
||||
volatile uint64 lastPublishTs; /**< HRT counter of last successful flush (Accumulate mode) */
|
||||
uint8 *accumBuffer; /**< Heap: [maxBatchCount × totalSrcBytes] linear fill */
|
||||
uint64 *accumTimestamps; /**< Heap: [maxBatchCount] HRT counter per snapshot */
|
||||
uint32 accumFill; /**< Slots filled in accumBuffer (0..maxBatchCount) */
|
||||
uint64 *readyTimestamps; /**< Heap: [maxBatchCount] HRT for completed ready batch */
|
||||
uint64 *scratchTimestamps; /**< Heap: [maxBatchCount] background-thread local copy */
|
||||
uint32 readyFill; /**< Snapshot count in the ready batch */
|
||||
/* Decimate mode */
|
||||
uint32 decimateRatio; /**< Send 1 packet every decimateRatio Synchronise() calls */
|
||||
uint32 decimateCounter; /**< Current decimate cycle counter */
|
||||
|
||||
/* Signal metadata */
|
||||
uint32 numSigs; /**< Number of signals */
|
||||
|
||||
Reference in New Issue
Block a user