#!/usr/bin/env python3 # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2026 Martino Ferrari """A minimal UDPStreamer (UDPS) producer, for exercising udpscope without MARTe2. Unicast only: binds `--port`, waits for a CONNECT datagram, then streams CONFIG once and DATA packets to whoever connected. Publish mode is Accumulate, so each packet carries `--batch` consecutive samples per (scalar) signal -- the shape the viewer's per-sample path expects. Signals: `time` (the stream's own clock, seconds), `sine`, `square`, `ramp`. Everything is float64 and unquantised. ./tools/mock_udpstreamer.py --port 8000 --rate 10000 --batch 100 """ import argparse import math import random import socket import struct import time MAGIC = 0x53504455 PKT_DATA, PKT_CONFIG, PKT_ACK, PKT_CONNECT, PKT_DISCONNECT = 0, 1, 2, 3, 4 T_FLOAT64 = 9 QUANT_NONE = 0 TIME_PACKET, TIME_FULL_ARRAY, TIME_FIRST_SAMPLE = 0, 1, 2 PUBLISH_ACCUMULATE = 1 NO_TIME_SIGNAL = 0xFFFFFFFF def header(pkt_type, counter, payload_bytes): return struct.pack("= 0.0 else -1.0 for t in stamps] ramp = [(freq * t) % 1.0 * 2.0 - 1.0 for t in stamps] # In FIRST_SAMPLE mode only element 0 of the time signal is meaningful: the producer # stamps the first sample of the batch and says nothing about the rest. A scalar # signal in Accumulate mode still occupies `batch` slots on the wire (the client # reads `num_samples` values for any 1-element signal), so the anchor is repeated -- # which is exactly the shape that made udpscope derive a step of zero. wire_time = [t0] * batch if time_mode == TIME_FIRST_SAMPLE else stamps payload = struct.pack("= 17: magic, ptype = struct.unpack_from(" 0.0: pdt = dt * (1.0 + random.uniform(-args.clock_jitter, args.clock_jitter)) sock.sendto(build_data(counter, t_stream, pdt, args.batch, args.freq, time_mode), peer) counter += 1 t_stream += args.batch * pdt next_send += period if args.duration > 0.0 and started is not None and now - started > args.duration: print("mock udpstreamer: done (%d packets)" % counter, flush=True) return if __name__ == "__main__": main()