Skip to content

feature: upgrade to use udp unicast #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ And a toy device `synapse-sim` for local development,

options:
-h, --help show this help message and exit
--iface-ip IFACE_IP IP of the network interface to use for multicast traffic
--iface-ip IFACE_IP IP of the network interface to use for streaming data
--rpc-port RPC_PORT Port to listen for RPC requests
--discovery-port DISCOVERY_PORT
Port to listen for discovery requests
--discovery-addr DISCOVERY_ADDR
Multicast address to listen for discovery requests
UDP address to listen for discovery requests
--name NAME Device name
--serial SERIAL Device serial number
-v, --verbose Enable verbose output
Expand Down
3 changes: 2 additions & 1 deletion synapse/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import logging
from importlib import metadata
from synapse.cli import discover, rpc, streaming, offline_plot, files
from rich.logging import RichHandler


def main():
logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.INFO, handlers=[RichHandler()])

parser = argparse.ArgumentParser(
description="Synapse Device Manager",
Expand Down
65 changes: 37 additions & 28 deletions synapse/cli/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,32 @@
import sys

from rich.console import Console
from rich.live import Live
from rich.table import Table
from rich.spinner import Spinner


class DeviceTable:
def __init__(self):
self.devices = []
self.table = Table(show_lines=True, min_width=80)
self.table.title = Spinner("dots")
self.table.add_column("Name", justify="left")
self.table.add_column("Host", justify="right")

def add_device(self, device):
self.devices.append(device)
self.table.add_row(device.name, device.host)


def generate_layout(device_table):
device_count = len(device_table.devices)
platform_info = "⌘C to stop" if sys.platform == "darwin" else "Ctrl-C to stop"
spinner_text = (
f"Discovering Synapse devices... Found {device_count} so far ({platform_info})"
)
device_table.table.title.text = spinner_text
return device_table.table


def add_commands(subparsers):
Expand All @@ -15,33 +40,17 @@ def add_commands(subparsers):

def discover(args):
console = Console()
device_table = Table(
title="Synapse Devices", show_lines=True, row_styles=["dim", ""]
)
device_table.add_column("Name", justify="left")
device_table.add_column("Host", justify="right")

devices = []
with console.status(
"Discovering Synapse devices...", spinner="bouncingBall", spinner_style="yellow"
) as status:
for d in _discover_iter():
devices.append(d)
device_table.add_row(d.name, d.host)

# Clear the previous table and show the updated one
console.clear()
console.print(device_table)

if sys.platform == "darwin":
status.update(
f"Discovering Synapse devices... Found {len(devices)} so far (press ⌘C to stop)"
)
else:
status.update(
f"Discovering Synapse devices... Found {len(devices)} so far (press Ctrl-C to stop)"
)

if not devices:
device_table = DeviceTable()
try:
with Live(generate_layout(device_table), refresh_per_second=4) as live:
for d in _discover_iter():
device_table.add_device(d)
live.update(generate_layout(device_table))

except KeyboardInterrupt:
pass

console = Console()
if not device_table.devices:
console.print("[bold red]No Synapse devices found")
return
155 changes: 66 additions & 89 deletions synapse/cli/streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import synapse.client.channel as channel
import synapse.utils.ndtp_types as ndtp_types
import synapse.cli.synapse_plotter as plotter
from synapse.utils.packet_monitor import PacketMonitor

from rich.console import Console
from rich.live import Live
from rich.pretty import pprint


Expand Down Expand Up @@ -76,9 +78,7 @@ def read(args):
return
else:
console.print(f"[bold yellow]Overwriting existing files in {output_base}")

device = syn.Device(args.uri, args.verbose)

with console.status(
"Reading from Synapse Device", spinner="bouncingBall", spinner_style="green"
) as status:
Expand Down Expand Up @@ -210,112 +210,89 @@ def read(args):
if args.duration
else "Streaming data indefinitely"
)
with console.status(
status_title, spinner="bouncingBall", spinner_style="green"
) as status:
q = queue.Queue()
plot_q = queue.Queue() if args.plot else None

threads = []
stop = threading.Event()
if args.bin:
threads.append(
threading.Thread(
target=_binary_writer, args=(stop, q, num_ch, output_base)
)
)
else:
threads.append(
threading.Thread(target=_data_writer, args=(stop, q, output_base))
)
console.print(status_title)

