feat(tcp): TCPStreamer DataSources, in-process TCPStreamService, stream multiplexing and TCPStreamJoin
Add TCPStreamer/TCPStreamerClient (TCP transport behind the UDPSServerI/UDPSClientI seam), an optional single in-process TCPStreamService serving many streams over worker threads, multiplexed SUBSCRIBE (comma list, stream index in fragmentIdx), and TCPStreamJoin, which merges several streams into one sample-and-hold stream paced by a master. StreamHub takes a comma list of streams over one connection. Includes GTests, E2E scenarios s58-s73 and docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
5562877c99
commit
50d85363a3
79 files changed
+12348
-138
No files matched your search
@@ -103,10 +103,23 @@ def _streamer_sig(sig):
|
||||
return f"{sig['name']} = {{ {' '.join(parts)} }}"
|
||||
|
||||
|
||||
SERVICE_NAME = "Svc"
|
||||
|
||||
|
||||
def _service_port(scenario):
|
||||
"""Port of the shared TCPStreamService (first source's port)."""
|
||||
return scenario["sources"][0]["udp_port"]
|
||||
|
||||
|
||||
def _streamer_block(src, scenario):
|
||||
s = src["signals"]
|
||||
parts = [f"Port = {src['udp_port']}",
|
||||
f"MaxPayloadSize = {scenario['max_payload']}",
|
||||
net = scenario["network"]
|
||||
if net == "tcp_service":
|
||||
# Port, MaxClients, ... belong to the service; the streamer only joins it.
|
||||
parts = [f'Service = "{SERVICE_NAME}"', f'StreamName = "{S.stream_name(scenario, src)}"']
|
||||
else:
|
||||
parts = [f"Port = {src['udp_port']}"]
|
||||
parts += [f"MaxPayloadSize = {scenario['max_payload']}",
|
||||
f'PublishingMode = "{scenario["publishing"]}"']
|
||||
if scenario["publishing"] == "Accumulate":
|
||||
parts.append(f"MinRefreshRate = {scenario['min_refresh_hz']}")
|
||||
@@ -118,7 +131,8 @@ def _streamer_block(src, scenario):
|
||||
iface = src.get("interface") or _mcast_interface_ip(src["multicast_group"])
|
||||
parts.append(f'Interface = "{iface}"')
|
||||
sigs = " ".join(_streamer_sig(sig) for sig in s)
|
||||
return (f" +Streamer_{src['id']} = {{ Class = UDPStreamer "
|
||||
cls = "UDPStreamer" if net in ("unicast", "multicast") else "TCPStreamer"
|
||||
return (f" +Streamer_{src['id']} = {{ Class = {cls} "
|
||||
f"{' '.join(parts)} Signals = {{ {sigs} }} }}")
|
||||
|
||||
|
||||
@@ -202,7 +216,19 @@ def write_marte_cfg(scenario, path, input_bin, tap_bin=None):
|
||||
data_block = "\n".join(datas)
|
||||
thread_list = " ".join(thread_funcs)
|
||||
|
||||
cfg = f"""$ChainE2E = {{
|
||||
service_block = ""
|
||||
if scenario["network"] == "tcp_service":
|
||||
service_block = (f"+{SERVICE_NAME} = {{ Class = TCPStreamService "
|
||||
f"Port = {_service_port(scenario)} Workers = 2 "
|
||||
f"MaxClients = 16 MaxStreams = 16 }}\n")
|
||||
hub = scenario.get("hub")
|
||||
if hub and hub["mode"] == "join":
|
||||
names = ",".join(S.stream_name(scenario, x) for x in scenario["sources"])
|
||||
service_block += (f"+Join = {{ Class = TCPStreamJoin Service = \"{SERVICE_NAME}\" "
|
||||
f"StreamName = \"{hub['name']}\" Sources = \"{names}\" "
|
||||
f"Master = \"{hub['master']}\" }}\n")
|
||||
|
||||
cfg = f"""{service_block}$ChainE2E = {{
|
||||
Class = RealTimeApplication
|
||||
+Functions = {{
|
||||
Class = ReferenceContainer
|
||||
@@ -227,10 +253,26 @@ def write_marte_cfg(scenario, path, input_bin, tap_bin=None):
|
||||
def write_hub_cfg(scenario, path):
|
||||
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
||||
src_blocks = []
|
||||
for src in scenario["sources"]:
|
||||
hub = scenario.get("hub")
|
||||
if hub and hub["mode"] == "mux":
|
||||
names = ",".join(S.stream_name(scenario, x) for x in scenario["sources"])
|
||||
src_blocks.append(
|
||||
f' {hub["node"]} = {{ Label = "chain" Addr = "127.0.0.1" '
|
||||
f'Port = {_service_port(scenario)} Transport = "tcp" StreamName = "{names}" }}')
|
||||
elif hub and hub["mode"] == "join":
|
||||
src_blocks.append(
|
||||
f' {hub["name"]} = {{ Label = "chain {hub["name"]}" Addr = "127.0.0.1" '
|
||||
f'Port = {_service_port(scenario)} Transport = "tcp" StreamName = "{hub["name"]}" }}')
|
||||
for src in ([] if hub else scenario["sources"]):
|
||||
net = scenario["network"]
|
||||
port = _service_port(scenario) if net == "tcp_service" else src["udp_port"]
|
||||
parts = [f'Label = "chain {src["id"]}"', 'Addr = "127.0.0.1"',
|
||||
f"Port = {src['udp_port']}"]
|
||||
if scenario["network"] == "multicast":
|
||||
f"Port = {port}"]
|
||||
if net in ("tcp", "tcp_service"):
|
||||
parts.append('Transport = "tcp"')
|
||||
if net == "tcp_service":
|
||||
parts.append(f'StreamName = "{src["id"]}"')
|
||||
if net == "multicast":
|
||||
parts.append(f'MulticastGroup = "{src["multicast_group"]}"')
|
||||
parts.append(f"DataPort = {src['data_port']}")
|
||||
src_blocks.append(f" {src['id']} = {{ {' '.join(parts)} }}")
|
||||
|
||||
Reference in new issue
Block a user