if args.plot:
threads.append(
threading.Thread(target=_plot_data, args=(stop, plot_q, runtime_config))
)
q = queue.Queue()
plot_q = queue.Queue() if args.plot else None

threads = []
stop = threading.Event()
if args.bin:
threads.append(
threading.Thread(target=_binary_writer, args=(stop, q, num_ch, output_base))
)
else:
threads.append(
threading.Thread(target=_data_writer, args=(stop, q, output_base))
)

if args.plot:
threads.append(
threading.Thread(target=_plot_data, args=(stop, plot_q, runtime_config))
)

for thread in threads:
thread.start()

try:
read_packets(stream_out, q, plot_q, stop, args.duration)
except KeyboardInterrupt:
pass
finally:
console.print("Stopping read...")
stop.set()
for thread in threads:
thread.start()
thread.join()

try:
read_packets(stream_out, q, plot_q, args.duration)
except KeyboardInterrupt:
pass
finally:
print("Stopping read...")
stop.set()
for thread in threads:
thread.join()

if args.config:
console.print("Stopping device...")
if not device.stop():
console.print("[red]Failed to stop device")
console.print("Stopped")
if args.config:
console.print("Stopping device...")
if not device.stop():
console.print("[red]Failed to stop device")
console.print("Stopped")

console.print("[bold green]Streaming complete")
console.print("[cyan]================")
console.print(f"[cyan]Output directory: {output_base}/")
if args.bin:
console.print(f"[cyan]{output_base}.dat")
else:
console.print(f"[cyan]{output_base}.jsonl")
console.print(f"[cyan]Run `synapsectl plot --dir {output_base}/` to plot the data")
console.print("[cyan]================")


def read_packets(
node: syn.StreamOut,
q: queue.Queue,
plot_q: queue.Queue,
stop,
duration: Optional[int] = None,
num_ch: int = 32,
):
packet_count = 0
seq_number = None
dropped_packets = 0
start = time.time()
print_interval = 1000

print(
f"Reading packets for duration {duration} seconds"
if duration
else "Reading packets..."
)

while True:
header, data = node.read()
if not data:
continue

packet_count += 1

# Detect dropped packets via seq_number
if seq_number is None:
seq_number = header.seq_number
else:
expected = (seq_number + 1) % (2**16)
if header.seq_number != expected:
print(f"Seq out of order: got {header.seq_number}, expected {expected}")
dropped_packets += header.seq_number - expected
seq_number = header.seq_number

# Always add the data to the writer queues
q.put(data)
if plot_q:
plot_q.put(copy.deepcopy(data))

if packet_count == 1:
print(f"First packet received at {time.time() - start:.2f} seconds")

if packet_count % print_interval == 0:
print(
f"Recieved {packet_count} packets in {time.time() - start} seconds. Dropped {dropped_packets} packets ({(dropped_packets / packet_count) * 100}%)"
)
if duration and (time.time() - start) > duration:
break

print(
f"Recieved {packet_count} packets in {time.time() - start} seconds. Dropped {dropped_packets} packets ({(dropped_packets / packet_count) * 100}%)"
)
# Keep track of our statistics
monitor = PacketMonitor()
monitor.start_monitoring()

with Live(monitor.generate_stat_table(), refresh_per_second=4) as live:
while not stop.is_set():
read_ret = node.read()
if read_ret is None:
print("Could not get a valid read from the node")
continue

synapse_data, bytes_read = read_ret
if synapse_data is None or bytes_read == 0:
print("Could not read data from node")
continue
header, data = synapse_data
monitor.process_packet(header, data, bytes_read)
live.update(monitor.generate_stat_table())

# Always add the data to the writer queues
q.put(data)
if plot_q:
plot_q.put(copy.deepcopy(data))

if duration and (time.time() - start) > duration:
break


def _binary_writer(stop, q, num_ch, output_base):
Expand Down
1 change: 0 additions & 1 deletion synapse/client/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ def from_proto(cls, proto: NodeConfig):
oneof = proto.WhichOneof("config")
if oneof and hasattr(proto, oneof):
config = getattr(proto, oneof)

node = cls._from_proto(config)
node.id = proto.id
return node
Loading